با Compose می توانید اشکالی ایجاد کنید که از چند ضلعی ساخته شده اند. به عنوان مثال، می توانید انواع شکل های زیر را بسازید:

برای ایجاد یک چند ضلعی گرد سفارشی در Compose، وابستگی graphics-shapes
به app/build.gradle
خود اضافه کنید:
implementation "androidx.graphics:graphics-shapes:1.0.0-rc01"
این کتابخانه به شما امکان می دهد اشکالی را ایجاد کنید که از چند ضلعی ساخته شده اند. در حالی که اشکال چند ضلعی فقط لبههای مستقیم و گوشههای تیز دارند، این شکلها امکان گوشههای گرد اختیاری را دارند. تغییر شکل بین دو شکل مختلف را ساده می کند. شکلگیری بین اشکال دلخواه دشوار است و معمولاً یک مشکل زمان طراحی است. اما این کتابخانه با تغییر شکل بین این اشکال با ساختارهای چند ضلعی مشابه، کار را ساده می کند.
چند ضلعی ایجاد کنید
قطعه زیر یک شکل چند ضلعی اساسی با 6 نقطه در مرکز منطقه ترسیم ایجاد می کند:
Box( modifier = Modifier .drawWithCache { val roundedPolygon = RoundedPolygon( numVertices = 6, radius = size.minDimension / 2, centerX = size.width / 2, centerY = size.height / 2 ) val roundedPolygonPath = roundedPolygon.toPath().asComposePath() onDrawBehind { drawPath(roundedPolygonPath, color = Color.Blue) } } .fillMaxSize() )

در این مثال، کتابخانه یک RoundedPolygon
ایجاد می کند که هندسه شکل درخواستی را نشان می دهد. برای ترسیم آن شکل در یک برنامه Compose، باید یک شی Path
از آن دریافت کنید تا شکل را به شکلی تبدیل کنید که Compose چگونه ترسیم کند.
گوشه های یک چند ضلعی را گرد کنید
برای گرد کردن گوشه های یک چند ضلعی، از پارامتر CornerRounding
استفاده کنید. این به دو پارامتر، radius
و smoothing
نیاز دارد. هر گوشه گرد از 1 تا 3 منحنی مکعبی تشکیل شده است که مرکز آن یک شکل قوس دایره ای دارد در حالی که منحنی های دو طرفه ("طرف") از لبه شکل به منحنی وسط منتقل می شوند.
شعاع
radius
دایره ای است که برای گرد کردن یک راس استفاده می شود.
به عنوان مثال، مثلث گوشه گرد زیر به صورت زیر ساخته شده است:


r
اندازه گرد کردن گوشه های گرد را تعیین می کند.صاف کردن
هموار شدن عاملی است که تعیین می کند چه مدت طول می کشد تا از قسمت گرد گوشه به لبه برسد. یک ضریب هموارسازی 0 (صاف نشده، مقدار پیشفرض برای CornerRounding
) منجر به گرد کردن گوشه کاملاً دایرهای میشود. یک ضریب هموارسازی غیر صفر (تا حداکثر 1.0) باعث می شود که گوشه توسط سه منحنی مجزا گرد شود.


به عنوان مثال، قطعه زیر تفاوت ظریف در تنظیم هموارسازی 0 در مقابل 1 را نشان می دهد:
Box( modifier = Modifier .drawWithCache { val roundedPolygon = RoundedPolygon( numVertices = 3, radius = size.minDimension / 2, centerX = size.width / 2, centerY = size.height / 2, rounding = CornerRounding( size.minDimension / 10f, smoothing = 0.1f ) ) val roundedPolygonPath = roundedPolygon.toPath().asComposePath() onDrawBehind { drawPath(roundedPolygonPath, color = Color.Black) } } .size(100.dp) )

اندازه و موقعیت
به طور پیش فرض، شکلی با شعاع 1
در اطراف مرکز ایجاد می شود ( 0, 0
). این شعاع نشان دهنده فاصله بین مرکز و رئوس بیرونی چند ضلعی است که شکل بر آن استوار است. توجه داشته باشید که گرد کردن گوشه ها به شکل کوچکتری منجر می شود زیرا گوشه های گرد نسبت به رئوس گرد به مرکز نزدیکتر هستند. برای اندازه چند ضلعی، مقدار radius
را تنظیم کنید. برای تنظیم موقعیت، centerX
یا centerY
چند ضلعی را تغییر دهید. روش دیگر، تبدیل شی به تغییر اندازه، موقعیت و چرخش آن با استفاده از توابع تبدیل DrawScope
استاندارد مانند DrawScope#translate()
.
شکل های مورف
یک شیء Morph
یک شکل جدید است که نشان دهنده یک انیمیشن بین دو شکل چند ضلعی است. برای تغییر شکل بین دو شکل، دو RoundedPolygons
و یک شی Morph
ایجاد کنید که این دو شکل را بگیرد. برای محاسبه یک شکل بین شکل های شروع و پایان، یک مقدار progress
بین صفر و یک ارائه دهید تا شکل آن بین شکل های شروع (0) و پایان (1) مشخص شود:
Box( modifier = Modifier .drawWithCache { val triangle = RoundedPolygon( numVertices = 3, radius = size.minDimension / 2f, centerX = size.width / 2f, centerY = size.height / 2f, rounding = CornerRounding( size.minDimension / 10f, smoothing = 0.1f ) ) val square = RoundedPolygon( numVertices = 4, radius = size.minDimension / 2f, centerX = size.width / 2f, centerY = size.height / 2f ) val morph = Morph(start = triangle, end = square) val morphPath = morph .toPath(progress = 0.5f).asComposePath() onDrawBehind { drawPath(morphPath, color = Color.Black) } } .fillMaxSize() )
در مثال بالا، پیشرفت دقیقاً در نیمه راه بین دو شکل (مثلث گرد و مربع) است که نتیجه زیر را ایجاد می کند:

در اکثر سناریوها، شکلگیری بهعنوان بخشی از یک انیمیشن انجام میشود، و نه فقط یک رندر ثابت. برای متحرک سازی بین این دو، می توانید از API های استاندارد Animation در Compose برای تغییر مقدار پیشرفت در طول زمان استفاده کنید. به عنوان مثال، شما می توانید بی نهایت شکل بین این دو شکل را به صورت زیر متحرک کنید:
val infiniteAnimation = rememberInfiniteTransition(label = "infinite animation") val morphProgress = infiniteAnimation.animateFloat( initialValue = 0f, targetValue = 1f, animationSpec = infiniteRepeatable( tween(500), repeatMode = RepeatMode.Reverse ), label = "morph" ) Box( modifier = Modifier .drawWithCache { val triangle = RoundedPolygon( numVertices = 3, radius = size.minDimension / 2f, centerX = size.width / 2f, centerY = size.height / 2f, rounding = CornerRounding( size.minDimension / 10f, smoothing = 0.1f ) ) val square = RoundedPolygon( numVertices = 4, radius = size.minDimension / 2f, centerX = size.width / 2f, centerY = size.height / 2f ) val morph = Morph(start = triangle, end = square) val morphPath = morph .toPath(progress = morphProgress.value) .asComposePath() onDrawBehind { drawPath(morphPath, color = Color.Black) } } .fillMaxSize() )

از چند ضلعی به عنوان کلیپ استفاده کنید
استفاده از اصلاح کننده clip
در Compose برای تغییر نحوه رندر شدن یک composable و استفاده از سایه هایی که در اطراف ناحیه برش کشیده می شوند، معمول است:
fun RoundedPolygon.getBounds() = calculateBounds().let { Rect(it[0], it[1], it[2], it[3]) } class RoundedPolygonShape( private val polygon: RoundedPolygon, private var matrix: Matrix = Matrix() ) : Shape { private var path = Path() override fun createOutline( size: Size, layoutDirection: LayoutDirection, density: Density ): Outline { path.rewind() path = polygon.toPath().asComposePath() matrix.reset() val bounds = polygon.getBounds() val maxDimension = max(bounds.width, bounds.height) matrix.scale(size.width / maxDimension, size.height / maxDimension) matrix.translate(-bounds.left, -bounds.top) path.transform(matrix) return Outline.Generic(path) } }
سپس می توانید از چند ضلعی به عنوان یک کلیپ استفاده کنید، همانطور که در قطعه زیر نشان داده شده است:
val hexagon = remember { RoundedPolygon( 6, rounding = CornerRounding(0.2f) ) } val clip = remember(hexagon) { RoundedPolygonShape(polygon = hexagon) } Box( modifier = Modifier .clip(clip) .background(MaterialTheme.colorScheme.secondary) .size(200.dp) ) { Text( "Hello Compose", color = MaterialTheme.colorScheme.onSecondary, modifier = Modifier.align(Alignment.Center) ) }
این منجر به موارد زیر می شود:

این ممکن است تفاوت چندانی با آنچه قبلاً رندر بود نداشته باشد، اما امکان استفاده از سایر ویژگیها در Compose را فراهم میکند. به عنوان مثال، این تکنیک را می توان برای برش دادن یک تصویر و اعمال سایه در اطراف منطقه بریده شده استفاده کرد:
val hexagon = remember { RoundedPolygon( 6, rounding = CornerRounding(0.2f) ) } val clip = remember(hexagon) { RoundedPolygonShape(polygon = hexagon) } Box( modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center ) { Image( painter = painterResource(id = R.drawable.dog), contentDescription = "Dog", contentScale = ContentScale.Crop, modifier = Modifier .graphicsLayer { this.shadowElevation = 6.dp.toPx() this.shape = clip this.clip = true this.ambientShadowColor = Color.Black this.spotShadowColor = Color.Black } .size(200.dp) ) }

دکمه مورف با کلیک
می توانید از کتابخانه graphics-shape
برای ایجاد دکمه ای استفاده کنید که با فشار دادن بین دو شکل تغییر شکل می دهد. ابتدا یک MorphPolygonShape
ایجاد کنید که Shape
گسترش دهد، مقیاسبندی و ترجمه کند تا متناسب باشد. به عبور از پیشرفت توجه کنید تا شکل را بتوان متحرک کرد:
class MorphPolygonShape( private val morph: Morph, private val percentage: Float ) : Shape { private val matrix = Matrix() override fun createOutline( size: Size, layoutDirection: LayoutDirection, density: Density ): Outline { // Below assumes that you haven't changed the default radius of 1f, nor the centerX and centerY of 0f // By default this stretches the path to the size of the container, if you don't want stretching, use the same size.width for both x and y. matrix.scale(size.width / 2f, size.height / 2f) matrix.translate(1f, 1f) val path = morph.toPath(progress = percentage).asComposePath() path.transform(matrix) return Outline.Generic(path) } }
برای استفاده از این شکل مورف، دو چند ضلعی shapeA
و shapeB
ایجاد کنید. Morph
ایجاد و به خاطر بسپارید. سپس، با استفاده از interactionSource
در فشار به عنوان نیروی محرکه انیمیشن، شکل را به عنوان یک کلیپ کلیپ روی دکمه اعمال کنید:
val shapeA = remember { RoundedPolygon( 6, rounding = CornerRounding(0.2f) ) } val shapeB = remember { RoundedPolygon.star( 6, rounding = CornerRounding(0.1f) ) } val morph = remember { Morph(shapeA, shapeB) } val interactionSource = remember { MutableInteractionSource() } val isPressed by interactionSource.collectIsPressedAsState() val animatedProgress = animateFloatAsState( targetValue = if (isPressed) 1f else 0f, label = "progress", animationSpec = spring(dampingRatio = 0.4f, stiffness = Spring.StiffnessMedium) ) Box( modifier = Modifier .size(200.dp) .padding(8.dp) .clip(MorphPolygonShape(morph, animatedProgress.value)) .background(Color(0xFF80DEEA)) .size(200.dp) .clickable(interactionSource = interactionSource, indication = null) { } ) { Text("Hello", modifier = Modifier.align(Alignment.Center)) }
با ضربه زدن روی جعبه، انیمیشن زیر ایجاد می شود:

شکل را بی نهایت متحرک کنید
برای متحرک سازی بی پایان یک شکل مورف، از rememberInfiniteTransition
استفاده کنید. در زیر نمونه ای از عکس پروفایل است که در طول زمان بی نهایت تغییر شکل می دهد (و می چرخد). این رویکرد از یک تنظیم کوچک برای MorphPolygonShape
که در بالا نشان داده شده است استفاده می کند:
class CustomRotatingMorphShape( private val morph: Morph, private val percentage: Float, private val rotation: Float ) : Shape { private val matrix = Matrix() override fun createOutline( size: Size, layoutDirection: LayoutDirection, density: Density ): Outline { // Below assumes that you haven't changed the default radius of 1f, nor the centerX and centerY of 0f // By default this stretches the path to the size of the container, if you don't want stretching, use the same size.width for both x and y. matrix.scale(size.width / 2f, size.height / 2f) matrix.translate(1f, 1f) matrix.rotateZ(rotation) val path = morph.toPath(progress = percentage).asComposePath() path.transform(matrix) return Outline.Generic(path) } } @Preview @Composable private fun RotatingScallopedProfilePic() { val shapeA = remember { RoundedPolygon( 12, rounding = CornerRounding(0.2f) ) } val shapeB = remember { RoundedPolygon.star( 12, rounding = CornerRounding(0.2f) ) } val morph = remember { Morph(shapeA, shapeB) } val infiniteTransition = rememberInfiniteTransition("infinite outline movement") val animatedProgress = infiniteTransition.animateFloat( initialValue = 0f, targetValue = 1f, animationSpec = infiniteRepeatable( tween(2000, easing = LinearEasing), repeatMode = RepeatMode.Reverse ), label = "animatedMorphProgress" ) val animatedRotation = infiniteTransition.animateFloat( initialValue = 0f, targetValue = 360f, animationSpec = infiniteRepeatable( tween(6000, easing = LinearEasing), repeatMode = RepeatMode.Reverse ), label = "animatedMorphProgress" ) Box( modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center ) { Image( painter = painterResource(id = R.drawable.dog), contentDescription = "Dog", contentScale = ContentScale.Crop, modifier = Modifier .clip( CustomRotatingMorphShape( morph, animatedProgress.value, animatedRotation.value ) ) .size(200.dp) ) } }
این کد نتیجه جالب زیر را می دهد:

چند ضلعی های سفارشی
اگر اشکال ایجاد شده از چند ضلعی های معمولی مورد استفاده شما را پوشش نمی دهند، می توانید یک شکل سفارشی تر با لیستی از رئوس ایجاد کنید. برای مثال، ممکن است بخواهید یک شکل قلب مانند زیر ایجاد کنید:

میتوانید رئوس منفرد این شکل را با استفاده از اضافه بار RoundedPolygon
که یک آرایه شناور از مختصات x، y میگیرد، مشخص کنید.
برای شکستن چند ضلعی قلب، توجه کنید که سیستم مختصات قطبی برای مشخص کردن نقاط، این کار را آسانتر از استفاده از سیستم مختصات دکارتی (x,y) میکند، جایی که 0°
از سمت راست شروع میشود و در جهت عقربههای ساعت، با 270°
در موقعیت ساعت 12:

اکنون می توان با تعیین زاویه (𝜭) و شعاع از مرکز در هر نقطه، شکل را به روشی ساده تر تعریف کرد:

اکنون می توان رئوس را ایجاد کرد و به تابع RoundedPolygon
ارسال کرد:
val vertices = remember { val radius = 1f val radiusSides = 0.8f val innerRadius = .1f floatArrayOf( radialToCartesian(radiusSides, 0f.toRadians()).x, radialToCartesian(radiusSides, 0f.toRadians()).y, radialToCartesian(radius, 90f.toRadians()).x, radialToCartesian(radius, 90f.toRadians()).y, radialToCartesian(radiusSides, 180f.toRadians()).x, radialToCartesian(radiusSides, 180f.toRadians()).y, radialToCartesian(radius, 250f.toRadians()).x, radialToCartesian(radius, 250f.toRadians()).y, radialToCartesian(innerRadius, 270f.toRadians()).x, radialToCartesian(innerRadius, 270f.toRadians()).y, radialToCartesian(radius, 290f.toRadians()).x, radialToCartesian(radius, 290f.toRadians()).y, ) }
رئوس باید با استفاده از این تابع radialToCartesian
به مختصات دکارتی ترجمه شوند:
internal fun Float.toRadians() = this * PI.toFloat() / 180f internal val PointZero = PointF(0f, 0f) internal fun radialToCartesian( radius: Float, angleRadians: Float, center: PointF = PointZero ) = directionVectorPointF(angleRadians) * radius + center internal fun directionVectorPointF(angleRadians: Float) = PointF(cos(angleRadians), sin(angleRadians))
کد قبلی رئوس خام قلب را به شما می دهد، اما برای به دست آوردن شکل قلب انتخابی باید گوشه های خاصی را گرد کنید. گوشه های 90°
و 270°
هیچ گردی ندارند، اما گوشه های دیگر گرد می شوند. برای دستیابی به گرد کردن سفارشی برای هر گوشه، از پارامتر perVertexRounding
استفاده کنید:
val rounding = remember { val roundingNormal = 0.6f val roundingNone = 0f listOf( CornerRounding(roundingNormal), CornerRounding(roundingNone), CornerRounding(roundingNormal), CornerRounding(roundingNormal), CornerRounding(roundingNone), CornerRounding(roundingNormal), ) } val polygon = remember(vertices, rounding) { RoundedPolygon( vertices = vertices, perVertexRounding = rounding ) } Box( modifier = Modifier .drawWithCache { val roundedPolygonPath = polygon.toPath().asComposePath() onDrawBehind { scale(size.width * 0.5f, size.width * 0.5f) { translate(size.width * 0.5f, size.height * 0.5f) { drawPath(roundedPolygonPath, color = Color(0xFFF15087)) } } } } .size(400.dp) )
این باعث ایجاد قلب صورتی می شود:

اگر شکلهای قبلی مورد استفاده شما را پوشش نمیدهند، از کلاس Path
برای ترسیم یک شکل سفارشی یا بارگیری یک فایل ImageVector
از دیسک استفاده کنید. کتابخانه graphics-shapes
برای استفاده برای اشکال دلخواه در نظر گرفته نشده است، بلکه به طور خاص برای ساده سازی ایجاد چند ضلعی های گرد و انیمیشن های مورف بین آنها طراحی شده است.
منابع اضافی
برای اطلاعات بیشتر و نمونه ها به منابع زیر مراجعه کنید:
،با Compose می توانید اشکالی ایجاد کنید که از چند ضلعی ساخته شده اند. به عنوان مثال، می توانید انواع شکل های زیر را بسازید:

برای ایجاد یک چند ضلعی گرد سفارشی در Compose، وابستگی graphics-shapes
به app/build.gradle
خود اضافه کنید:
implementation "androidx.graphics:graphics-shapes:1.0.0-rc01"
این کتابخانه به شما امکان می دهد اشکالی را ایجاد کنید که از چند ضلعی ساخته شده اند. در حالی که اشکال چند ضلعی فقط لبههای مستقیم و گوشههای تیز دارند، این شکلها امکان گوشههای گرد اختیاری را دارند. تغییر شکل بین دو شکل مختلف را ساده می کند. شکلگیری بین اشکال دلخواه دشوار است و معمولاً یک مشکل زمان طراحی است. اما این کتابخانه با تغییر شکل بین این اشکال با ساختارهای چند ضلعی مشابه، کار را ساده می کند.
چند ضلعی ایجاد کنید
قطعه زیر یک شکل چند ضلعی اساسی با 6 نقطه در مرکز منطقه ترسیم ایجاد می کند:
Box( modifier = Modifier .drawWithCache { val roundedPolygon = RoundedPolygon( numVertices = 6, radius = size.minDimension / 2, centerX = size.width / 2, centerY = size.height / 2 ) val roundedPolygonPath = roundedPolygon.toPath().asComposePath() onDrawBehind { drawPath(roundedPolygonPath, color = Color.Blue) } } .fillMaxSize() )

در این مثال، کتابخانه یک RoundedPolygon
ایجاد می کند که هندسه شکل درخواستی را نشان می دهد. برای ترسیم آن شکل در یک برنامه Compose، باید یک شی Path
از آن دریافت کنید تا شکل را به شکلی تبدیل کنید که Compose چگونه ترسیم کند.
گوشه های یک چند ضلعی را گرد کنید
برای گرد کردن گوشه های یک چند ضلعی، از پارامتر CornerRounding
استفاده کنید. این به دو پارامتر، radius
و smoothing
نیاز دارد. هر گوشه گرد از 1 تا 3 منحنی مکعبی تشکیل شده است که مرکز آن یک شکل قوس دایره ای دارد در حالی که منحنی های دو طرفه ("طرف") از لبه شکل به منحنی وسط منتقل می شوند.
شعاع
radius
دایره ای است که برای گرد کردن یک راس استفاده می شود.
به عنوان مثال، مثلث گوشه گرد زیر به صورت زیر ساخته شده است:


r
اندازه گرد کردن گوشه های گرد را تعیین می کند.صاف کردن
هموار شدن عاملی است که تعیین می کند چه مدت طول می کشد تا از قسمت گرد گوشه به لبه برسد. یک ضریب هموارسازی 0 (صاف نشده، مقدار پیشفرض برای CornerRounding
) منجر به گرد کردن گوشه کاملاً دایرهای میشود. یک ضریب هموارسازی غیر صفر (تا حداکثر 1.0) باعث می شود که گوشه توسط سه منحنی مجزا گرد شود.


به عنوان مثال، قطعه زیر تفاوت ظریف در تنظیم هموارسازی 0 در مقابل 1 را نشان می دهد:
Box( modifier = Modifier .drawWithCache { val roundedPolygon = RoundedPolygon( numVertices = 3, radius = size.minDimension / 2, centerX = size.width / 2, centerY = size.height / 2, rounding = CornerRounding( size.minDimension / 10f, smoothing = 0.1f ) ) val roundedPolygonPath = roundedPolygon.toPath().asComposePath() onDrawBehind { drawPath(roundedPolygonPath, color = Color.Black) } } .size(100.dp) )

اندازه و موقعیت
به طور پیش فرض، شکلی با شعاع 1
در اطراف مرکز ایجاد می شود ( 0, 0
). این شعاع نشان دهنده فاصله بین مرکز و رئوس بیرونی چند ضلعی است که شکل بر آن استوار است. توجه داشته باشید که گرد کردن گوشه ها به شکل کوچکتری منجر می شود زیرا گوشه های گرد نسبت به رئوس گرد به مرکز نزدیکتر هستند. برای اندازه چند ضلعی، مقدار radius
را تنظیم کنید. برای تنظیم موقعیت، centerX
یا centerY
چند ضلعی را تغییر دهید. روش دیگر، تبدیل شی به تغییر اندازه، موقعیت و چرخش آن با استفاده از توابع تبدیل DrawScope
استاندارد مانند DrawScope#translate()
.
شکل های مورف
یک شیء Morph
یک شکل جدید است که نشان دهنده یک انیمیشن بین دو شکل چند ضلعی است. برای تغییر شکل بین دو شکل، دو RoundedPolygons
و یک شی Morph
ایجاد کنید که این دو شکل را بگیرد. برای محاسبه یک شکل بین شکل های شروع و پایان، یک مقدار progress
بین صفر و یک ارائه دهید تا شکل آن بین شکل های شروع (0) و پایان (1) مشخص شود:
Box( modifier = Modifier .drawWithCache { val triangle = RoundedPolygon( numVertices = 3, radius = size.minDimension / 2f, centerX = size.width / 2f, centerY = size.height / 2f, rounding = CornerRounding( size.minDimension / 10f, smoothing = 0.1f ) ) val square = RoundedPolygon( numVertices = 4, radius = size.minDimension / 2f, centerX = size.width / 2f, centerY = size.height / 2f ) val morph = Morph(start = triangle, end = square) val morphPath = morph .toPath(progress = 0.5f).asComposePath() onDrawBehind { drawPath(morphPath, color = Color.Black) } } .fillMaxSize() )
در مثال بالا، پیشرفت دقیقاً در نیمه راه بین دو شکل (مثلث گرد و مربع) است که نتیجه زیر را ایجاد می کند:

در اکثر سناریوها، شکلگیری بهعنوان بخشی از یک انیمیشن انجام میشود، و نه فقط یک رندر ثابت. برای متحرک سازی بین این دو، می توانید از API های استاندارد Animation در Compose برای تغییر مقدار پیشرفت در طول زمان استفاده کنید. به عنوان مثال، شما می توانید بی نهایت شکل بین این دو شکل را به صورت زیر متحرک کنید:
val infiniteAnimation = rememberInfiniteTransition(label = "infinite animation") val morphProgress = infiniteAnimation.animateFloat( initialValue = 0f, targetValue = 1f, animationSpec = infiniteRepeatable( tween(500), repeatMode = RepeatMode.Reverse ), label = "morph" ) Box( modifier = Modifier .drawWithCache { val triangle = RoundedPolygon( numVertices = 3, radius = size.minDimension / 2f, centerX = size.width / 2f, centerY = size.height / 2f, rounding = CornerRounding( size.minDimension / 10f, smoothing = 0.1f ) ) val square = RoundedPolygon( numVertices = 4, radius = size.minDimension / 2f, centerX = size.width / 2f, centerY = size.height / 2f ) val morph = Morph(start = triangle, end = square) val morphPath = morph .toPath(progress = morphProgress.value) .asComposePath() onDrawBehind { drawPath(morphPath, color = Color.Black) } } .fillMaxSize() )

از چند ضلعی به عنوان کلیپ استفاده کنید
استفاده از اصلاح کننده clip
در Compose برای تغییر نحوه رندر شدن یک composable و استفاده از سایه هایی که در اطراف ناحیه برش کشیده می شوند، معمول است:
fun RoundedPolygon.getBounds() = calculateBounds().let { Rect(it[0], it[1], it[2], it[3]) } class RoundedPolygonShape( private val polygon: RoundedPolygon, private var matrix: Matrix = Matrix() ) : Shape { private var path = Path() override fun createOutline( size: Size, layoutDirection: LayoutDirection, density: Density ): Outline { path.rewind() path = polygon.toPath().asComposePath() matrix.reset() val bounds = polygon.getBounds() val maxDimension = max(bounds.width, bounds.height) matrix.scale(size.width / maxDimension, size.height / maxDimension) matrix.translate(-bounds.left, -bounds.top) path.transform(matrix) return Outline.Generic(path) } }
سپس می توانید از چند ضلعی به عنوان یک کلیپ استفاده کنید، همانطور که در قطعه زیر نشان داده شده است:
val hexagon = remember { RoundedPolygon( 6, rounding = CornerRounding(0.2f) ) } val clip = remember(hexagon) { RoundedPolygonShape(polygon = hexagon) } Box( modifier = Modifier .clip(clip) .background(MaterialTheme.colorScheme.secondary) .size(200.dp) ) { Text( "Hello Compose", color = MaterialTheme.colorScheme.onSecondary, modifier = Modifier.align(Alignment.Center) ) }
این منجر به موارد زیر می شود:

این ممکن است تفاوت چندانی با آنچه قبلاً رندر بود نداشته باشد، اما امکان استفاده از سایر ویژگیها در Compose را فراهم میکند. به عنوان مثال، این تکنیک را می توان برای برش دادن یک تصویر و اعمال سایه در اطراف منطقه بریده شده استفاده کرد:
val hexagon = remember { RoundedPolygon( 6, rounding = CornerRounding(0.2f) ) } val clip = remember(hexagon) { RoundedPolygonShape(polygon = hexagon) } Box( modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center ) { Image( painter = painterResource(id = R.drawable.dog), contentDescription = "Dog", contentScale = ContentScale.Crop, modifier = Modifier .graphicsLayer { this.shadowElevation = 6.dp.toPx() this.shape = clip this.clip = true this.ambientShadowColor = Color.Black this.spotShadowColor = Color.Black } .size(200.dp) ) }

دکمه مورف با کلیک
می توانید از کتابخانه graphics-shape
برای ایجاد دکمه ای استفاده کنید که با فشار دادن بین دو شکل تغییر شکل می دهد. ابتدا یک MorphPolygonShape
ایجاد کنید که Shape
گسترش دهد، مقیاسبندی و ترجمه کند تا متناسب باشد. به عبور از پیشرفت توجه کنید تا شکل را بتوان متحرک کرد:
class MorphPolygonShape( private val morph: Morph, private val percentage: Float ) : Shape { private val matrix = Matrix() override fun createOutline( size: Size, layoutDirection: LayoutDirection, density: Density ): Outline { // Below assumes that you haven't changed the default radius of 1f, nor the centerX and centerY of 0f // By default this stretches the path to the size of the container, if you don't want stretching, use the same size.width for both x and y. matrix.scale(size.width / 2f, size.height / 2f) matrix.translate(1f, 1f) val path = morph.toPath(progress = percentage).asComposePath() path.transform(matrix) return Outline.Generic(path) } }
برای استفاده از این شکل مورف، دو چند ضلعی shapeA
و shapeB
ایجاد کنید. Morph
ایجاد و به خاطر بسپارید. سپس، با استفاده از interactionSource
در فشار به عنوان نیروی محرکه انیمیشن، شکل را به عنوان یک کلیپ کلیپ روی دکمه اعمال کنید:
val shapeA = remember { RoundedPolygon( 6, rounding = CornerRounding(0.2f) ) } val shapeB = remember { RoundedPolygon.star( 6, rounding = CornerRounding(0.1f) ) } val morph = remember { Morph(shapeA, shapeB) } val interactionSource = remember { MutableInteractionSource() } val isPressed by interactionSource.collectIsPressedAsState() val animatedProgress = animateFloatAsState( targetValue = if (isPressed) 1f else 0f, label = "progress", animationSpec = spring(dampingRatio = 0.4f, stiffness = Spring.StiffnessMedium) ) Box( modifier = Modifier .size(200.dp) .padding(8.dp) .clip(MorphPolygonShape(morph, animatedProgress.value)) .background(Color(0xFF80DEEA)) .size(200.dp) .clickable(interactionSource = interactionSource, indication = null) { } ) { Text("Hello", modifier = Modifier.align(Alignment.Center)) }
با ضربه زدن روی جعبه، انیمیشن زیر ایجاد می شود:

شکل را بی نهایت متحرک کنید
برای متحرک سازی بی پایان یک شکل مورف، از rememberInfiniteTransition
استفاده کنید. در زیر نمونه ای از عکس پروفایل است که در طول زمان بی نهایت تغییر شکل می دهد (و می چرخد). این رویکرد از یک تنظیم کوچک برای MorphPolygonShape
که در بالا نشان داده شده است استفاده می کند:
class CustomRotatingMorphShape( private val morph: Morph, private val percentage: Float, private val rotation: Float ) : Shape { private val matrix = Matrix() override fun createOutline( size: Size, layoutDirection: LayoutDirection, density: Density ): Outline { // Below assumes that you haven't changed the default radius of 1f, nor the centerX and centerY of 0f // By default this stretches the path to the size of the container, if you don't want stretching, use the same size.width for both x and y. matrix.scale(size.width / 2f, size.height / 2f) matrix.translate(1f, 1f) matrix.rotateZ(rotation) val path = morph.toPath(progress = percentage).asComposePath() path.transform(matrix) return Outline.Generic(path) } } @Preview @Composable private fun RotatingScallopedProfilePic() { val shapeA = remember { RoundedPolygon( 12, rounding = CornerRounding(0.2f) ) } val shapeB = remember { RoundedPolygon.star( 12, rounding = CornerRounding(0.2f) ) } val morph = remember { Morph(shapeA, shapeB) } val infiniteTransition = rememberInfiniteTransition("infinite outline movement") val animatedProgress = infiniteTransition.animateFloat( initialValue = 0f, targetValue = 1f, animationSpec = infiniteRepeatable( tween(2000, easing = LinearEasing), repeatMode = RepeatMode.Reverse ), label = "animatedMorphProgress" ) val animatedRotation = infiniteTransition.animateFloat( initialValue = 0f, targetValue = 360f, animationSpec = infiniteRepeatable( tween(6000, easing = LinearEasing), repeatMode = RepeatMode.Reverse ), label = "animatedMorphProgress" ) Box( modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center ) { Image( painter = painterResource(id = R.drawable.dog), contentDescription = "Dog", contentScale = ContentScale.Crop, modifier = Modifier .clip( CustomRotatingMorphShape( morph, animatedProgress.value, animatedRotation.value ) ) .size(200.dp) ) } }
این کد نتیجه جالب زیر را می دهد:

چند ضلعی های سفارشی
اگر اشکال ایجاد شده از چند ضلعی های معمولی مورد استفاده شما را پوشش نمی دهند، می توانید یک شکل سفارشی تر با لیستی از رئوس ایجاد کنید. برای مثال، ممکن است بخواهید یک شکل قلب مانند زیر ایجاد کنید:

میتوانید رئوس منفرد این شکل را با استفاده از اضافه بار RoundedPolygon
که یک آرایه شناور از مختصات x، y میگیرد، مشخص کنید.
برای شکستن چند ضلعی قلب، توجه کنید که سیستم مختصات قطبی برای مشخص کردن نقاط، این کار را آسانتر از استفاده از سیستم مختصات دکارتی (x,y) میکند، جایی که 0°
از سمت راست شروع میشود و در جهت عقربههای ساعت، با 270°
در موقعیت ساعت 12:

اکنون می توان با تعیین زاویه (𝜭) و شعاع از مرکز در هر نقطه، شکل را به روشی ساده تر تعریف کرد:

اکنون می توان رئوس را ایجاد کرد و به تابع RoundedPolygon
ارسال کرد:
val vertices = remember { val radius = 1f val radiusSides = 0.8f val innerRadius = .1f floatArrayOf( radialToCartesian(radiusSides, 0f.toRadians()).x, radialToCartesian(radiusSides, 0f.toRadians()).y, radialToCartesian(radius, 90f.toRadians()).x, radialToCartesian(radius, 90f.toRadians()).y, radialToCartesian(radiusSides, 180f.toRadians()).x, radialToCartesian(radiusSides, 180f.toRadians()).y, radialToCartesian(radius, 250f.toRadians()).x, radialToCartesian(radius, 250f.toRadians()).y, radialToCartesian(innerRadius, 270f.toRadians()).x, radialToCartesian(innerRadius, 270f.toRadians()).y, radialToCartesian(radius, 290f.toRadians()).x, radialToCartesian(radius, 290f.toRadians()).y, ) }
رئوس باید با استفاده از این تابع radialToCartesian
به مختصات دکارتی ترجمه شوند:
internal fun Float.toRadians() = this * PI.toFloat() / 180f internal val PointZero = PointF(0f, 0f) internal fun radialToCartesian( radius: Float, angleRadians: Float, center: PointF = PointZero ) = directionVectorPointF(angleRadians) * radius + center internal fun directionVectorPointF(angleRadians: Float) = PointF(cos(angleRadians), sin(angleRadians))
کد قبلی رئوس خام قلب را به شما می دهد، اما برای به دست آوردن شکل قلب انتخابی باید گوشه های خاصی را گرد کنید. گوشه های 90°
و 270°
هیچ گردی ندارند، اما گوشه های دیگر گرد می شوند. برای دستیابی به گرد کردن سفارشی برای هر گوشه، از پارامتر perVertexRounding
استفاده کنید:
val rounding = remember { val roundingNormal = 0.6f val roundingNone = 0f listOf( CornerRounding(roundingNormal), CornerRounding(roundingNone), CornerRounding(roundingNormal), CornerRounding(roundingNormal), CornerRounding(roundingNone), CornerRounding(roundingNormal), ) } val polygon = remember(vertices, rounding) { RoundedPolygon( vertices = vertices, perVertexRounding = rounding ) } Box( modifier = Modifier .drawWithCache { val roundedPolygonPath = polygon.toPath().asComposePath() onDrawBehind { scale(size.width * 0.5f, size.width * 0.5f) { translate(size.width * 0.5f, size.height * 0.5f) { drawPath(roundedPolygonPath, color = Color(0xFFF15087)) } } } } .size(400.dp) )
این باعث ایجاد قلب صورتی می شود:

اگر شکلهای قبلی مورد استفاده شما را پوشش نمیدهند، از کلاس Path
برای ترسیم یک شکل سفارشی یا بارگیری یک فایل ImageVector
از دیسک استفاده کنید. کتابخانه graphics-shapes
برای استفاده برای اشکال دلخواه در نظر گرفته نشده است، بلکه به طور خاص برای ساده سازی ایجاد چند ضلعی های گرد و انیمیشن های مورف بین آنها طراحی شده است.
منابع اضافی
برای اطلاعات بیشتر و نمونه ها به منابع زیر مراجعه کنید:
،با Compose می توانید اشکالی ایجاد کنید که از چند ضلعی ساخته شده اند. به عنوان مثال، می توانید انواع شکل های زیر را بسازید:

برای ایجاد یک چند ضلعی گرد سفارشی در Compose، وابستگی graphics-shapes
به app/build.gradle
خود اضافه کنید:
implementation "androidx.graphics:graphics-shapes:1.0.0-rc01"
این کتابخانه به شما امکان می دهد اشکالی را ایجاد کنید که از چند ضلعی ساخته شده اند. در حالی که اشکال چند ضلعی فقط لبههای مستقیم و گوشههای تیز دارند، این شکلها امکان گوشههای گرد اختیاری را دارند. تغییر شکل بین دو شکل مختلف را ساده می کند. شکلگیری بین اشکال دلخواه دشوار است و معمولاً یک مشکل زمان طراحی است. اما این کتابخانه با تغییر شکل بین این اشکال با ساختارهای چند ضلعی مشابه، کار را ساده می کند.
چند ضلعی ایجاد کنید
قطعه زیر یک شکل چند ضلعی اساسی با 6 نقطه در مرکز منطقه ترسیم ایجاد می کند:
Box( modifier = Modifier .drawWithCache { val roundedPolygon = RoundedPolygon( numVertices = 6, radius = size.minDimension / 2, centerX = size.width / 2, centerY = size.height / 2 ) val roundedPolygonPath = roundedPolygon.toPath().asComposePath() onDrawBehind { drawPath(roundedPolygonPath, color = Color.Blue) } } .fillMaxSize() )

در این مثال، کتابخانه یک RoundedPolygon
ایجاد می کند که هندسه شکل درخواستی را نشان می دهد. برای ترسیم آن شکل در یک برنامه Compose، باید یک شی Path
از آن دریافت کنید تا شکل را به شکلی تبدیل کنید که Compose چگونه ترسیم کند.
گوشه های یک چند ضلعی را گرد کنید
برای گرد کردن گوشه های یک چند ضلعی، از پارامتر CornerRounding
استفاده کنید. این به دو پارامتر، radius
و smoothing
نیاز دارد. هر گوشه گرد از 1 تا 3 منحنی مکعبی تشکیل شده است که مرکز آن یک شکل قوس دایره ای دارد در حالی که منحنی های دو طرفه ("طرف") از لبه شکل به منحنی وسط منتقل می شوند.
شعاع
radius
دایره ای است که برای گرد کردن یک راس استفاده می شود.
به عنوان مثال، مثلث گوشه گرد زیر به صورت زیر ساخته شده است:


r
اندازه گرد کردن گوشه های گرد را تعیین می کند.صاف کردن
هموار شدن عاملی است که تعیین می کند چه مدت طول می کشد تا از قسمت گرد گوشه به لبه برسد. یک ضریب هموارسازی 0 (صاف نشده، مقدار پیشفرض برای CornerRounding
) منجر به گرد کردن گوشه کاملاً دایرهای میشود. یک ضریب هموارسازی غیر صفر (تا حداکثر 1.0) باعث می شود که گوشه توسط سه منحنی مجزا گرد شود.


به عنوان مثال، قطعه زیر تفاوت ظریف در تنظیم هموارسازی 0 در مقابل 1 را نشان می دهد:
Box( modifier = Modifier .drawWithCache { val roundedPolygon = RoundedPolygon( numVertices = 3, radius = size.minDimension / 2, centerX = size.width / 2, centerY = size.height / 2, rounding = CornerRounding( size.minDimension / 10f, smoothing = 0.1f ) ) val roundedPolygonPath = roundedPolygon.toPath().asComposePath() onDrawBehind { drawPath(roundedPolygonPath, color = Color.Black) } } .size(100.dp) )

اندازه و موقعیت
به طور پیش فرض، شکلی با شعاع 1
در اطراف مرکز ایجاد می شود ( 0, 0
). این شعاع نشان دهنده فاصله بین مرکز و رئوس بیرونی چند ضلعی است که شکل بر آن استوار است. توجه داشته باشید که گرد کردن گوشه ها به شکل کوچکتری منجر می شود زیرا گوشه های گرد نسبت به رئوس گرد به مرکز نزدیکتر هستند. برای اندازه چند ضلعی، مقدار radius
را تنظیم کنید. برای تنظیم موقعیت، centerX
یا centerY
چند ضلعی را تغییر دهید. روش دیگر، تبدیل شی به تغییر اندازه، موقعیت و چرخش آن با استفاده از توابع تبدیل DrawScope
استاندارد مانند DrawScope#translate()
.
شکل های مورف
یک شیء Morph
یک شکل جدید است که نشان دهنده یک انیمیشن بین دو شکل چند ضلعی است. برای تغییر شکل بین دو شکل، دو RoundedPolygons
و یک شی Morph
ایجاد کنید که این دو شکل را بگیرد. برای محاسبه یک شکل بین شکل های شروع و پایان، یک مقدار progress
بین صفر و یک ارائه دهید تا شکل آن بین شکل های شروع (0) و پایان (1) مشخص شود:
Box( modifier = Modifier .drawWithCache { val triangle = RoundedPolygon( numVertices = 3, radius = size.minDimension / 2f, centerX = size.width / 2f, centerY = size.height / 2f, rounding = CornerRounding( size.minDimension / 10f, smoothing = 0.1f ) ) val square = RoundedPolygon( numVertices = 4, radius = size.minDimension / 2f, centerX = size.width / 2f, centerY = size.height / 2f ) val morph = Morph(start = triangle, end = square) val morphPath = morph .toPath(progress = 0.5f).asComposePath() onDrawBehind { drawPath(morphPath, color = Color.Black) } } .fillMaxSize() )
در مثال بالا، پیشرفت دقیقاً در نیمه راه بین دو شکل (مثلث گرد و مربع) است که نتیجه زیر را ایجاد می کند:

در اکثر سناریوها، شکلگیری بهعنوان بخشی از یک انیمیشن انجام میشود، و نه فقط یک رندر ثابت. برای متحرک سازی بین این دو، می توانید از API های استاندارد Animation در Compose برای تغییر مقدار پیشرفت در طول زمان استفاده کنید. به عنوان مثال، شما می توانید بی نهایت شکل بین این دو شکل را به صورت زیر متحرک کنید:
val infiniteAnimation = rememberInfiniteTransition(label = "infinite animation") val morphProgress = infiniteAnimation.animateFloat( initialValue = 0f, targetValue = 1f, animationSpec = infiniteRepeatable( tween(500), repeatMode = RepeatMode.Reverse ), label = "morph" ) Box( modifier = Modifier .drawWithCache { val triangle = RoundedPolygon( numVertices = 3, radius = size.minDimension / 2f, centerX = size.width / 2f, centerY = size.height / 2f, rounding = CornerRounding( size.minDimension / 10f, smoothing = 0.1f ) ) val square = RoundedPolygon( numVertices = 4, radius = size.minDimension / 2f, centerX = size.width / 2f, centerY = size.height / 2f ) val morph = Morph(start = triangle, end = square) val morphPath = morph .toPath(progress = morphProgress.value) .asComposePath() onDrawBehind { drawPath(morphPath, color = Color.Black) } } .fillMaxSize() )

از چند ضلعی به عنوان کلیپ استفاده کنید
استفاده از اصلاح کننده clip
در Compose برای تغییر نحوه رندر شدن یک composable و استفاده از سایه هایی که در اطراف ناحیه برش کشیده می شوند، معمول است:
fun RoundedPolygon.getBounds() = calculateBounds().let { Rect(it[0], it[1], it[2], it[3]) } class RoundedPolygonShape( private val polygon: RoundedPolygon, private var matrix: Matrix = Matrix() ) : Shape { private var path = Path() override fun createOutline( size: Size, layoutDirection: LayoutDirection, density: Density ): Outline { path.rewind() path = polygon.toPath().asComposePath() matrix.reset() val bounds = polygon.getBounds() val maxDimension = max(bounds.width, bounds.height) matrix.scale(size.width / maxDimension, size.height / maxDimension) matrix.translate(-bounds.left, -bounds.top) path.transform(matrix) return Outline.Generic(path) } }
سپس می توانید از چند ضلعی به عنوان یک کلیپ استفاده کنید، همانطور که در قطعه زیر نشان داده شده است:
val hexagon = remember { RoundedPolygon( 6, rounding = CornerRounding(0.2f) ) } val clip = remember(hexagon) { RoundedPolygonShape(polygon = hexagon) } Box( modifier = Modifier .clip(clip) .background(MaterialTheme.colorScheme.secondary) .size(200.dp) ) { Text( "Hello Compose", color = MaterialTheme.colorScheme.onSecondary, modifier = Modifier.align(Alignment.Center) ) }
این منجر به موارد زیر می شود:

این ممکن است با آنچه قبلاً رندر شده بود متفاوت به نظر نرسد، اما امکان استفاده از ویژگی های دیگر در Compose را فراهم می کند. به عنوان مثال، از این تکنیک می توان برای برش دادن یک تصویر و اعمال سایه در اطراف ناحیه بریده شده استفاده کرد:
val hexagon = remember { RoundedPolygon( 6, rounding = CornerRounding(0.2f) ) } val clip = remember(hexagon) { RoundedPolygonShape(polygon = hexagon) } Box( modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center ) { Image( painter = painterResource(id = R.drawable.dog), contentDescription = "Dog", contentScale = ContentScale.Crop, modifier = Modifier .graphicsLayer { this.shadowElevation = 6.dp.toPx() this.shape = clip this.clip = true this.ambientShadowColor = Color.Black this.spotShadowColor = Color.Black } .size(200.dp) ) }

دکمه مورف با کلیک
می توانید از کتابخانه graphics-shape
برای ایجاد دکمه ای استفاده کنید که با فشار دادن بین دو شکل تغییر شکل می دهد. ابتدا یک MorphPolygonShape
ایجاد کنید که Shape
گسترش دهد، مقیاسبندی و ترجمه کند تا متناسب باشد. به عبور از پیشرفت توجه کنید تا شکل را بتوان متحرک کرد:
class MorphPolygonShape( private val morph: Morph, private val percentage: Float ) : Shape { private val matrix = Matrix() override fun createOutline( size: Size, layoutDirection: LayoutDirection, density: Density ): Outline { // Below assumes that you haven't changed the default radius of 1f, nor the centerX and centerY of 0f // By default this stretches the path to the size of the container, if you don't want stretching, use the same size.width for both x and y. matrix.scale(size.width / 2f, size.height / 2f) matrix.translate(1f, 1f) val path = morph.toPath(progress = percentage).asComposePath() path.transform(matrix) return Outline.Generic(path) } }
برای استفاده از این شکل مورف، دو چند ضلعی shapeA
و shapeB
ایجاد کنید. Morph
ایجاد و به خاطر بسپارید. سپس، با استفاده از interactionSource
در فشار به عنوان نیروی محرکه انیمیشن، شکل را به عنوان یک کلیپ کلیپ روی دکمه اعمال کنید:
val shapeA = remember { RoundedPolygon( 6, rounding = CornerRounding(0.2f) ) } val shapeB = remember { RoundedPolygon.star( 6, rounding = CornerRounding(0.1f) ) } val morph = remember { Morph(shapeA, shapeB) } val interactionSource = remember { MutableInteractionSource() } val isPressed by interactionSource.collectIsPressedAsState() val animatedProgress = animateFloatAsState( targetValue = if (isPressed) 1f else 0f, label = "progress", animationSpec = spring(dampingRatio = 0.4f, stiffness = Spring.StiffnessMedium) ) Box( modifier = Modifier .size(200.dp) .padding(8.dp) .clip(MorphPolygonShape(morph, animatedProgress.value)) .background(Color(0xFF80DEEA)) .size(200.dp) .clickable(interactionSource = interactionSource, indication = null) { } ) { Text("Hello", modifier = Modifier.align(Alignment.Center)) }
با ضربه زدن روی جعبه، انیمیشن زیر ایجاد می شود:

شکل را بی نهایت متحرک کنید
برای متحرک سازی بی پایان یک شکل مورف، از rememberInfiniteTransition
استفاده کنید. در زیر نمونه ای از عکس پروفایل است که در طول زمان بی نهایت تغییر شکل می دهد (و می چرخد). این رویکرد از یک تنظیم کوچک برای MorphPolygonShape
که در بالا نشان داده شده است استفاده می کند:
class CustomRotatingMorphShape( private val morph: Morph, private val percentage: Float, private val rotation: Float ) : Shape { private val matrix = Matrix() override fun createOutline( size: Size, layoutDirection: LayoutDirection, density: Density ): Outline { // Below assumes that you haven't changed the default radius of 1f, nor the centerX and centerY of 0f // By default this stretches the path to the size of the container, if you don't want stretching, use the same size.width for both x and y. matrix.scale(size.width / 2f, size.height / 2f) matrix.translate(1f, 1f) matrix.rotateZ(rotation) val path = morph.toPath(progress = percentage).asComposePath() path.transform(matrix) return Outline.Generic(path) } } @Preview @Composable private fun RotatingScallopedProfilePic() { val shapeA = remember { RoundedPolygon( 12, rounding = CornerRounding(0.2f) ) } val shapeB = remember { RoundedPolygon.star( 12, rounding = CornerRounding(0.2f) ) } val morph = remember { Morph(shapeA, shapeB) } val infiniteTransition = rememberInfiniteTransition("infinite outline movement") val animatedProgress = infiniteTransition.animateFloat( initialValue = 0f, targetValue = 1f, animationSpec = infiniteRepeatable( tween(2000, easing = LinearEasing), repeatMode = RepeatMode.Reverse ), label = "animatedMorphProgress" ) val animatedRotation = infiniteTransition.animateFloat( initialValue = 0f, targetValue = 360f, animationSpec = infiniteRepeatable( tween(6000, easing = LinearEasing), repeatMode = RepeatMode.Reverse ), label = "animatedMorphProgress" ) Box( modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center ) { Image( painter = painterResource(id = R.drawable.dog), contentDescription = "Dog", contentScale = ContentScale.Crop, modifier = Modifier .clip( CustomRotatingMorphShape( morph, animatedProgress.value, animatedRotation.value ) ) .size(200.dp) ) } }
این کد نتیجه جالب زیر را می دهد:

چند ضلعی های سفارشی
اگر اشکال ایجاد شده از چند ضلعی های معمولی مورد استفاده شما را پوشش نمی دهند، می توانید یک شکل سفارشی تر با لیستی از رئوس ایجاد کنید. برای مثال، ممکن است بخواهید یک شکل قلب مانند زیر ایجاد کنید:

میتوانید رئوس منفرد این شکل را با استفاده از اضافه بار RoundedPolygon
که یک آرایه شناور از مختصات x، y میگیرد، مشخص کنید.
برای شکستن چند ضلعی قلب، توجه کنید که سیستم مختصات قطبی برای مشخص کردن نقاط، این کار را آسانتر از استفاده از سیستم مختصات دکارتی (x,y) میکند، جایی که 0°
از سمت راست شروع میشود و در جهت عقربههای ساعت، با 270°
در موقعیت ساعت 12:

اکنون می توان با تعیین زاویه (𝜭) و شعاع از مرکز در هر نقطه، شکل را به روشی ساده تر تعریف کرد:

اکنون می توان رئوس را ایجاد کرد و به تابع RoundedPolygon
ارسال کرد:
val vertices = remember { val radius = 1f val radiusSides = 0.8f val innerRadius = .1f floatArrayOf( radialToCartesian(radiusSides, 0f.toRadians()).x, radialToCartesian(radiusSides, 0f.toRadians()).y, radialToCartesian(radius, 90f.toRadians()).x, radialToCartesian(radius, 90f.toRadians()).y, radialToCartesian(radiusSides, 180f.toRadians()).x, radialToCartesian(radiusSides, 180f.toRadians()).y, radialToCartesian(radius, 250f.toRadians()).x, radialToCartesian(radius, 250f.toRadians()).y, radialToCartesian(innerRadius, 270f.toRadians()).x, radialToCartesian(innerRadius, 270f.toRadians()).y, radialToCartesian(radius, 290f.toRadians()).x, radialToCartesian(radius, 290f.toRadians()).y, ) }
رئوس باید با استفاده از این تابع radialToCartesian
به مختصات دکارتی ترجمه شوند:
internal fun Float.toRadians() = this * PI.toFloat() / 180f internal val PointZero = PointF(0f, 0f) internal fun radialToCartesian( radius: Float, angleRadians: Float, center: PointF = PointZero ) = directionVectorPointF(angleRadians) * radius + center internal fun directionVectorPointF(angleRadians: Float) = PointF(cos(angleRadians), sin(angleRadians))
کد قبلی رئوس خام قلب را به شما می دهد، اما برای به دست آوردن شکل قلب انتخابی باید گوشه های خاصی را گرد کنید. گوشه های 90°
و 270°
هیچ گردی ندارند، اما گوشه های دیگر گرد می شوند. برای دستیابی به گرد کردن سفارشی برای هر گوشه، از پارامتر perVertexRounding
استفاده کنید:
val rounding = remember { val roundingNormal = 0.6f val roundingNone = 0f listOf( CornerRounding(roundingNormal), CornerRounding(roundingNone), CornerRounding(roundingNormal), CornerRounding(roundingNormal), CornerRounding(roundingNone), CornerRounding(roundingNormal), ) } val polygon = remember(vertices, rounding) { RoundedPolygon( vertices = vertices, perVertexRounding = rounding ) } Box( modifier = Modifier .drawWithCache { val roundedPolygonPath = polygon.toPath().asComposePath() onDrawBehind { scale(size.width * 0.5f, size.width * 0.5f) { translate(size.width * 0.5f, size.height * 0.5f) { drawPath(roundedPolygonPath, color = Color(0xFFF15087)) } } } } .size(400.dp) )
این باعث ایجاد قلب صورتی می شود:

اگر شکلهای قبلی مورد استفاده شما را پوشش نمیدهند، از کلاس Path
برای ترسیم یک شکل سفارشی یا بارگیری یک فایل ImageVector
از دیسک استفاده کنید. کتابخانه graphics-shapes
برای استفاده برای اشکال دلخواه در نظر گرفته نشده است، بلکه به طور خاص برای ساده سازی ایجاد چند ضلعی های گرد و انیمیشن های مورف بین آنها طراحی شده است.
منابع اضافی
برای اطلاعات بیشتر و نمونه ها به منابع زیر مراجعه کنید:
،با Compose می توانید اشکالی ایجاد کنید که از چند ضلعی ساخته شده اند. به عنوان مثال، می توانید انواع شکل های زیر را بسازید:

برای ایجاد یک چند ضلعی گرد سفارشی در Compose، وابستگی graphics-shapes
به app/build.gradle
خود اضافه کنید:
implementation "androidx.graphics:graphics-shapes:1.0.0-rc01"
این کتابخانه به شما امکان می دهد اشکالی را ایجاد کنید که از چند ضلعی ساخته شده اند. در حالی که اشکال چند ضلعی فقط لبههای مستقیم و گوشههای تیز دارند، این شکلها امکان گوشههای گرد اختیاری را دارند. تغییر شکل بین دو شکل مختلف را ساده می کند. شکلگیری بین اشکال دلخواه دشوار است و معمولاً یک مشکل زمان طراحی است. اما این کتابخانه با تغییر شکل بین این اشکال با ساختارهای چند ضلعی مشابه، کار را ساده می کند.
چند ضلعی ایجاد کنید
قطعه زیر یک شکل چند ضلعی اساسی با 6 نقطه در مرکز منطقه ترسیم ایجاد می کند:
Box( modifier = Modifier .drawWithCache { val roundedPolygon = RoundedPolygon( numVertices = 6, radius = size.minDimension / 2, centerX = size.width / 2, centerY = size.height / 2 ) val roundedPolygonPath = roundedPolygon.toPath().asComposePath() onDrawBehind { drawPath(roundedPolygonPath, color = Color.Blue) } } .fillMaxSize() )

در این مثال، کتابخانه یک RoundedPolygon
ایجاد می کند که هندسه شکل درخواستی را نشان می دهد. برای ترسیم آن شکل در یک برنامه Compose، باید یک شی Path
از آن دریافت کنید تا شکل را به شکلی تبدیل کنید که Compose چگونه ترسیم کند.
گوشه های یک چند ضلعی را گرد کنید
برای گرد کردن گوشه های یک چند ضلعی، از پارامتر CornerRounding
استفاده کنید. این به دو پارامتر، radius
و smoothing
نیاز دارد. هر گوشه گرد از 1 تا 3 منحنی مکعبی تشکیل شده است که مرکز آن یک شکل قوس دایره ای دارد در حالی که منحنی های دو طرفه ("طرف") از لبه شکل به منحنی وسط منتقل می شوند.
شعاع
radius
دایره ای است که برای گرد کردن یک راس استفاده می شود.
به عنوان مثال، مثلث گوشه گرد زیر به صورت زیر ساخته شده است:


r
اندازه گرد کردن گوشه های گرد را تعیین می کند.صاف کردن
هموار شدن عاملی است که تعیین می کند چه مدت طول می کشد تا از قسمت گرد دایره ای گوشه به لبه برسد. یک ضریب هموارسازی 0 (صاف نشده، مقدار پیشفرض برای CornerRounding
) منجر به گرد کردن گوشه کاملاً دایرهای میشود. یک ضریب هموارسازی غیر صفر (تا حداکثر 1.0) باعث می شود که گوشه توسط سه منحنی مجزا گرد شود.


به عنوان مثال ، قطعه زیر تفاوت ظریف در تنظیم صاف کردن 0 در مقابل 1 را نشان می دهد:
Box( modifier = Modifier .drawWithCache { val roundedPolygon = RoundedPolygon( numVertices = 3, radius = size.minDimension / 2, centerX = size.width / 2, centerY = size.height / 2, rounding = CornerRounding( size.minDimension / 10f, smoothing = 0.1f ) ) val roundedPolygonPath = roundedPolygon.toPath().asComposePath() onDrawBehind { drawPath(roundedPolygonPath, color = Color.Black) } } .size(100.dp) )

اندازه و موقعیت
به طور پیش فرض ، یک شکل با شعاع 1
در اطراف مرکز ایجاد می شود ( 0, 0
). این شعاع نشانگر فاصله بین مرکز و راس های بیرونی چند ضلعی است که شکل بر اساس آن است. توجه داشته باشید که گرد کردن گوشه ها به شکل کمتری منجر می شوند زیرا گوشه های گرد به مرکز نزدیک تر از گرد و پنجه های گرد هستند. برای اندازه گیری چند ضلعی ، مقدار radius
را تنظیم کنید. برای تنظیم موقعیت ، centerX
یا centerY
چند ضلعی را تغییر دهید. از طرف دیگر ، شیء را تغییر دهید تا اندازه ، موقعیت و چرخش آن را با استفاده از توابع استاندارد تبدیل DrawScope
مانند DrawScope#translate()
تغییر دهید.
شکل های مورف
یک شیء Morph
شکل جدیدی است که یک انیمیشن بین دو شکل چند ضلعی را نشان می دهد. برای تقویت بین دو شکل ، دو RoundedPolygons
و یک شیء Morph
را ایجاد کنید که این دو شکل را به خود اختصاص می دهد. برای محاسبه یک شکل بین اشکال شروع و پایان ، یک مقدار progress
بین صفر و یک را فراهم کنید تا شکل آن بین شکل (0) و پایان (1) شکل تعیین شود:
Box( modifier = Modifier .drawWithCache { val triangle = RoundedPolygon( numVertices = 3, radius = size.minDimension / 2f, centerX = size.width / 2f, centerY = size.height / 2f, rounding = CornerRounding( size.minDimension / 10f, smoothing = 0.1f ) ) val square = RoundedPolygon( numVertices = 4, radius = size.minDimension / 2f, centerX = size.width / 2f, centerY = size.height / 2f ) val morph = Morph(start = triangle, end = square) val morphPath = morph .toPath(progress = 0.5f).asComposePath() onDrawBehind { drawPath(morphPath, color = Color.Black) } } .fillMaxSize() )
در مثال بالا ، پیشرفت دقیقاً در نیمه راه بین دو شکل (مثلث گرد و یک مربع) است و نتیجه زیر را تولید می کند:

در بیشتر سناریوها ، مورفین به عنوان بخشی از یک انیمیشن انجام می شود ، و نه فقط یک رندر استاتیک. برای جابجایی بین این دو ، می توانید از API های انیمیشن استاندارد در آهنگسازی استفاده کنید تا مقدار پیشرفت را با گذشت زمان تغییر دهید. به عنوان مثال ، شما می توانید بی نهایت مورف بین این دو شکل را به شرح زیر تحریک کنید:
val infiniteAnimation = rememberInfiniteTransition(label = "infinite animation") val morphProgress = infiniteAnimation.animateFloat( initialValue = 0f, targetValue = 1f, animationSpec = infiniteRepeatable( tween(500), repeatMode = RepeatMode.Reverse ), label = "morph" ) Box( modifier = Modifier .drawWithCache { val triangle = RoundedPolygon( numVertices = 3, radius = size.minDimension / 2f, centerX = size.width / 2f, centerY = size.height / 2f, rounding = CornerRounding( size.minDimension / 10f, smoothing = 0.1f ) ) val square = RoundedPolygon( numVertices = 4, radius = size.minDimension / 2f, centerX = size.width / 2f, centerY = size.height / 2f ) val morph = Morph(start = triangle, end = square) val morphPath = morph .toPath(progress = morphProgress.value) .asComposePath() onDrawBehind { drawPath(morphPath, color = Color.Black) } } .fillMaxSize() )

از چند ضلعی به عنوان کلیپ استفاده کنید
استفاده از اصلاح کننده clip
در آهنگسازی معمول است تا نحوه ارائه یک ترکیب را تغییر دهد و از سایه هایی که در اطراف منطقه قطع می شوند استفاده کنید:
fun RoundedPolygon.getBounds() = calculateBounds().let { Rect(it[0], it[1], it[2], it[3]) } class RoundedPolygonShape( private val polygon: RoundedPolygon, private var matrix: Matrix = Matrix() ) : Shape { private var path = Path() override fun createOutline( size: Size, layoutDirection: LayoutDirection, density: Density ): Outline { path.rewind() path = polygon.toPath().asComposePath() matrix.reset() val bounds = polygon.getBounds() val maxDimension = max(bounds.width, bounds.height) matrix.scale(size.width / maxDimension, size.height / maxDimension) matrix.translate(-bounds.left, -bounds.top) path.transform(matrix) return Outline.Generic(path) } }
سپس می توانید از چند ضلعی به عنوان کلیپ استفاده کنید ، همانطور که در قطعه زیر نشان داده شده است:
val hexagon = remember { RoundedPolygon( 6, rounding = CornerRounding(0.2f) ) } val clip = remember(hexagon) { RoundedPolygonShape(polygon = hexagon) } Box( modifier = Modifier .clip(clip) .background(MaterialTheme.colorScheme.secondary) .size(200.dp) ) { Text( "Hello Compose", color = MaterialTheme.colorScheme.onSecondary, modifier = Modifier.align(Alignment.Center) ) }
این منجر به موارد زیر می شود:

این ممکن است متفاوت از آنچه قبلاً ارائه می شد ، باشد ، اما امکان استفاده از سایر ویژگی ها را در آهنگسازی فراهم می کند. به عنوان مثال ، از این تکنیک می توان برای کلیپ کردن یک تصویر و استفاده از سایه ای در اطراف منطقه قطع شده استفاده کرد:
val hexagon = remember { RoundedPolygon( 6, rounding = CornerRounding(0.2f) ) } val clip = remember(hexagon) { RoundedPolygonShape(polygon = hexagon) } Box( modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center ) { Image( painter = painterResource(id = R.drawable.dog), contentDescription = "Dog", contentScale = ContentScale.Crop, modifier = Modifier .graphicsLayer { this.shadowElevation = 6.dp.toPx() this.shape = clip this.clip = true this.ambientShadowColor = Color.Black this.spotShadowColor = Color.Black } .size(200.dp) ) }

دکمه MORPH روی کلیک
می توانید از کتابخانه graphics-shape
استفاده کنید تا دکمه ای را ایجاد کنید که بین دو شکل در مطبوعات قرار بگیرد. ابتدا ، یک MorphPolygonShape
ایجاد کنید که Shape
، مقیاس بندی و ترجمه آن را برای مناسب بودن مناسب می کند. به پیشرفت در پیشرفت توجه داشته باشید تا شکل بتواند متحرک شود:
class MorphPolygonShape( private val morph: Morph, private val percentage: Float ) : Shape { private val matrix = Matrix() override fun createOutline( size: Size, layoutDirection: LayoutDirection, density: Density ): Outline { // Below assumes that you haven't changed the default radius of 1f, nor the centerX and centerY of 0f // By default this stretches the path to the size of the container, if you don't want stretching, use the same size.width for both x and y. matrix.scale(size.width / 2f, size.height / 2f) matrix.translate(1f, 1f) val path = morph.toPath(progress = percentage).asComposePath() path.transform(matrix) return Outline.Generic(path) } }
برای استفاده از این شکل مورف ، دو چند ضلعی ، shapeA
و shapeB
ایجاد کنید. Morph
ایجاد کرده و به یاد بیاورید. سپس با استفاده از interactionSource
در مطبوعات به عنوان نیروی محرک پشت انیمیشن ، مورف را روی دکمه به عنوان طرح کلیپ بمالید:
val shapeA = remember { RoundedPolygon( 6, rounding = CornerRounding(0.2f) ) } val shapeB = remember { RoundedPolygon.star( 6, rounding = CornerRounding(0.1f) ) } val morph = remember { Morph(shapeA, shapeB) } val interactionSource = remember { MutableInteractionSource() } val isPressed by interactionSource.collectIsPressedAsState() val animatedProgress = animateFloatAsState( targetValue = if (isPressed) 1f else 0f, label = "progress", animationSpec = spring(dampingRatio = 0.4f, stiffness = Spring.StiffnessMedium) ) Box( modifier = Modifier .size(200.dp) .padding(8.dp) .clip(MorphPolygonShape(morph, animatedProgress.value)) .background(Color(0xFF80DEEA)) .size(200.dp) .clickable(interactionSource = interactionSource, indication = null) { } ) { Text("Hello", modifier = Modifier.align(Alignment.Center)) }
این منجر به انیمیشن زیر می شود که جعبه ضربه خورده است:

شکل دادن به شکل بی نهایت
برای بی پایان شکل دادن به شکل مورف ، از rememberInfiniteTransition
استفاده کنید. در زیر نمونه ای از یک تصویر پروفایل وجود دارد که با گذشت زمان شکل (و می چرخد) را تغییر می دهد. این روش از یک تنظیم کوچک با MorphPolygonShape
نشان داده شده در بالا استفاده می کند:
class CustomRotatingMorphShape( private val morph: Morph, private val percentage: Float, private val rotation: Float ) : Shape { private val matrix = Matrix() override fun createOutline( size: Size, layoutDirection: LayoutDirection, density: Density ): Outline { // Below assumes that you haven't changed the default radius of 1f, nor the centerX and centerY of 0f // By default this stretches the path to the size of the container, if you don't want stretching, use the same size.width for both x and y. matrix.scale(size.width / 2f, size.height / 2f) matrix.translate(1f, 1f) matrix.rotateZ(rotation) val path = morph.toPath(progress = percentage).asComposePath() path.transform(matrix) return Outline.Generic(path) } } @Preview @Composable private fun RotatingScallopedProfilePic() { val shapeA = remember { RoundedPolygon( 12, rounding = CornerRounding(0.2f) ) } val shapeB = remember { RoundedPolygon.star( 12, rounding = CornerRounding(0.2f) ) } val morph = remember { Morph(shapeA, shapeB) } val infiniteTransition = rememberInfiniteTransition("infinite outline movement") val animatedProgress = infiniteTransition.animateFloat( initialValue = 0f, targetValue = 1f, animationSpec = infiniteRepeatable( tween(2000, easing = LinearEasing), repeatMode = RepeatMode.Reverse ), label = "animatedMorphProgress" ) val animatedRotation = infiniteTransition.animateFloat( initialValue = 0f, targetValue = 360f, animationSpec = infiniteRepeatable( tween(6000, easing = LinearEasing), repeatMode = RepeatMode.Reverse ), label = "animatedMorphProgress" ) Box( modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center ) { Image( painter = painterResource(id = R.drawable.dog), contentDescription = "Dog", contentScale = ContentScale.Crop, modifier = Modifier .clip( CustomRotatingMorphShape( morph, animatedProgress.value, animatedRotation.value ) ) .size(200.dp) ) } }
این کد نتیجه سرگرم کننده زیر را ارائه می دهد:

چند ضلعی های سفارشی
اگر اشکال ایجاد شده از چند ضلعی معمولی مورد استفاده شما را پوشش نمی دهد ، می توانید با لیستی از رئوس ها شکل سفارشی تری ایجاد کنید. به عنوان مثال ، شما ممکن است بخواهید شکل قلبی مانند این ایجاد کنید:

شما می توانید با استفاده از اضافه بار RoundedPolygon
که یک آرایه شناور از مختصات x ، y را می گیرد ، رئوس های این شکل را مشخص کنید.
برای تجزیه چند ضلعی قلب ، توجه داشته باشید که سیستم مختصات قطبی برای مشخص کردن نقاط ، این کار را آسانتر از استفاده از سیستم مختصات دکارتی (X ، Y) ، جایی که 0°
در سمت راست شروع می شود ، و در جهت عقربه های ساعت با 270°
در حرکت می کند. موقعیت ساعت 12:

اکنون با مشخص کردن زاویه (𝜭) و شعاع از مرکز در هر نقطه می توان شکل را به روشی ساده تر تعریف کرد:

اکنون رئوس ها می توانند ایجاد و به عملکرد RoundedPolygon
منتقل شوند:
val vertices = remember { val radius = 1f val radiusSides = 0.8f val innerRadius = .1f floatArrayOf( radialToCartesian(radiusSides, 0f.toRadians()).x, radialToCartesian(radiusSides, 0f.toRadians()).y, radialToCartesian(radius, 90f.toRadians()).x, radialToCartesian(radius, 90f.toRadians()).y, radialToCartesian(radiusSides, 180f.toRadians()).x, radialToCartesian(radiusSides, 180f.toRadians()).y, radialToCartesian(radius, 250f.toRadians()).x, radialToCartesian(radius, 250f.toRadians()).y, radialToCartesian(innerRadius, 270f.toRadians()).x, radialToCartesian(innerRadius, 270f.toRadians()).y, radialToCartesian(radius, 290f.toRadians()).x, radialToCartesian(radius, 290f.toRadians()).y, ) }
رئوس ها باید با استفاده از این عملکرد radialToCartesian
به مختصات دکارتی ترجمه شوند:
internal fun Float.toRadians() = this * PI.toFloat() / 180f internal val PointZero = PointF(0f, 0f) internal fun radialToCartesian( radius: Float, angleRadians: Float, center: PointF = PointZero ) = directionVectorPointF(angleRadians) * radius + center internal fun directionVectorPointF(angleRadians: Float) = PointF(cos(angleRadians), sin(angleRadians))
کد قبلی به شما درآمدهای خام قلب را به شما می دهد ، اما برای به دست آوردن شکل قلب انتخاب شده ، باید گوشه های خاصی را دور بزنید. گوشه ها در 90°
و 270°
گرد نیستند ، اما گوشه های دیگر این کار را انجام می دهند. برای دستیابی به گردهای سفارشی برای گوشه های جداگانه ، از پارامتر perVertexRounding
استفاده کنید:
val rounding = remember { val roundingNormal = 0.6f val roundingNone = 0f listOf( CornerRounding(roundingNormal), CornerRounding(roundingNone), CornerRounding(roundingNormal), CornerRounding(roundingNormal), CornerRounding(roundingNone), CornerRounding(roundingNormal), ) } val polygon = remember(vertices, rounding) { RoundedPolygon( vertices = vertices, perVertexRounding = rounding ) } Box( modifier = Modifier .drawWithCache { val roundedPolygonPath = polygon.toPath().asComposePath() onDrawBehind { scale(size.width * 0.5f, size.width * 0.5f) { translate(size.width * 0.5f, size.height * 0.5f) { drawPath(roundedPolygonPath, color = Color(0xFFF15087)) } } } } .size(400.dp) )
این منجر به قلب صورتی می شود:

اگر اشکال قبلی مورد استفاده شما را پوشش نمی دهد ، استفاده از کلاس Path
را برای ترسیم یک شکل سفارشی یا بارگیری یک فایل ImageVector
از دیسک در نظر بگیرید. کتابخانه graphics-shapes
برای استفاده برای اشکال دلخواه در نظر گرفته نشده است ، اما به طور خاص به منظور ساده سازی ایجاد چند ضلعی های گرد و انیمیشن های مورف بین آنها است.
منابع اضافی
برای اطلاعات بیشتر و مثال ، به منابع زیر مراجعه کنید: