Compose की मदद से, पॉलीगॉन से बने आकार बनाए जा सकते हैं. उदाहरण के लिए, इस तरह के आकार बनाए जा सकते हैं:
Compose में अपनी पसंद के मुताबिक गोल कोनों वाला पॉलीगॉन बनाने के लिए, अपनी app/build.gradle में graphics-shapes डिपेंडेंसी जोड़ें:
implementation "androidx.graphics:graphics-shapes:1.0.1"
इस लाइब्रेरी की मदद से, पॉलीगॉन से बने आकार बनाए जा सकते हैं. पॉलिगॉन शेप में सिर्फ़ सीधी लाइनें और नुकीले कोने होते हैं. हालांकि, इन शेप में गोल कोने भी बनाए जा सकते हैं. इससे दो अलग-अलग शेप के बीच मॉर्फ़ करना आसान हो जाता है. किसी भी आकार के बीच मॉर्फ़िंग करना मुश्किल होता है. साथ ही, यह डिज़ाइन के समय की समस्या होती है. हालांकि, यह लाइब्रेरी एक जैसे पॉलीगॉन स्ट्रक्चर वाले इन शेप के बीच मॉर्फ़ करके, इसे आसान बनाती है.
पॉलीगॉन बनाना
नीचे दिया गया स्निपेट, ड्रॉइंग एरिया के बीच में छह पॉइंट वाला एक बेसिक पॉलीगॉन बनाता है:
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. हर गोल किनारा, एक से तीन क्यूबिक कर्व से बना होता है. इसके बीच में एक गोलाकार आर्क शेप होती है. वहीं, दोनों तरफ़ के ("फ़्लैंकिंग") कर्व, शेप के किनारे से बीच के कर्व में बदल जाते हैं.
दायरा
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) )
साइज़ और पोज़िशन
डिफ़ॉल्ट रूप से, सेंटर (0, 0) के आस-पास 1 की त्रिज्या वाला आकार बनाया जाता है.
यह त्रिज्या, सेंटर और पॉलीगॉन के बाहरी वर्टिसेस के बीच की दूरी को दिखाती है.
इस पॉलीगॉन के आधार पर आकार बनाया जाता है. ध्यान दें कि कोनों को गोल करने से आकार छोटा हो जाता है. ऐसा इसलिए होता है, क्योंकि गोल किए गए कोने, गोल किए जा रहे शीर्षों की तुलना में केंद्र के ज़्यादा करीब होते हैं. पॉलीगॉन का साइज़ बदलने के लिए, 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() )
ऊपर दिए गए उदाहरण में, प्रोग्रेस दोनों आकृतियों (गोल कोनों वाला त्रिकोण और वर्ग) के बीच में है. इससे यह नतीजा मिलता है:
ज़्यादातर मामलों में, मॉर्फ़िंग को ऐनिमेशन के तौर पर किया जाता है, न कि सिर्फ़ स्टैटिक रेंडरिंग के तौर पर. इन दोनों के बीच ऐनिमेशन दिखाने के लिए, 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() )
पॉलीगॉन को क्लिप के तौर पर इस्तेमाल करना
Compose में, 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) ) }
इससे ये नतीजे मिलते हैं:
यह पहले रेंडर होने वाले कोड से ज़्यादा अलग नहीं लग सकता है. हालांकि, इससे 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 लाइब्रेरी का इस्तेमाल, किसी भी तरह के आकार बनाने के लिए नहीं किया जाता. इसे खास तौर पर, गोल पॉलीगॉन और उनके बीच मॉर्फ़ ऐनिमेशन बनाने की प्रोसेस को आसान बनाने के लिए डिज़ाइन किया गया है.
अन्य संसाधन
ज़्यादा जानकारी और उदाहरणों के लिए, ये लेख पढ़ें: