Oluşturma aracıyla çokgenlerden oluşan şekiller oluşturabilirsiniz. Örneğin, aşağıdaki şekilleri oluşturabilirsiniz:

Compose'da özel yuvarlak poligon oluşturmak için app/build.gradle
öğenize graphics-shapes
bağımlılığını ekleyin:
implementation "androidx.graphics:graphics-shapes:1.0.1"
Bu kitaplık, çokgenlerden oluşan şekiller oluşturmanıza olanak tanır. Çokgen şekillerin yalnızca düz kenarları ve keskin köşeleri olsa da bu şekillerde isteğe bağlı olarak yuvarlatılmış köşeler kullanılabilir. İki farklı şekil arasında kolayca geçiş yapabilirsiniz. İstediğiniz şekiller arasında dönüştürme yapmak zordur ve genellikle tasarım zamanında sorun yaşanır. Ancak bu kitaplık, benzer çokgen yapıları olan bu şekiller arasında dönüşüm yaparak işi kolaylaştırır.
Poligon oluşturma
Aşağıdaki snippet, çizim alanının ortasında 6 noktalı 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 içeren bir RoundedPolygon
oluşturur. Bu şekli bir Compose uygulamasında çizmek için, şekli Compose'un nasıl çizeceğini bildiği bir forma dönüştürmek üzere Path
nesnesi almanız gerekir.
Çokgenin köşelerini yuvarlama
Bir poligonun köşelerini yuvarlamak için CornerRounding
parametresini kullanın. Bu işlev iki parametre (radius
ve smoothing
) alır. Her yuvarlak köşe, 1-3 kübik eğriden oluşur. Bu eğrilerin ortası dairesel yay şeklindedir. İki yan eğri ise şeklin kenarından merkez eğriye geçiş yapar.
Yarıçap
radius
, bir köşeyi yuvarlamak için kullanılan dairenin yarıçapıdır.
Örneğin, aşağıdaki yuvarlak köşeli üçgen şu şekilde oluşturulur:


r
, yuvarlatılmış köşelerin dairesel yuvarlama boyutunu belirler.Yumuşatma
Yumuşatma, köşenin yuvarlak kısmından kenara ulaşmanın ne kadar sürdüğünü belirleyen bir faktördür. 0 düzleştirme faktörü (düzleştirilmemiş, CornerRounding
için varsayılan değer) tamamen dairesel köşe yuvarlama ile sonuçlanır. Sıfırdan farklı bir düzleştirme faktörü (en fazla 1,0) köşenin üç ayrı eğriyle yuvarlatılmasına neden olur.


Örneğin, aşağıdaki snippet'te düzeltme ayarının 0 ile 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, şekil merkez (0, 0
) etrafında 1
yarıçapıyla oluşturulur.
Bu yarıçap, şeklin temel alındığı poligonun merkezi ile dış köşeleri arasındaki mesafeyi temsil eder. Köşeleri yuvarlamanın, yuvarlatılan köşeler yuvarlatılan köşelerden daha merkeze yakın olacağından daha küçük bir şekil oluşturduğunu unutmayın. Bir poligonun boyutunu ayarlamak için radius
değerini düzenleyin. Konumu ayarlamak için poligonun centerX
veya centerY
değerini değiştirin.
Alternatif olarak, DrawScope#translate()
gibi standart DrawScope
dönüştürme işlevlerini kullanarak nesnenin boyutunu, konumunu ve dönüşünü değiştirmek için dönüştürme işlemini kullanın.
Şekilleri dönüştürme
Morph
nesnesi, iki çokgen şekil arasındaki animasyonu temsil eden yeni bir şekildir. İki şekil arasında geçiş yapmak için iki RoundedPolygons
ve bu iki şekli alan bir Morph
nesnesi oluşturun. Başlangıç ve bitiş şekilleri arasında bir şekil hesaplamak için başlangıç (0) ve bitiş (1) şekilleri arasındaki formu belirlemek üzere sıfır ile bir arasında bir progress
değeri sağlayın:
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 durumu iki şeklin (yuvarlak üçgen ve kare) tam ortasında olduğundan aşağıdaki sonuç elde edilir:

