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-এর ডিফল্ট ভ্যালু) হলে কোণগুলি পুরোপুরি বৃত্তাকার
হয়ে যায়। একটি ননজিরো স্মুদিং ফ্যাক্টর (সর্বাধিক ১.০ পর্যন্ত) এর ফলে
কোণটি তিনটি পৃথক বক্ররেখা দ্বারা বৃত্তাকার হয়।
যেমন, নিচের স্নিপেটটি স্মুদিং ০ বনাম ১-এ সেট করার মধ্যে সূক্ষ্ম পার্থক্য ব্যাখ্যা করে:
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 মান প্রদান করুন:
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-এ স্ট্যান্ডার্ড অ্যানিমেশন 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) ) }
এর ফলে নিম্নলিখিত ঘটনাগুলি ঘটে:
এটি আগে যা রেন্ডার করা হচ্ছিল তার থেকে খুব একটা আলাদা নাও লাগতে পারে, তবে এটি 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° ১২টার পজিশনে থাকে:
এখন প্রতিটি পয়েন্টে কেন্দ্র থেকে কোণ (𝜭) এবং ব্যাসার্ধ নির্দিষ্ট করে আরও সহজ উপায়ে আকৃতিটি সংজ্ঞায়িত করা যেতে পারে:
এখন ভার্টিসেস তৈরি করে 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 লাইব্রেরিটি ইচ্ছামতো আকৃতি তৈরি করার জন্য নয়, বরং এটি বিশেষভাবে রাউন্ডেড পলিগন এবং তাদের মধ্যে মর্ফ অ্যানিমেশন তৈরি করার কাজ সহজ করার জন্য ডিজাইন করা হয়েছে।
অতিরিক্ত রিসোর্স
আরও তথ্য ও উদাহরণের জন্য, নিম্নলিখিত রিসোর্স দেখুন: