@@ -38,20 +38,20 @@ import androidx.compose.foundation.layout.size
38
38
import androidx.compose.material3.MaterialTheme
39
39
import androidx.compose.material3.Text
40
40
import androidx.compose.runtime.Composable
41
- import androidx.compose.runtime.State
42
41
import androidx.compose.runtime.getValue
43
- import androidx.compose.runtime.mutableStateOf
44
42
import androidx.compose.runtime.remember
45
43
import androidx.compose.ui.Alignment
46
44
import androidx.compose.ui.Modifier
47
45
import androidx.compose.ui.draw.clip
48
46
import androidx.compose.ui.draw.drawWithCache
47
+ import androidx.compose.ui.geometry.Rect
49
48
import androidx.compose.ui.geometry.Size
50
49
import androidx.compose.ui.graphics.Color
51
50
import androidx.compose.ui.graphics.Matrix
52
51
import androidx.compose.ui.graphics.Outline
53
52
import androidx.compose.ui.graphics.Path
54
53
import androidx.compose.ui.graphics.Shape
54
+ import androidx.compose.ui.graphics.asComposePath
55
55
import androidx.compose.ui.graphics.drawscope.scale
56
56
import androidx.compose.ui.graphics.drawscope.translate
57
57
import androidx.compose.ui.graphics.graphicsLayer
@@ -68,9 +68,11 @@ import androidx.graphics.shapes.Cubic
68
68
import androidx.graphics.shapes.Morph
69
69
import androidx.graphics.shapes.RoundedPolygon
70
70
import androidx.graphics.shapes.star
71
+ import androidx.graphics.shapes.toPath
71
72
import com.example.compose.snippets.R
72
73
import kotlin.math.PI
73
74
import kotlin.math.cos
75
+ import kotlin.math.max
74
76
import kotlin.math.sin
75
77
76
78
@Preview
@@ -86,8 +88,7 @@ fun BasicShapeCanvas() {
86
88
centerX = size.width / 2 ,
87
89
centerY = size.height / 2
88
90
)
89
- val roundedPolygonPath = roundedPolygon.cubics
90
- .toPath()
91
+ val roundedPolygonPath = roundedPolygon.toPath().asComposePath()
91
92
onDrawBehind {
92
93
drawPath(roundedPolygonPath, color = Color .Blue )
93
94
}
@@ -114,8 +115,7 @@ private fun RoundedShapeExample() {
114
115
smoothing = 1f
115
116
)
116
117
)
117
- val roundedPolygonPath = roundedPolygon.cubics
118
- .toPath()
118
+ val roundedPolygonPath = roundedPolygon.toPath().asComposePath()
119
119
onDrawBehind {
120
120
drawPath(roundedPolygonPath, color = Color .Black )
121
121
}
@@ -142,8 +142,7 @@ private fun RoundedShapeSmoothnessExample() {
142
142
smoothing = 0.1f
143
143
)
144
144
)
145
- val roundedPolygonPath = roundedPolygon.cubics
146
- .toPath()
145
+ val roundedPolygonPath = roundedPolygon.toPath().asComposePath()
147
146
onDrawBehind {
148
147
drawPath(roundedPolygonPath, color = Color .Black )
149
148
}
@@ -180,7 +179,7 @@ private fun MorphExample() {
180
179
181
180
val morph = Morph (start = triangle, end = square)
182
181
val morphPath = morph
183
- .toComposePath (progress = 0.5f )
182
+ .toPath (progress = 0.5f ).asComposePath( )
184
183
185
184
onDrawBehind {
186
185
drawPath(morphPath, color = Color .Black )
@@ -227,7 +226,8 @@ private fun MorphExampleAnimation() {
227
226
228
227
val morph = Morph (start = triangle, end = square)
229
228
val morphPath = morph
230
- .toComposePath(progress = morphProgress.value)
229
+ .toPath(progress = morphProgress.value)
230
+ .asComposePath()
231
231
232
232
onDrawBehind {
233
233
drawPath(morphPath, color = Color .Black )
@@ -238,6 +238,7 @@ private fun MorphExampleAnimation() {
238
238
// [END android_compose_graphics_polygon_morph_animation]
239
239
}
240
240
241
+ // [START android_compose_morph_to_path]
241
242
/* *
242
243
* Transforms the morph at a given progress into a [Path].
243
244
* It can optionally be scaled, using the origin (0,0) as pivot point.
@@ -259,10 +260,11 @@ fun Morph.toComposePath(progress: Float, scale: Float = 1f, path: Path = Path())
259
260
path.close()
260
261
return path
261
262
}
262
-
263
+ // [END android_compose_morph_to_path]
263
264
/* *
264
265
* Function used to create a Path from a list of Cubics.
265
266
*/
267
+ // [START android_compose_list_cubics_to_path]
266
268
fun List<Cubic>.toPath (path : Path = Path (), scale : Float = 1f): Path {
267
269
path.rewind()
268
270
firstOrNull()?.let { first ->
@@ -278,11 +280,12 @@ fun List<Cubic>.toPath(path: Path = Path(), scale: Float = 1f): Path {
278
280
path.close()
279
281
return path
280
282
}
283
+ // [END android_compose_list_cubics_to_path]
281
284
282
285
// [START android_compose_morph_clip_shape]
283
286
class MorphPolygonShape (
284
287
private val morph : Morph ,
285
- private val percentage : State < Float >
288
+ private val percentage : Float
286
289
) : Shape {
287
290
288
291
private val matrix = Matrix ()
@@ -296,7 +299,7 @@ class MorphPolygonShape(
296
299
matrix.scale(size.width / 2f , size.height / 2f )
297
300
matrix.translate(1f , 1f )
298
301
299
- val path = morph.toComposePath (progress = percentage.value )
302
+ val path = morph.toPath (progress = percentage).asComposePath( )
300
303
path.transform(matrix)
301
304
return Outline .Generic (path)
302
305
}
@@ -335,7 +338,7 @@ private fun MorphOnClick() {
335
338
modifier = Modifier
336
339
.size(200 .dp)
337
340
.padding(8 .dp)
338
- .clip(MorphPolygonShape (morph, animatedProgress))
341
+ .clip(MorphPolygonShape (morph, animatedProgress.value ))
339
342
.background(Color (0xFF80DEEA ))
340
343
.size(200 .dp)
341
344
.clickable(interactionSource = interactionSource, indication = null ) {
@@ -347,22 +350,26 @@ private fun MorphOnClick() {
347
350
}
348
351
349
352
// [START android_compose_shapes_polygon_compose_shape]
353
+ fun RoundedPolygon.getBounds () = calculateBounds().let { Rect (it[0 ], it[1 ], it[2 ], it[3 ]) }
350
354
class RoundedPolygonShape (
351
- private val polygon : State <RoundedPolygon >
355
+ private val polygon : RoundedPolygon ,
356
+ private var matrix : Matrix = Matrix ()
352
357
) : Shape {
353
- private val matrix = Matrix ()
358
+ private var path = Path ()
354
359
override fun createOutline (
355
360
size : Size ,
356
361
layoutDirection : LayoutDirection ,
357
362
density : Density
358
363
): Outline {
359
- val path = polygon.value.cubics.toPath()
360
- // below assumes that you haven't changed the default radius of 1f, nor the centerX and centerY of 0f
361
- // 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.
362
- matrix.scale(size.width / 2f , size.height / 2f )
363
- matrix.translate(1f , 1f )
364
- path.transform(matrix)
364
+ path.rewind()
365
+ path = polygon.toPath().asComposePath()
366
+ matrix.reset()
367
+ val bounds = polygon.getBounds()
368
+ val maxDimension = max(bounds.width, bounds.height)
369
+ matrix.scale(size.width / maxDimension, size.height / maxDimension)
370
+ matrix.translate(- bounds.left, - bounds.top)
365
371
372
+ path.transform(matrix)
366
373
return Outline .Generic (path)
367
374
}
368
375
}
@@ -373,14 +380,12 @@ class RoundedPolygonShape(
373
380
fun ApplyPolygonAsClipBasic () {
374
381
// [START android_compose_shapes_apply_as_clip]
375
382
val hexagon = remember {
376
- mutableStateOf(
377
- RoundedPolygon (
378
- 6 ,
379
- rounding = CornerRounding (0.2f )
380
- )
383
+ RoundedPolygon (
384
+ 6 ,
385
+ rounding = CornerRounding (0.2f )
381
386
)
382
387
}
383
- val clip = remember {
388
+ val clip = remember(hexagon) {
384
389
RoundedPolygonShape (polygon = hexagon)
385
390
}
386
391
Box (
@@ -403,11 +408,9 @@ fun ApplyPolygonAsClipBasic() {
403
408
fun ApplyPolygonAsClipImage () {
404
409
// [START android_compose_shapes_apply_as_clip_advanced]
405
410
val hexagon = remember {
406
- mutableStateOf(
407
- RoundedPolygon (
408
- 6 ,
409
- rounding = CornerRounding (0.2f )
410
- )
411
+ RoundedPolygon (
412
+ 6 ,
413
+ rounding = CornerRounding (0.2f )
411
414
)
412
415
}
413
416
val clip = remember(hexagon) {
@@ -439,8 +442,8 @@ fun ApplyPolygonAsClipImage() {
439
442
// [START android_compose_shapes_custom_rotating_morph_shape]
440
443
class CustomRotatingMorphShape (
441
444
private val morph : Morph ,
442
- private val percentage : State < Float > ,
443
- private val rotation : State < Float >
445
+ private val percentage : Float ,
446
+ private val rotation : Float
444
447
) : Shape {
445
448
446
449
private val matrix = Matrix ()
@@ -453,9 +456,9 @@ class CustomRotatingMorphShape(
453
456
// 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.
454
457
matrix.scale(size.width / 2f , size.height / 2f )
455
458
matrix.translate(1f , 1f )
456
- matrix.rotateZ(rotation.value )
459
+ matrix.rotateZ(rotation)
457
460
458
- val path = morph.toComposePath (progress = percentage.value )
461
+ val path = morph.toPath (progress = percentage).asComposePath( )
459
462
path.transform(matrix)
460
463
461
464
return Outline .Generic (path)
@@ -499,13 +502,6 @@ private fun RotatingScallopedProfilePic() {
499
502
),
500
503
label = " animatedMorphProgress"
501
504
)
502
- val morphingShape = remember {
503
- CustomRotatingMorphShape (
504
- morph,
505
- animatedProgress,
506
- animatedRotation
507
- )
508
- }
509
505
Box (
510
506
modifier = Modifier .fillMaxSize(),
511
507
contentAlignment = Alignment .Center
@@ -515,7 +511,13 @@ private fun RotatingScallopedProfilePic() {
515
511
contentDescription = " Dog" ,
516
512
contentScale = ContentScale .Crop ,
517
513
modifier = Modifier
518
- .clip(morphingShape)
514
+ .clip(
515
+ CustomRotatingMorphShape (
516
+ morph,
517
+ animatedProgress.value,
518
+ animatedRotation.value
519
+ )
520
+ )
519
521
.size(200 .dp)
520
522
)
521
523
}
@@ -570,8 +572,7 @@ private fun CartesianPoints() {
570
572
Box (
571
573
modifier = Modifier
572
574
.drawWithCache {
573
- val roundedPolygonPath = polygon.cubics
574
- .toPath()
575
+ val roundedPolygonPath = polygon.toPath().asComposePath()
575
576
onDrawBehind {
576
577
scale(size.width * 0.5f , size.width * 0.5f ) {
577
578
translate(size.width * 0.5f , size.height * 0.5f ) {
0 commit comments