مال من باشید و با آهنگسازی و بوم تعامل را اضافه کنید

روز ولنتاین در حال نزدیک شدن است ، و در حالی که من نسخه فنلاندی (“روز دوست”) را ترجیح می دهم ، من الهام گرفتم که در موضوع تعطیلات یک برنامه نویسی خلاق را انجام دهم.
صنعت فیلم سازی محور هالیوود (علاوه بر جهانی سازی به طور کلی) آن آب نبات های قلبی را نیز به شمال منتقل کرده است. در حالی که برخی از تصاویر را برای الهام بخش می کنم ، تصمیم گرفتم از آنها برای قطعه بعدی که می نویسم استفاده کنم.
و این همه نیست! من همچنین می خواستم سری پست های وبلاگ مربوط به بوم را ادامه دهم ، بنابراین این یکی از مفهوم دیگری با بوم عبور می کند: چگونه می توان حرکات ورودی اشاره گر را تشخیص داد و تعامل را اضافه کرد.
بنابراین ، ما امروز چه می سازیم؟ در اینجا ویدئویی آورده شده است که برخی از آب نبات های قلبی با پیام ها و نحوه افزایش آنها در هنگام لمس کردن آنها را نشان می دهد:
بیایید کد نویسی کنیم!
نقاشی قلب
این پروژه بر اساس پست وبلاگ قبلی من ، با استفاده از SVG در بوم با Compose Multiplatform ساخته شده است ، که نحوه ترسیم از رشته های مسیر SVG برای تهیه بوم را توضیح می دهد. من یک تصویر SVG را بر روی FIGMA تهیه کردم و سپس رشته های مسیر را برای جلب قلب ها استخراج کردم.
ما از برخی توابع از پست وبلاگ قبلی استفاده خواهیم کرد: Float.scaleToSize
وت PathNode.scaleTo
بشر من آنها را در این پست وبلاگ توضیح نمی دهم. فقط آنها را ذکر کنید. اگر می خواهید در مورد نحوه کار آنها یک تازه کننده باشد ، به پست وبلاگ که آنها را توصیف می کند بروید.
خوب ، بیایید با کشیدن یک قلب روی بوم شروع کنیم.
ترسیم یک قلب
ما می خواهیم منطق را برای ترسیم قلب به یک عملکرد استخراج کنیم. بیایید آن را صدا کنیم drawCandyHeart
، و از جبران سمت چپ بالا ، اندازه قلب و رنگی که قلب را با آن ترسیم می کنیم عبور کنید:
private fun DrawScope.drawCandyHeart(
topLeft: Offset,
heartSize: Size,
color: Color,
) {
}
من رشته های مسیر را در یک لیست جداگانه تعریف کرده ام ، اما آنها را از این پست وبلاگ برای کوتاه بودن خارج می کنم. آنها در کد نهایی گنجانده شده اند که می توانید در انتهای پست وبلاگ پیدا کنید.
ما می توانیم رشته های مسیر را با یک عملکرد بدست آوریم pathString(color: Color): Pair
، که دو رشته مسیر را با رنگ با مقادیر مختلف کدورت باز می گرداند تا ظاهری را که مورد نظر ما قرار می دهد ، بدست آوریم.
بنابراین ، با استفاده از عملکرد برای به دست آوردن رشته های مسیر با رنگ ، ابتدا رشته ها را در آن تجزیه کنیم Path
S:
HeartCandy.Heart
.pathStrings(color)
.map { (pathString, color) ->
val path =
PathParser()
.parsePathString(pathString)
.toNodes()
.map { path -> path.scaleTo(heartSize.height) }
.toPath()
.apply {
translate(topLeft)
}
...
}
در اینجا ، ما از هر مسیر رشته و جفت رنگ عبور می کنیم و با استفاده از آن PathParser
، ما رشته های مسیر را در آن تجزیه می کنیم Path
s. سپس ، ما تبدیل می کنیم Path
به Node
S ، بنابراین ما می توانیم آنها را به اندازه صحیح مقیاس کنیم. پس از اتمام این کار ، ما آنها را به Path
S ، و سپس آنها را به سمت اصلاح موقعیت سوق دهید.
پس از آن ، ما می توانیم از مسیر تجزیه شده برای ترسیم آب نبات استفاده کنیم:
drawPath(
path = path,
color = color,
)
drawPath(
path = path,
color = color,
style = Stroke(width = 2f),
)
ما دو بار مسیر را ترسیم می کنیم تا پر شود و سپس سکته مغزی را به شکل شکل ها بکشیم. بعد از این مراحل ، قلب ما به این شکل به نظر می رسد:
مرحله بعدی اضافه کردن مقداری متن به آب نبات است.
اضافه کردن متن
برای ترسیم متن روی بوم ، ما به متن نیاز داریم TextLayoutResult
، بنابراین بیایید یک پارامتر جدید به drawHeartCandy
فراخوانی text
، که از نوع است TextLayoutResult
:
private fun DrawScope.drawCandyHeart(
...
text: TextLayoutResult,
) { ... }
به این ترتیب ، ما می توانیم بدون تصویب از همه تغییرات مرتبط با متن در مؤلفه والدین TextMeasurer
به مؤلفه نقاشی ، به عنوان drawText
روش به متن نیاز دارد TextLayoutResult
بشر
ما در متنی که می خواهیم نمایش دهیم عبور می کنیم ، که ما نیز با آن اندازه گیری می کنیم textMeasurer
:
val textMeasurer = rememberTextMeasurer()
...
drawCandyHeart(
...
text = textMeasurer.measure(
text = "Enby\nLove".uppercase(),
style = TextStyle.Default.copy(
fontFamily = RighteousFontFamily,
textAlign = TextAlign.Center,
fontSize = 10.sp,
),
)
)
ما همچنین با چرخاندن همه حروف بزرگ و تعریف سبک آن ، متن را در اینجا سبک می کنیم. اگر در مورد خانواده قلم (صالح) تعجب می کنید ، آن را به کد وارد می کند که در یک مرحله توضیح داده نشده است – متن با آهنگسازی و بوم.
مرحله بعدی ترسیم متن است. کد ما به این شکل است:
rotate(
degrees = -7f,
pivot = Offset(
x = topLeft.x + heartSize.width * 0.5f,
y = topLeft.y + heartSize.height * 0.5f,
),
) {
drawText(
textLayoutResult = text,
topLeft = calculateTextTopLeft(
topLeft = topLeft,
text = text,
heartWidth = heartSize.width,
padding = 5.dp.toPx()
),
color = color,
)
}
آنچه در اینجا شایان ذکر است این است که ما باید متن را کمی بچرخانیم تا به درستی با قلب تراز شود. همچنین ، مختصات بالا سمت چپ را برای متن با عملکرد زیر محاسبه می کنیم:
private fun calculateTextTopLeft(
topLeft: Offset,
text: TextLayoutResult,
heartWidth: Float,
padding: Float
) = Offset(
y = topLeft.y + (text.size.height * (1f / text.lineCount) + padding),
x = topLeft.x + heartWidth * 0.5f - text.size.width * 0.3f,
)
بعد از این تغییرات ، قلب به این شکل به نظر می رسد:
ترسیم همه آنها
خوب ، ما یک قلب داریم. اما هدف نهایی این است که 16 مورد از آنها را داشته باشیم. من جفت های رنگ و متن را در خارج از مؤلفه به عنوان نوع تعریف کردم List
بشر
برای ایجاد طرح 4 4 4 برای قلب ها ، ما می خواهیم ابتدا لیست موارد را به چهار زیرمجموعه تقسیم کنیم و سپس از طریق هر مورد در ردیف نقشه برداری کنیم:
HeartCandy.hearts
.chunked(4)
.mapIndexed { row, colors ->
colors.mapIndexed { index, (color, text) ->
...
}
}
سپس با گسترش و تعمیم کدی که برای یک قلب نوشتیم ، در داخل colors.mapIndexed
ما تماس می گیریم:
drawCandyHeart(
topLeft = Offset(
x = index * (heartSize.width + horizontalPadding),
y = row * (heartSize.height + verticalPadding),
),
heartSize = HeartCandy.Heart.size,
color = color,
text = textMeasurer.measure(
text = text.uppercase(),
style = HeartCandy.Heart.textStyle,
)
)
ما از شاخص موجود در ستون و ردیف استفاده می کنیم تا جبران کافی به هر قلب اضافه کنیم تا آنها را به درستی قرار دهد. ما همچنین بالشتک افقی و عمودی را اضافه می کنیم که بر اساس فضای باقیمانده روی صفحه محاسبه می شود.
ما به جای متن سخت کد شده که برای یک قلب داشتیم ، از رنگ عبور می کنیم و از متن استفاده می کنیم. سرانجام ، سبک متن برای صرفه جویی در فضا ، در مقایسه با کد برای ترسیم یک قلب ، به یک شیء برای صرفه جویی در فضا استخراج شده است.
بعد از این تغییرات ، اثر هنری ما به این شکل است:
اضافه کردن تعامل
سرانجام ، ما در جایی هستیم که تعامل را اضافه می کنیم! ابتدا باید یک متغیر را ذخیره کنیم و یک اصلاح کننده را در مؤلفه والدین اضافه کنیم تا حرکات کشیدن را فعال کنیم:
@Composable
fun HeartCandy() {
var dragPosition by remember { mutableStateOf(Offset.Zero) }
...
Canvas(
modifier = Modifier
...
.pointerInput(Unit) {
detectDragGestures(
onDragEnd = {
dragPosition = Offset.Zero
},
) { change, _ ->
dragPosition = dragPosition.copy(
x = change.position.x,
y = change.position.y
)
}
},
) { ... }
}
ما یک متغیر را برای ذخیره موقعیت کشیدن تعریف می کنیم ، اضافه می کنیم pointerInput
-مدیرر ، و سپس تماس بگیرید detectDragGestures
داخل اصلاح کننده سپس ، هنگامی که یک ژست کشیدن تشخیص داده می شود ، ما موقعیت تغییر یافته را روی آن قرار می دهیم dragPosition
-متغیر وقتی ژست کشیدن به پایان رسید ، ما موقعیت را روی مقدار پیش فرض تنظیم می کنیم ، بنابراین ، در این حالت ، Offset.Zero
بشر
اکنون می دانیم که ورودی نشانگر کاربر در حال حرکت است. در مرحله بعد ، ما می خواهیم از آن اطلاعات استفاده کنیم و وقتی که ورودی نشانگر آن را لمس می کند ، قلب را تغییر دهیم. ابتدا بیایید موقعیت کشیدن را به drawCandyHeart
-method:
private fun DrawScope.drawCandyHeart(
dragPosition: Offset,
) { ... }
در مرحله بعد ، ما می خواهیم بدانیم که آیا نقطه کشیدن در مسیرهای ترسیم شده برای قلب ها قرار دارد یا خیر. Path
یک روش عالی را ارائه می دهد: getBounds
، که مرزهای مسیر را به عنوان Rect
بشر سپس ، ما می توانیم بررسی کنیم که آیا این Rect
حاوی موقعیت کشیدن فعلی است.
فقط یک چیز وجود دارد: ما دو مسیر مختلف برای قلب داریم ، بنابراین می خواهیم بررسی کنیم که آیا نقطه کشیدن در هر یک از آنها برای انجام تحولات است. ما باید با تقسیم مسیر تجزیه و تحلیل مسیر از مسیر نقشه ، کمی از ترسیم را تغییر دهیم:
val paths = HeartCandy.Heart
.pathStrings(color)
.map { (pathString, pathColor) ->
val path = // Parse the path
Pair(path, pathColor)
}
val pathBounds = paths.map { it.first.getBounds() }
val isSelected = pathBounds.any { it.contains(dragPosition) }
paths
.map { (path, color) ->
// Draw the paths
}
ما ابتدا مسیرهایی را که قبلاً انجام دادیم ترسیم می کنیم و از نقشه ، داده ها را به عنوان باز می گردانیم Pair
بشر سپس ، ما از طریق مسیرها نقشه برداری می کنیم تا مرزهای هر مسیر را بدست آوریم و نتیجه را ذخیره کنیم pathBounds
بشر پس از آن ، ما از طریق آن نقشه می کشیم و بررسی می کنیم که آیا هر یک از آنها حاوی موقعیت کشیدن فعلی هستند.
اکنون که می دانیم قلب در حال حاضر انتخاب شده است ، می توانیم برخی از تحولات را انجام دهیم. بیایید دو کار انجام دهیم: قلب را کمی بزرگتر کنید و رنگ متن را تغییر دهید.
ابتدا متغیرها را تعریف کنیم:
val scaleAmount = if (isSelected) 1.05f else 1f
val textColor = if (isSelected) HeartCandy.highlightColor else color
سپس ، ما می خواهیم هر آنچه را که می کشیم با عملکرد مقیاس تبدیل می کنیم و در مقدار مقیاس عبور می کنیم:
scale(scaleAmount) {
paths.map {...}
}
و در آخر ، رنگ متن را به متغیر تازه تعریف شده تغییر دهید:
drawText(
...
color = textColor,
)
و همین است. با این تغییر کد ، ما تعامل به بوم خود اضافه کرده ایم.
پیچیدن
در این پست وبلاگ ، ما به بررسی چگونگی اضافه کردن تعامل در بوم های آهنگسازی پرداختیم. ما برخی از قلب های مضمون روز ولنتاین را با پیچ و تاب عجیب و غریب ترسیم کردیم و توانایی انتخاب هر یک از آنها را با ورودی اشاره گر اضافه کردیم. کد کامل در این قطعه کد موجود است.
در حالی که این پروژه سرگرم کننده بود ، متخصص دسترسی درونی من در مورد چند مورد به من یادآوری می کند: اول از همه ، متن موجود در آب نبات ها تضاد کافی بین متن و پس زمینه برای خوانا ندارند. نکته دیگر این است که در حال حاضر ، تمام تعامل ها فقط برای ورودی اشاره گر در دسترس هستند و هر کسی را با استفاده از فناوری های کمکی که از ورودی اشاره گر پشتیبانی نمی کند ، خارج می کند.
ممکن است بعداً یک پست وبلاگ در مورد بهبود این جنبه ها بنویسم.
آیا با آهنگسازی و بوم چیزی هیجان انگیز و تعاملی ساخته اید؟ یا برنامه های آینده دارید؟
پیوندها در پست وبلاگ