Oluştur özelliğini kullanarak poligonlardan şekiller oluşturabilirsiniz. Örneğin, aşağıdaki şekil türlerini oluşturabilirsiniz:

Oluşturma'da özel yuvarlatılmış bir poligon oluşturmak için app/build.gradle
dosyanıza graphics-shapes
bağımlılığını ekleyin:
implementation "androidx.graphics:graphics-shapes:1.0.1"
Bu kitaplık, poligonlardan oluşan şekiller oluşturmanıza olanak tanır. Poligonal şekiller yalnızca düz kenarlara ve keskin köşelere sahip olsa da bu şekillerde isteğe bağlı olarak yuvarlatılmış köşeler kullanılabilir. İki farklı şekil arasında geçiş yapmayı kolaylaştırır. Rastgele şekiller arasında dönüşüm yapmak zordur ve tasarım aşamasında ortaya çıkan bir sorundur. Ancak bu kitaplık, benzer poligonal yapılara sahip bu şekiller arasında geçiş yaparak işlemi basitleştirir.
Poligon oluşturma
Aşağıdaki snippet, çizim alanının ortasında 6 nokta bulunan temel bir poligon şekli oluşturur:
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() )

Bu örnekte kitaplık, istenen şekli temsil eden geometriyi barındıran bir RoundedPolygon
oluşturur. Bu şekli bir Oluştur uygulamasında çizmek için, şekli Oluştur'un çizebileceği bir forma dönüştürmek üzere Path
nesnesi almanız gerekir.
Bir poligonun köşelerini yuvarlama
Bir poligonun köşelerini yuvarlamak için CornerRounding
parametresini kullanın. Bu işlev, radius
ve smoothing
olmak üzere iki parametre alır. Her yuvarlatılmış köşe, 1-3 kübik eğriden oluşur. Bu eğrilerin ortasında dairesel bir yay şekli bulunurken iki yan ("yan yana") eğri, şeklin kenarından merkez eğriye geçiş yapar.
Yarıçap
radius
, bir köşeyi yuvarlatmak için kullanılan dairenin yarıçapıdır.
Örneğin, aşağıdaki yuvarlatılmış köşeli üçgen aşağıdaki gibi oluşturulur:


r
, yuvarlatılmış köşelerin dairesel yuvarlanma boyutunu belirler.Yumuşatma
Yumuşatma, köşenin dairesel yuvarlama kısmından kenara geçişin ne kadar süreceğini belirleyen bir faktördür. 0 olan yumuşatma faktörü (pürüzsüz değil, CornerRounding
için varsayılan değer), köşelerin tamamen dairesel olarak yuvarlanmasına neden olur. Sıfır olmayan bir yumuşatma faktörü (maksimum 1,0 değerine kadar), köşenin üç ayrı eğri ile yuvarlanmasına neden olur.


Örneğin, aşağıdaki snippet'te, yumuşatma değerinin 0 ve 1 olarak ayarlanması arasındaki küçük fark gösterilmektedir:
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) )

Boyut ve konum
Varsayılan olarak, merkez (0, 0
) etrafında 1
yarıçaplı bir şekil oluşturulur. Bu yarıçap, şeklin temel aldığı poligonun merkezi ile dış köşeleri arasındaki mesafeyi temsil eder. Yuvarlanan köşeler, yuvarlanma işlemi uygulanmayan köşelerden merkeze daha yakın olacağından, köşelerin yuvarlanması sonucu şeklin daha küçük olacağını unutmayın. Bir poligonun boyutunu ayarlamak için radius
değerini değiştirin. Konumu ayarlamak için poligonun centerX
veya centerY
değerini değiştirin.
Alternatif olarak, DrawScope#translate()
gibi standart DrawScope
dönüşüm işlevlerini kullanarak nesnenin boyutunu, konumunu ve dönüşünü değiştirmek için nesneyi dönüştürebilirsiniz.
Şekilleri dönüştürme
Morph
nesnesi, iki poligonal şekil arasındaki animasyonu temsil eden yeni bir şekildir. İki şekil arasında dönüşüm yapmak için iki RoundedPolygons
ve bu iki şekli alan bir Morph
nesnesi oluşturun. Başlangıç ve bitiş şekilleri arasındaki bir şekli hesaplamak için sıfır ile bir arasında bir progress
değeri sağlayarak başlangıç (0) ve bitiş (1) şekilleri arasındaki şeklini belirleyin:
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() )
Yukarıdaki örnekte, ilerleme tam olarak iki şeklin (yuvarlatılmış üçgen ve kare) ortasındadır ve aşağıdaki sonucu verir:

Çoğu durumda, şekil değiştirme işlemi yalnızca statik bir oluşturma işlemi olarak değil, animasyon kapsamında gerçekleştirilir. Bu iki değer arasında animasyon oluşturmak için ilerleme değerini zaman içinde değiştirmek üzere standart Compose'daki animasyon API'lerini kullanabilirsiniz. Örneğin, bu iki şekil arasında dönüşümü aşağıdaki gibi sonsuz olarak animasyonlu hale getirebilirsiniz:
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() )

