Skip to content
This repository was archived by the owner on Dec 27, 2024. It is now read-only.

Commit e63fd29

Browse files
committed
Update MotionLayout Compose examples
- Added some Javadoc - Formatting - Some tweaks & cleanup
1 parent 4a1c1d7 commit e63fd29

File tree

6 files changed

+224
-185
lines changed

6 files changed

+224
-185
lines changed

demoProjects/ExamplesComposeMotionLayout/app/src/main/java/com/example/examplescomposemotionlayout/CollapsingToolbarDsl.kt

+24-13
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,21 @@ import androidx.constraintlayout.compose.MotionScene
2525
import java.lang.Float.min
2626

2727
/**
28-
* A demo of using MotionLayout as a collapsing Toolbar using the DSL to define the MotionScene
28+
* A demo of using MotionLayout as a collapsing Toolbar using the DSL to define the MotionScene.
29+
*
30+
* This is based on using
31+
*
32+
* ```
33+
* Column(
34+
* horizontalAlignment = Alignment.CenterHorizontally,
35+
* modifier = Modifier.verticalScroll(scroll)
36+
* )
37+
* ```
38+
* The Column's modifier Modifier.verticalScroll(scroll) will modify scroll.value as it scrolls.
39+
* We can use this value with a little math to calculate the appropriate progress.
40+
*
41+
* When the Column is at the start the MotionLayout sits on top of the Spacer. As the user scrolls
42+
* up the MotionLayout shrinks with the scrolling Spacer then, stops.
2943
*/
3044
@OptIn(ExperimentalMotionApi::class)
3145
@Preview(group = "scroll", device = "spec:shape=Normal,width=480,height=800,unit=dp,dpi=440")
@@ -34,10 +48,9 @@ fun ToolBarExampleDsl() {
3448
val scroll = rememberScrollState(0)
3549
val big = 250.dp
3650
val small = 50.dp
37-
var scene = MotionScene() {
38-
val title = createRefFor("title")
39-
val image = createRefFor("image")
40-
val icon = createRefFor("icon")
51+
val scene = MotionScene {
52+
val (title, image, icon) = createRefsFor("title", "image", "icon")
53+
4154
val start1 = constraintSet {
4255
constrain(title) {
4356
bottom.linkTo(image.bottom)
@@ -74,7 +87,7 @@ fun ToolBarExampleDsl() {
7487
start.linkTo(image.start, 16.dp)
7588
}
7689
}
77-
transition(start1, end1,"default") {}
90+
transition(start1, end1, "default") {}
7891
}
7992

8093
Column(
@@ -91,24 +104,22 @@ fun ToolBarExampleDsl() {
91104
)
92105
}
93106
}
94-
val gap = with(LocalDensity.current){big.toPx() - small.toPx()}
95-
val progress = min(scroll.value / gap, 1f);
107+
val gap = with(LocalDensity.current) { big.toPx() - small.toPx() }
108+
val progress = minOf(scroll.value / gap, 1f)
96109

97110
MotionLayout(
98111
modifier = Modifier.fillMaxSize(),
99112
motionScene = scene,
100113
progress = progress
101114
) {
102115
Image(
103-
modifier = Modifier.layoutId("image"),
116+
modifier = Modifier
117+
.layoutId("image")
118+
.background(customColor("image", "cover")),
104119
painter = painterResource(R.drawable.bridge),
105120
contentDescription = null,
106121
contentScale = ContentScale.Crop
107122
)
108-
Box(modifier = Modifier
109-
.layoutId("image")
110-
.background(motionProperties("image").value.color("cover"))) {
111-
}
112123
Image(
113124
modifier = Modifier.layoutId("icon"),
114125
painter = painterResource(R.drawable.menu),

demoProjects/ExamplesComposeMotionLayout/app/src/main/java/com/example/examplescomposemotionlayout/CollapsingToolbarLazyDsl.kt

+32-22
Original file line numberDiff line numberDiff line change
@@ -31,17 +31,34 @@ import androidx.constraintlayout.compose.MotionLayout
3131
import androidx.constraintlayout.compose.MotionScene
3232

3333
/**
34-
* A demo of using MotionLayout as a collapsing Toolbar using JSON to define the MotionScene
34+
* A demo using MotionLayout as a collapsing Toolbar using the DSL to define the MotionScene, where
35+
* the scrolling of the LazyColumn is obtained with a NestedScrollConnection.
36+
*
37+
* ```
38+
* LazyColumn(
39+
* Modifier
40+
* .fillMaxWidth()
41+
* .nestedScroll(nestedScrollConnection)
42+
* ) {
43+
* items(100) {
44+
* Text(text = "item $it", modifier = Modifier.padding(4.dp))
45+
* }
46+
* }
47+
* ```
48+
*
49+
* A NestedScrollConnection object is passed to a LazyColumn Composable via a modifier
50+
* (Modifier.nestedScroll(nestedScrollConnection)).
51+
*
52+
* When the onPreScroll of the NestedScrollConnection is called It returns the amount of "offset" to
53+
* absorb and uses the offset to collapse the MotionLayout.
3554
*/
3655
@OptIn(ExperimentalMotionApi::class)
3756
@Preview(group = "scroll", device = "spec:shape=Normal,width=480,height=800,unit=dp,dpi=440")
3857
@Composable
3958
fun ToolBarLazyExampleDsl() {
40-
val scroll = rememberScrollState(0)
41-
4259
val big = 250.dp
4360
val small = 50.dp
44-
var scene = MotionScene() {
61+
val scene = MotionScene {
4562
val title = createRefFor("title")
4663
val image = createRefFor("image")
4764
val icon = createRefFor("icon")
@@ -83,7 +100,7 @@ fun ToolBarLazyExampleDsl() {
83100
start.linkTo(image.start, 16.dp)
84101
}
85102
}
86-
transition( start1, end1, "default") {}
103+
transition(start1, end1, "default") {}
87104
}
88105

89106
val maxPx = with(LocalDensity.current) { big.roundToPx().toFloat() }
@@ -93,7 +110,7 @@ fun ToolBarLazyExampleDsl() {
93110
val nestedScrollConnection = remember {
94111
object : NestedScrollConnection {
95112
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
96-
val height = toolbarHeight.value;
113+
val height = toolbarHeight.value
97114

98115
if (height + available.y > maxPx) {
99116
toolbarHeight.value = maxPx
@@ -108,30 +125,24 @@ fun ToolBarLazyExampleDsl() {
108125
toolbarHeight.value += available.y
109126
return Offset(0f, available.y)
110127
}
111-
112128
}
113129
}
114130

115-
val progress = 1 - (toolbarHeight.value - minPx) / (maxPx - minPx);
131+
val progress = 1 - (toolbarHeight.value - minPx) / (maxPx - minPx)
116132

117133
Column {
118134
MotionLayout(
119-
modifier = Modifier.background(Color.Green),
120135
motionScene = scene,
121136
progress = progress
122137
) {
123138
Image(
124-
modifier = Modifier.layoutId("image"),
139+
modifier = Modifier
140+
.layoutId("image")
141+
.background(customColor("image", "cover")),
125142
painter = painterResource(R.drawable.bridge),
126143
contentDescription = null,
127144
contentScale = ContentScale.Crop
128145
)
129-
Box(
130-
modifier = Modifier
131-
.layoutId("image")
132-
.background(motionProperties("image").value.color("cover"))
133-
) {
134-
}
135146
Image(
136147
modifier = Modifier.layoutId("icon"),
137148
painter = painterResource(R.drawable.menu),
@@ -144,14 +155,13 @@ fun ToolBarLazyExampleDsl() {
144155
color = Color.White
145156
)
146157
}
147-
Box(
158+
LazyColumn(
148159
Modifier
149160
.fillMaxWidth()
150-
.nestedScroll(nestedScrollConnection)) {
151-
LazyColumn() {
152-
items(100) {
153-
Text(text = "item $it", modifier = Modifier.padding(4.dp))
154-
}
161+
.nestedScroll(nestedScrollConnection)
162+
) {
163+
items(100) {
164+
Text(text = "item $it", modifier = Modifier.padding(4.dp))
155165
}
156166
}
157167
}

demoProjects/ExamplesComposeMotionLayout/app/src/main/java/com/example/examplescomposemotionlayout/DynamicGraph.kt

+34-23
Original file line numberDiff line numberDiff line change
@@ -25,24 +25,30 @@ import kotlin.random.Random
2525

2626

2727
/**
28-
* A demo of using MotionLayout as a collapsing Toolbar using JSON to define the MotionScene
28+
* Shows how to use MotionLayout to have animated graphs in a LazyColumn, where each graph is
29+
* animated as it's revealed.
30+
*
31+
* Demonstrates how to dynamically create constraints based on input. See [DynamicGraph]. Where
32+
* constraints are created to lay out the given values into a single graph layout.
2933
*/
30-
3134
@Preview(group = "scroll", device = "spec:shape=Normal,width=480,height=800,unit=dp,dpi=440")
3235
@Composable
3336
fun ManyGraphs() {
34-
val rand = Random
35-
val count = 100
36-
val graphs = mutableListOf<List<Float>>()
37-
for (i in 0..100) {
38-
val values = FloatArray(10) { rand.nextInt(100).toFloat() + 10f }.asList()
39-
graphs.add(values)
37+
val graphs = remember {
38+
mutableListOf<List<Float>>().apply {
39+
for (i in 0..100) {
40+
val values = FloatArray(10) { Random.nextInt(100).toFloat() + 10f }.asList()
41+
add(values)
42+
}
43+
}
4044
}
41-
LazyColumn() {
45+
LazyColumn {
4246
items(100) {
43-
Box(modifier = Modifier
44-
.padding(3.dp)
45-
.height(200.dp)) {
47+
Box(
48+
modifier = Modifier
49+
.padding(3.dp)
50+
.height(200.dp)
51+
) {
4652
DynamicGraph(graphs[it])
4753
}
4854
}
@@ -61,7 +67,7 @@ fun DynamicGraph(values: List<Float> = listOf<Float>(12f, 32f, 21f, 32f, 2f), ma
6167
tmpNames[i] = "foo$i"
6268
}
6369
val names: List<String> = tmpNames.filterNotNull()
64-
var scene = MotionScene() {
70+
val scene = MotionScene {
6571
val cols = names.map { createRefFor(it) }.toTypedArray()
6672
val start1 = constraintSet {
6773
createHorizontalChain(elements = cols)
@@ -84,21 +90,28 @@ fun DynamicGraph(values: List<Float> = listOf<Float>(12f, 32f, 21f, 32f, 2f), ma
8490
}
8591
}
8692
}
87-
transition(start1, end1,"default") {
93+
transition(start1, end1, "default") {
8894
}
8995
}
9096
var animateToEnd by remember { mutableStateOf(true) }
97+
val animateToEndFlow = remember { snapshotFlow { animateToEnd } }
9198
val progress = remember { Animatable(0f) }
92-
LaunchedEffect(animateToEnd) {
93-
progress.animateTo(
94-
if (animateToEnd) 1f else 0f,
95-
animationSpec = tween(800)
96-
)
99+
100+
// Animate on reveal
101+
LaunchedEffect(Unit) {
102+
animateToEndFlow.collect {
103+
progress.animateTo(
104+
if (animateToEnd) 1f else 0f,
105+
animationSpec = tween(800)
106+
)
107+
}
97108
}
109+
98110
MotionLayout(
99111
modifier = Modifier
100112
.background(Color(0xFF221010))
101-
.fillMaxSize().clickable{animateToEnd = !animateToEnd}
113+
.fillMaxSize()
114+
.clickable { animateToEnd = !animateToEnd }
102115
.padding(1.dp),
103116
motionScene = scene,
104117
progress = progress.value
@@ -108,10 +121,8 @@ fun DynamicGraph(values: List<Float> = listOf<Float>(12f, 32f, 21f, 32f, 2f), ma
108121
modifier = Modifier
109122
.layoutId("foo$i")
110123
.clip(RoundedCornerShape(20.dp))
111-
.background(Color.hsv(i*240f/count,0.6f,0.6f))
124+
.background(Color.hsv(i * 240f / count, 0.6f, 0.6f))
112125
)
113-
114126
}
115127
}
116-
117128
}

demoProjects/ExamplesComposeMotionLayout/app/src/main/java/com/example/examplescomposemotionlayout/MotionInLazyColumnDsl.kt

+15-18
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package com.example.examplescomposemotionlayout
22

3-
import androidx.compose.animation.core.Animatable
3+
import androidx.compose.animation.core.animateFloatAsState
44
import androidx.compose.animation.core.tween
55
import androidx.compose.foundation.Image
66
import androidx.compose.foundation.background
@@ -25,14 +25,15 @@ import androidx.constraintlayout.compose.MotionLayout
2525
import androidx.constraintlayout.compose.MotionScene
2626

2727
/**
28-
* A demo of using MotionLayout in a Lazy Column written using DSL Syntax
28+
* Shows how to use MotionLayout to have animated expandable items in a LazyColumn.
29+
*
30+
* Where the MotionScene is defined using the DSL.
2931
*/
3032
@OptIn(ExperimentalMotionApi::class)
3133
@Preview(group = "scroll", device = "spec:shape=Normal,width=480,height=800,unit=dp,dpi=440")
3234
@Composable
3335
fun MotionInLazyColumnDsl() {
34-
35-
var scene = MotionScene() {
36+
val scene = MotionScene {
3637
val title = createRefFor("title")
3738
val image = createRefFor("image")
3839
val icon = createRefFor("icon")
@@ -72,30 +73,28 @@ fun MotionInLazyColumnDsl() {
7273
start.linkTo(parent.start, 16.dp)
7374
}
7475
}
75-
transition( start1, end1,"default") {}
76+
transition(start1, end1, "default") {}
7677
}
7778

7879
val model = remember { BooleanArray(100) }
7980

80-
LazyColumn() {
81+
LazyColumn {
8182
items(100) {
82-
// Text(text = "item $it", modifier = Modifier.padding(4.dp))
8383
Box(modifier = Modifier.padding(3.dp)) {
8484
var animateToEnd by remember { mutableStateOf(model[it]) }
85-
val progress = remember { Animatable(if (model[it]) 1f else 0f) }
86-
LaunchedEffect(animateToEnd) {
87-
progress.animateTo(
88-
if (animateToEnd) 1f else 0f,
89-
animationSpec = tween(700)
90-
)
91-
}
85+
86+
val progress by animateFloatAsState(
87+
targetValue = if (animateToEnd) 1f else 0f,
88+
animationSpec = tween(700)
89+
)
90+
9291
MotionLayout(
9392
modifier = Modifier
9493
.background(Color(0xFF331B1B))
9594
.fillMaxWidth()
9695
.padding(1.dp),
9796
motionScene = scene,
98-
progress = progress.value
97+
progress = progress
9998
) {
10099
Image(
101100
modifier = Modifier.layoutId("image"),
@@ -108,12 +107,11 @@ fun MotionInLazyColumnDsl() {
108107
.layoutId("icon")
109108
.clickable {
110109
animateToEnd = !animateToEnd
111-
model[it] = animateToEnd;
110+
model[it] = animateToEnd
112111
},
113112
painter = painterResource(R.drawable.menu),
114113
contentDescription = null
115114
)
116-
117115
Text(
118116
modifier = Modifier.layoutId("title"),
119117
text = "San Francisco $it",
@@ -124,5 +122,4 @@ fun MotionInLazyColumnDsl() {
124122
}
125123
}
126124
}
127-
128125
}

0 commit comments

Comments
 (0)