Çoğu durumda, dönüştürme işlemi yalnızca statik bir oluşturma olarak değil, animasyonun bir parçası olarak yapılır. Bu ikisi arasında animasyon yapmak için Compose'daki standart animasyon API'lerini kullanarak zaman içinde ilerleme değerini değiştirebilirsiniz. Örneğin, bu iki şekil arasındaki dönüşümü aşağıdaki gibi sonsuza kadar canlandırabilirsiniz:
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() )

Klip olarak poligon kullanma
Bir composable'ın nasıl oluşturulduğunu değiştirmek ve kırpma alanının etrafında çizilen gölgelerden yararlanmak için Compose'da clip
değiştiricisini 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, aşağıdaki snippet'te gösterildiği gibi poligonu 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 Compose'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ıklamada dönüştürme düğmesi
graphics-shape
kitaplığını kullanarak basıldığında iki şekil arasında dönüşen bir düğme oluşturabilirsiniz. Öncelikle, Shape
öğesini genişleten, uygun şekilde ölçeklendiren ve çeviren bir MorphPolygonShape
oluşturun. Şeklin animasyonlu hale getirilebilmesi için ilerleme durumunun 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üştürme özelliğini kullanmak için shapeA
ve shapeB
olmak üzere iki çokgen oluşturun. Morph
oluşturun ve hatırlayın. Ardından, animasyonun itici gücü olarak interactionSource
on press'i kullanarak düğmeye klip ana hattı olarak dönüştürme efektini 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 işlem, kutuya dokunulduğunda aşağıdaki animasyonun gösterilmesine neden olur:

Şekil dönüştürme animasyonunu sonsuz kez oynatma
Dönüşüm şeklini sonsuza kadar canlandırmak için rememberInfiniteTransition
simgesini kullanın.
Aşağıda, zaman içinde şekli (ve yönü) sonsuza kadar değişen 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
Düzenli çokgenlerden oluşturulan şekiller kullanım alanınızı kapsamıyorsa köşe listesiyle daha özel bir şekil oluşturabilirsiniz. Örneğin, aşağıdaki gibi bir kalp şekli oluşturmak isteyebilirsiniz:

Bu şeklin ayrı ayrı köşelerini, x ve y koordinatlarından oluşan bir kayan nokta dizisi alan RoundedPolygon
aşırı yüklemesini kullanarak belirtebilirsiniz.
Kalp poligonunu parçalamak için noktaları belirtmeye yönelik kutupsal koordinat sisteminin, 0°
sağ tarafta başlayıp saat yönünde ilerleyerek 12 konumunda 270°
ile devam eden kartezyen (x,y) koordinat sistemini kullanmaktan daha kolay olduğunu unutmayın:

Şekil artık her noktada merkezden olan açı (𝜭) ve yarıçap belirtilerek daha kolay bir şekilde tanımlanabilir:

Köşeler artık oluşturulabilir ve 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öşelerin, şu radialToCartesian
işlevi kullanılarak Kartezyen koordinatlara dönüştürülmesi 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))
Yukarıdaki kod, kalp için ham köşe noktalarını verir ancak seçilen kalp şeklini elde etmek için belirli köşeleri yuvarlamanız gerekir. 90°
ve 270°
konumlarındaki köşeler yuvarlatılmamış, diğer köşeler ise yuvarlatılmıştır. Köşeleri ayrı ayrı yuvarlatmak için 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 durumda pembe kalp gösterilir:

Yukarıdaki şekiller kullanım alanınızı kapsamıyorsa Path
sınıfını kullanarak özel bir şekil çizmeyi veya diskten bir ImageVector
dosyası yüklemeyi deneyin. graphics-shapes
kitaplığı, rastgele şekiller için kullanılmak üzere tasarlanmamıştır. Özellikle yuvarlatılmış çokgenlerin oluşturulmasını ve bunlar arasında şekil değiştirme animasyonlarının yapılmasını kolaylaştırmak için tasarlanmıştır.
Ek kaynaklar
Daha fazla bilgi ve örnek için aşağıdaki kaynakları inceleyin:
- Blog: Gelecekteki Şekiller - Şekiller
- Blog: Android'de şekil dönüştürme
- Şekillerle ilgili GitHub gösterimi