Poligonu klip olarak kullanma
Bir kompozisyonun oluşturulma şeklini değiştirmek ve kırpma alanının etrafında çizilen gölgelerden yararlanmak için Oluştur'da clip
değiştiriciyi kullanmak yaygındır:
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) } }
Ardından, poligonu aşağıdaki snippet'te gösterildiği gibi klip olarak kullanabilirsiniz:
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) ) }
Bu durum aşağıdakilerle sonuçlanır:

Bu, daha önce oluşturulanlardan çok farklı görünmeyebilir ancak Oluştur'daki diğer özelliklerden yararlanmanıza olanak tanır. Örneğin, bu teknik bir resmi kırpmak ve kırpılan bölgenin etrafına gölge uygulamak için kullanılabilir:
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) ) }

Tıklandığında dönüşüm düğmesi
Basıldığında iki şekil arasında dönüşüm yapan bir düğme oluşturmak için graphics-shape
kitaplığını kullanabilirsiniz. Öncelikle, Shape
'u genişleten bir MorphPolygonShape
oluşturun, uygun şekilde sığdıracak şekilde ölçeklendirin ve çevirin. Şeklin animasyonlu olabilmesi için ilerlemenin iletildiğini unutmayın:
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) } }
Bu şekil dönüşümünü kullanmak için shapeA
ve shapeB
olmak üzere iki poligon oluşturun. Morph
'yi oluşturun ve hatırlayın. Ardından, animasyon için itici güç olarak interactionSource
düğmesini kullanarak, düğmeye klip ana hattı olarak dönüşümü uygulayın:
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)) }
Bu durumda, kutuya dokunulduğunda aşağıdaki animasyon gösterilir:

Şekil dönüşümünü sonsuz olarak animasyonlu hale getirme
Bir dönüşüm şeklini sonsuz olarak canlandırmak için rememberInfiniteTransition
simgesini kullanın.
Aşağıda, zaman içinde sonsuz şekilde şekil değiştiren (ve dönen) bir profil resmi örneği verilmiştir. Bu yaklaşımda, yukarıda gösterilen MorphPolygonShape
için küçük bir ayarlama yapılır:
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) ) } }
Bu kod aşağıdaki eğlenceli sonucu verir:

Özel poligonlar
Normal poligonlardan oluşturulan şekiller kullanım alanınızı kapsamıyorsa köşe listesi içeren daha özel bir şekil oluşturabilirsiniz. Örneğin, aşağıdaki gibi bir kalp şekli oluşturmak isteyebilirsiniz:

x, y koordinatlarından oluşan bir kayan nokta dizisi alan RoundedPolygon
aşırı yüklemeyi kullanarak bu şeklin köşelerini tek tek belirtebilirsiniz.
Kalp poligonunu parçalara ayırmak için noktaları belirtme konusunda kutupsal koordinat sisteminin, Kartezyen (x, y) koordinat sistemini kullanmaktan daha kolay olduğunu unutmayın. Bu sistemde 0°
sağ taraftan başlar ve saat yönünde ilerler. 270°
ise 12 saat konumundadır:

Artık her noktadaki açı (𝜭) ve merkeze olan yarıçapı belirterek şekli daha kolay bir şekilde tanımlayabilirsiniz:

Köşe noktaları artık oluşturulup RoundedPolygon
işlevine aktarılabilir:
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, ) }
Köşe noktalarının, bu radialToCartesian
işlevi kullanılarak Kartezyen koordinatlara çevrilmesi gerekir:
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))
Önceki kodda kalbin ham köşeleri verilmiştir ancak seçilen kalp şeklini elde etmek için belirli köşeleri yuvarlamanız gerekir. 90°
ve 270°
köşelerinde yuvarlama yoktur ancak diğer köşelerde yuvarlama vardır. Ayrı köşeler için özel yuvarlama elde etmek üzere perVertexRounding
parametresini kullanın:
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) )
Bu işlem sonucunda pembe kalp elde edilir:

Önceki şekiller kullanım alanınızı kapsamıyorsa özel şekil çizmek için Path
sınıfını kullanabilir veya diskten bir ImageVector
dosyası yükleyebilirsiniz. graphics-shapes
kitaplığı, rastgele şekiller için kullanılmak üzere tasarlanmamıştır. Bunun yerine, yuvarlatılmış poligonların ve bunlar arasındaki dönüşüm animasyonlarının oluşturulmasını basitleştirmek için tasarlanmıştır.
Ek kaynaklar
Daha fazla bilgi ve örnek için aşağıdaki kaynaklara göz atın:
- Blog: The Shape of Things to Come - Shapes
- Blog: Android'de şekil değiştirme
- Şekiller GitHub gösterimi