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

Commit d7e308f

Browse files
authored
Add a MotionLayout that animates between changing constraint sets (#359)
1 parent 4880da5 commit d7e308f

File tree

2 files changed

+53
-15
lines changed

2 files changed

+53
-15
lines changed

constraintlayout/compose/src/main/java/androidx/constraintlayout/compose/MotionLayout.kt

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ package androidx.constraintlayout.compose
1818

1919
import android.annotation.SuppressLint
2020
import android.graphics.Matrix
21+
import androidx.compose.animation.core.Animatable
22+
import androidx.compose.animation.core.AnimationSpec
23+
import androidx.compose.animation.core.spring
2124
import androidx.compose.foundation.Canvas
2225
import androidx.compose.foundation.layout.Box
2326
import androidx.compose.foundation.layout.BoxScope
@@ -42,9 +45,53 @@ import androidx.constraintlayout.core.state.*
4245
import androidx.constraintlayout.core.state.Dimension
4346
import androidx.constraintlayout.core.state.Transition
4447
import androidx.constraintlayout.core.widgets.Optimizer
48+
import kotlinx.coroutines.channels.Channel
4549
import org.intellij.lang.annotations.Language
4650
import java.util.*
4751

52+
private val defaultAnimation = spring<Float>()
53+
/**
54+
* Layout that interpolate its children layout given a set of constraints and
55+
* animates any changes to those constraints
56+
*/
57+
@Composable
58+
fun MotionLayout(
59+
constraintSet: ConstraintSet,
60+
modifier: Modifier = Modifier,
61+
animationSpec: AnimationSpec<Float> = defaultAnimation,
62+
finishedListener: (() -> Unit)? = null,
63+
content: @Composable MotionLayoutScope.() -> Unit
64+
) {
65+
var currentConstraints by remember { mutableStateOf(constraintSet) }
66+
val progress = remember { Animatable(0.0f) }
67+
val channel = remember { Channel<ConstraintSet>(Channel.CONFLATED) }
68+
69+
SideEffect {
70+
channel.trySend(constraintSet)
71+
}
72+
73+
LaunchedEffect(channel) {
74+
for (constraints in channel) {
75+
val newConstraints = channel.tryReceive().getOrNull() ?: constraints
76+
if (newConstraints != currentConstraints) {
77+
progress.snapTo(0f)
78+
progress.animateTo(1f, animationSpec)
79+
80+
currentConstraints = newConstraints
81+
finishedListener?.invoke()
82+
}
83+
}
84+
85+
}
86+
87+
MotionLayout(
88+
start = currentConstraints,
89+
end = constraintSet,
90+
progress = progress.value,
91+
modifier = modifier,
92+
content = content)
93+
}
94+
4895
/**
4996
* Layout that interpolate its children layout given two sets of constraint and
5097
* a progress (from 0 to 1)

projects/ComposeConstraintLayout/app/src/main/java/com/example/constraintlayout/MotionComposeExamples.kt

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import androidx.compose.foundation.background
66
import androidx.compose.foundation.border
77
import androidx.compose.foundation.clickable
88
import androidx.compose.foundation.gestures.Orientation
9-
import androidx.compose.foundation.gestures.detectDragGestures
109
import androidx.compose.foundation.layout.*
1110
import androidx.compose.foundation.shape.CircleShape
1211
import androidx.compose.foundation.shape.RoundedCornerShape
@@ -19,10 +18,8 @@ import androidx.compose.runtime.*
1918
import androidx.compose.ui.Modifier
2019
import androidx.compose.ui.draw.clip
2120
import androidx.compose.ui.graphics.Color
22-
import androidx.compose.ui.input.pointer.pointerInput
2321
import androidx.compose.ui.layout.layoutId
2422
import androidx.compose.ui.layout.onSizeChanged
25-
import androidx.compose.ui.platform.LocalDensity
2623
import androidx.compose.ui.text.style.TextAlign
2724
import androidx.compose.ui.tooling.preview.Preview
2825
import androidx.compose.ui.unit.dp
@@ -34,13 +31,8 @@ import java.util.*
3431
@Composable
3532
public fun MotionExample1() {
3633
var animateToEnd by remember { mutableStateOf(false) }
37-
val progress by animateFloatAsState(
38-
targetValue = if (animateToEnd) 1f else 0f,
39-
animationSpec = tween(1000)
40-
)
4134

42-
43-
var baseConstraintSetStart = """
35+
val baseConstraintSetStart = """
4436
{
4537
Variables: {
4638
angle: { start: 0},
@@ -59,7 +51,7 @@ public fun MotionExample1() {
5951
}
6052
"""
6153

62-
var baseConstraintSetEnd = """
54+
val baseConstraintSetEnd = """
6355
{
6456
Variables: {
6557
angle: { from: 0, to: 10 },
@@ -78,17 +70,16 @@ public fun MotionExample1() {
7870
}
7971
"""
8072

81-
var cs1 = ConstraintSet(baseConstraintSetStart)
82-
var cs2 = ConstraintSet(baseConstraintSetEnd)
83-
73+
val cs1 = ConstraintSet(baseConstraintSetStart)
74+
val cs2 = ConstraintSet(baseConstraintSetEnd)
8475

76+
val constraints = if (animateToEnd) cs2 else cs1
8577
Column {
8678
Button(onClick = { animateToEnd = !animateToEnd }) {
8779
Text(text = "Run")
8880
}
8981
MotionLayout(
90-
cs1, cs2,
91-
progress = progress,
82+
constraints,
9283
modifier = Modifier
9384
.fillMaxSize()
9485
.background(Color.White)

0 commit comments

Comments
 (0)