1 /*
<lambda>null2  * Copyright 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 @file:OptIn(ExperimentalMotionApi::class)
18 
19 package androidx.constraintlayout.compose.demos
20 
21 import androidx.compose.animation.core.animateFloatAsState
22 import androidx.compose.animation.core.tween
23 import androidx.compose.foundation.Canvas
24 import androidx.compose.foundation.background
25 import androidx.compose.foundation.clickable
26 import androidx.compose.foundation.layout.fillMaxSize
27 import androidx.compose.material.icons.Icons
28 import androidx.compose.material.icons.filled.Face
29 import androidx.compose.runtime.Composable
30 import androidx.compose.runtime.getValue
31 import androidx.compose.runtime.mutableStateOf
32 import androidx.compose.runtime.remember
33 import androidx.compose.runtime.setValue
34 import androidx.compose.ui.Modifier
35 import androidx.compose.ui.graphics.Color
36 import androidx.compose.ui.graphics.drawscope.clipRect
37 import androidx.compose.ui.graphics.drawscope.translate
38 import androidx.compose.ui.graphics.painter.Painter
39 import androidx.compose.ui.graphics.vector.rememberVectorPainter
40 import androidx.compose.ui.layout.layoutId
41 import androidx.compose.ui.tooling.preview.Preview
42 import androidx.constraintlayout.compose.Arc
43 import androidx.constraintlayout.compose.ConstraintLayoutBaseScope
44 import androidx.constraintlayout.compose.Dimension
45 import androidx.constraintlayout.compose.ExperimentalMotionApi
46 import androidx.constraintlayout.compose.MotionLayout
47 import androidx.constraintlayout.compose.MotionScene
48 import androidx.constraintlayout.compose.Wrap
49 
50 /**
51  * Shows how to animate moving pieces of a puzzle using MotionLayout.
52  *
53  * &nbsp;
54  *
55  * The [PuzzlePiece]s are laid out using the [ConstraintLayoutBaseScope.createFlow] helper.
56  *
57  * And the animation is achieved by creating two ConstraintSets. One providing ordered IDs to Flow,
58  * and the other providing a shuffled list of the same IDs.
59  *
60  * @see PuzzlePiece
61  */
62 @Preview
63 @Composable
64 fun AnimatedPuzzlePiecesDemo() {
65     val grid = 5
66     val blocks = grid * grid
67 
68     var animateToEnd by remember { mutableStateOf(true) }
69 
70     val index = remember { Array(blocks) { it }.apply { shuffle() } }
71     val refId = remember { Array(blocks) { "W$it" } }
72 
73     // Recreate scene when order changes (which is driven by toggling `animateToEnd`)
74     val scene =
75         remember(animateToEnd) {
76             MotionScene {
77                 val ordered = refId.map { createRefFor(it) }.toTypedArray()
78                 val shuffle = index.map { ordered[it] }.toTypedArray()
79                 val set1 = constraintSet {
80                     val flow =
81                         createFlow(
82                             elements = ordered,
83                             maxElement = grid,
84                             wrapMode = Wrap.Aligned,
85                         )
86                     constrain(flow) {
87                         centerTo(parent)
88                         width = Dimension.ratio("1:1")
89                         height = Dimension.ratio("1:1")
90                     }
91                     ordered.forEach {
92                         constrain(it) {
93                             width = Dimension.percent(1f / grid)
94                             height = Dimension.ratio("1:1")
95                         }
96                     }
97                 }
98                 val set2 = constraintSet {
99                     val flow =
100                         createFlow(
101                             elements = shuffle,
102                             maxElement = grid,
103                             wrapMode = Wrap.Aligned,
104                         )
105                     constrain(flow) {
106                         centerTo(parent)
107                         width = Dimension.ratio("1:1")
108                         height = Dimension.ratio("1:1")
109                     }
110                     ordered.forEach {
111                         constrain(it) {
112                             width = Dimension.percent(1f / grid)
113                             height = Dimension.ratio("1:1")
114                         }
115                     }
116                 }
117                 transition(set1, set2, "default") {
118                     motionArc = Arc.StartHorizontal
119                     keyAttributes(*ordered) {
120                         frame(40) {
121                             // alpha = 0.0f
122                             rotationZ = -90f
123                             scaleX = 0.1f
124                             scaleY = 0.1f
125                         }
126                         frame(70) {
127                             rotationZ = 90f
128                             scaleX = 0.1f
129                             scaleY = 0.1f
130                         }
131                     }
132                 }
133             }
134         }
135 
136     val progress by
137         animateFloatAsState(targetValue = if (animateToEnd) 1f else 0f, animationSpec = tween(800))
138 
139     MotionLayout(
140         motionScene = scene,
141         modifier =
142             Modifier.clickable {
143                     animateToEnd = !animateToEnd
144                     index.shuffle()
145                 }
146                 .background(Color.Red)
147                 .fillMaxSize(),
148         progress = progress
149     ) {
150         val painter = rememberVectorPainter(image = Icons.Default.Face)
151         index.forEachIndexed { i, id ->
152             PuzzlePiece(
153                 x = i % grid,
154                 y = i / grid,
155                 gridSize = grid,
156                 painter = painter,
157                 modifier = Modifier.layoutId(refId[id])
158             )
159         }
160     }
161 }
162 
163 /**
164  * Composable that displays a fragment of the given surface (provided through [painter]) based on
165  * the given position ([x], [y]) of a square grid of size [gridSize].
166  */
167 @Composable
PuzzlePiecenull168 fun PuzzlePiece(x: Int, y: Int, gridSize: Int, painter: Painter, modifier: Modifier = Modifier) {
169     Canvas(modifier.fillMaxSize()) {
170         clipRect {
171             translate(left = -x * size.width, top = -y * size.height) {
172                 with(painter) { draw(size.times(gridSize.toFloat())) }
173             }
174         }
175     }
176 }
177