@@ -21,6 +21,9 @@ import android.os.Handler
2121import android.os.Looper
2222import android.util.Log
2323import androidx.annotation.FloatRange
24+ import androidx.compose.animation.core.Animatable
25+ import androidx.compose.animation.core.AnimationSpec
26+ import androidx.compose.animation.core.tween
2427import androidx.compose.foundation.Canvas
2528import androidx.compose.foundation.Image
2629import androidx.compose.foundation.background
@@ -63,6 +66,7 @@ import androidx.constraintlayout.core.widgets.ConstraintWidget.DimensionBehaviou
6366import androidx.constraintlayout.core.widgets.analyzer.BasicMeasure
6467import androidx.constraintlayout.core.widgets.analyzer.BasicMeasure.Measure.TRY_GIVEN_DIMENSIONS
6568import androidx.constraintlayout.core.widgets.analyzer.BasicMeasure.Measure.USE_GIVEN_DIMENSIONS
69+ import kotlinx.coroutines.channels.Channel
6670import org.intellij.lang.annotations.Language
6771import java.lang.StringBuilder
6872import java.util.*
@@ -209,57 +213,106 @@ private class ConstraintSetForInlineDsl(
209213 *
210214 * Example usage:
211215 * @sample androidx.compose.foundation.layout.samples.DemoConstraintSet
216+ *
217+ * When recomposed with different constraintsets, you can use the animateChanges parameter
218+ * to animate the layout changes (animationSpec and finishedAnimationListener attributes can
219+ * also be useful in this mode). This is only intended for basic transitions, if more control
220+ * is needed, we recommend using MotionLayout instead.
212221 */
213222@Suppress(" NOTHING_TO_INLINE" )
214223@Composable
215224inline fun ConstraintLayout (
216225 constraintSet : ConstraintSet ,
217226 modifier : Modifier = Modifier ,
218227 optimizationLevel : Int = Optimizer .OPTIMIZATION_STANDARD ,
228+ animateChanges : Boolean = false,
229+ animationSpec : AnimationSpec <Float > = tween<Float >(),
230+ noinline finishedAnimationListener : (() -> Unit )? = null,
219231 noinline content : @Composable () -> Unit
220232) {
221- val needsUpdate = remember {
222- mutableStateOf(0L )
223- }
233+ if (animateChanges) {
234+ var startConstraint by remember { mutableStateOf(constraintSet) }
235+ var endConstraint by remember { mutableStateOf(constraintSet) }
236+ val progress = remember { Animatable (0.0f ) }
237+ val channel = remember { Channel <ConstraintSet >(Channel .CONFLATED ) }
238+ val direction = remember { mutableStateOf(1 ) }
239+
240+ SideEffect {
241+ channel.trySend(constraintSet)
242+ }
243+
244+ LaunchedEffect (channel) {
245+ for (constraints in channel) {
246+ val newConstraints = channel.tryReceive().getOrNull() ? : constraints
247+ val currentConstraints = if (direction.value == 1 ) startConstraint else endConstraint
248+ if (newConstraints != currentConstraints) {
249+ if (direction.value == 1 ) {
250+ endConstraint = newConstraints
251+ } else {
252+ startConstraint = newConstraints
253+ }
254+ progress.animateTo(direction.value.toFloat(), animationSpec)
255+ direction.value = if (direction.value == 1 ) 0 else 1
256+ finishedAnimationListener?.invoke()
257+ }
258+ }
259+ }
224260
225- val measurer = remember { Measurer () }
226- val measurePolicy = rememberConstraintLayoutMeasurePolicy(optimizationLevel, needsUpdate, constraintSet, measurer)
227- if (constraintSet is EditableJSONLayout ) {
228- constraintSet.setUpdateFlag(needsUpdate)
229- }
230- if (constraintSet is JSONConstraintSet ) {
231- measurer.addLayoutInformationReceiver(constraintSet)
261+ MotionLayout (
262+ start = startConstraint,
263+ end = endConstraint,
264+ progress = progress.value,
265+ modifier = modifier,
266+ content = { content() })
232267 } else {
233- measurer.addLayoutInformationReceiver(null )
234- }
268+ val needsUpdate = remember {
269+ mutableStateOf(0L )
270+ }
235271
236- val forcedScaleFactor = measurer.forcedScaleFactor
237- if (! forcedScaleFactor.isNaN()) {
238- var mod = modifier.scale(measurer.forcedScaleFactor)
239- Box {
272+ val measurer = remember { Measurer () }
273+ val measurePolicy = rememberConstraintLayoutMeasurePolicy(
274+ optimizationLevel,
275+ needsUpdate,
276+ constraintSet,
277+ measurer
278+ )
279+ if (constraintSet is EditableJSONLayout ) {
280+ constraintSet.setUpdateFlag(needsUpdate)
281+ }
282+ if (constraintSet is JSONConstraintSet ) {
283+ measurer.addLayoutInformationReceiver(constraintSet)
284+ } else {
285+ measurer.addLayoutInformationReceiver(null )
286+ }
287+
288+ val forcedScaleFactor = measurer.forcedScaleFactor
289+ if (! forcedScaleFactor.isNaN()) {
290+ var mod = modifier.scale(measurer.forcedScaleFactor)
291+ Box {
292+ @Suppress(" DEPRECATION" )
293+ MultiMeasureLayout (
294+ modifier = mod.semantics { designInfoProvider = measurer },
295+ measurePolicy = measurePolicy,
296+ content = {
297+ measurer.createDesignElements()
298+ content()
299+ }
300+ )
301+ with (measurer) {
302+ drawDebugBounds(forcedScaleFactor)
303+ }
304+ }
305+ } else {
240306 @Suppress(" DEPRECATION" )
241307 MultiMeasureLayout (
242- modifier = mod .semantics { designInfoProvider = measurer },
308+ modifier = modifier .semantics { designInfoProvider = measurer },
243309 measurePolicy = measurePolicy,
244310 content = {
245311 measurer.createDesignElements()
246312 content()
247313 }
248314 )
249- with (measurer) {
250- drawDebugBounds(forcedScaleFactor)
251- }
252315 }
253- } else {
254- @Suppress(" DEPRECATION" )
255- MultiMeasureLayout (
256- modifier = modifier.semantics { designInfoProvider = measurer },
257- measurePolicy = measurePolicy,
258- content = {
259- measurer.createDesignElements()
260- content()
261- }
262- )
263316 }
264317}
265318
0 commit comments