Skip to content

Introduction to MotionLayout in Compose

John Hoford edited this page Mar 20, 2023 · 8 revisions

For those new to MotionLayout.

MotionLayout's core engine allows you to interpolate between two constraintSet With Transitions and keyframes controlling the transitions.

<SideNote:
The full MotionLayout has many features (See ConstraintLayout 2.1)
The first release of MotionLayout in ConstraintLayoutCompose 1.0 had a limited subset using the json syntax.
The current 1.1.0 has DSL and JSON support 
The hope is that users will provide feedback to allow us to tailor the API to developer needs. 

MotionLayout for Compose takes on of:

  • Two ConstraintsSet (start & end), a Transition, and progress
  • A MotionScene and progress

See Introduction to ConstraintLayout for more details on ConstraintSets

fun MotionLayout(
    start: ConstraintSet,
    end: ConstraintSet,
    transition: androidx.constraintlayout.compose.Transition? = null,
    progress: Float,
    debug: EnumSet<MotionLayoutDebugFlags> = EnumSet.of(MotionLayoutDebugFlags.NONE),
    modifier: Modifier = Modifier,
    optimizationLevel: Int = Optimizer.OPTIMIZATION_STANDARD,
    crossinline content: @Composable MotionLayoutScope.() -> Unit
) {}
fun MotionLayout(
    motionScene: MotionScene,
    progress: Float,
    debug: EnumSet<MotionLayoutDebugFlags> = EnumSet.of(MotionLayoutDebugFlags.NONE),
    modifier: Modifier = Modifier,
    optimizationLevel: Int = Optimizer.OPTIMIZATION_STANDARD,
    crossinline content: @Composable() (MotionLayoutScope.() -> Unit),
) {}

Transition

e.g:

// DSL
transition = transition(name="forward", to=startState, from=nextState) {
            keyAttributes(slot0, slot1, slot2) {
                frame(50) {
                    scaleX = .3f
                    scaleY = .3f
                }
            }
        }
// JSON
transition = Transition("""
            {
              KeyFrames: {
                KeyPositions: [
                {
                   target: ['a'],
                   frames: [50],
                   percentX: [0.8],
                   percentY: [0.8]
                }
                ]
              }
            }

Details on Transitions in json

MotionScene

MotionScene only supports a JSON syntax in 1.0.0

MotionScene supports DSL & JSON in 1.1.0

@Preview(group = "motion8")
@Composable
public fun AttributesRotationXY() {
    var animateToEnd by remember { mutableStateOf(false) }

    val progress by animateFloatAsState(
        targetValue = if (animateToEnd) 1f else 0f,
        animationSpec = tween(6000)
    )
    Column {
        MotionLayout(
            modifier = Modifier
                .fillMaxWidth()
                .height(400.dp)
                .background(Color.White),
            motionScene = MotionScene("""{
                ConstraintSets: {   // all ConstraintSets
                  start: {          // constraint set id = "start"
                    a: {            // Constraint for widget id='a'
                      width: 40,
                      height: 40,
                      start: ['parent', 'start', 16],
                      bottom: ['parent', 'bottom', 16]
                    }
                  },
                  end: {         // constraint set id = "start"
                    a: {
                      width: 40,
                      height: 40,
                      //rotationZ: 390,
                      end: ['parent', 'end', 16],
                      top: ['parent', 'top', 16]
                    }
                  }
                },
                Transitions: {            // All Transitions in here 
                  default: {              // transition named 'default'
                    from: 'start',        // go from Transition "start"
                    to: 'end',            // go to Transition "end"
                    pathMotionArc: 'startHorizontal',  // move in arc 
                    KeyFrames: {          // All keyframes go here
                      KeyAttributes: [    // keyAttributes key frames go here
                        {
                          target: ['a'],              // This keyAttributes group target id "a"
                          frames: [25,50,75],         // 3 points on progress 25% , 50% and 75%
                          rotationX: [0, 45, 60],     // the rotationX angles are a spline from 0,0,45,60,0
                          rotationY: [60, 45, 0],     // the rotationX angles are a spline from 0,60,45,0,0
                        }
                      ]
                    }
                  }
                }
            }"""),
            debug = EnumSet.of(MotionLayoutDebugFlags.SHOW_ALL),
            progress = progress) {
            Box(modifier = Modifier
                .layoutId("a")
                .background(Color.Red))
        }

        Button(onClick = { animateToEnd = !animateToEnd }) {
            Text(text = "Run")
        }
    }
}

OnSwipe

(new for 1.1.0-alpha0)

OnSwipe allows you to easily drive a transition from a drag gesture. In the typical Structure of a MotionLayout

 ms =  MotionScene() {
   start = constraintSet() {
   }
   end = constraintSet() {
   }
   transition(start,end,"default") {
     onSwipe = OnSwipe(
       anchor    = box,
       direction = SwipeDirection.Right,
       side      = SwipeSide.Left,
       mode      = mode,
       onTouchUp = touchUp
   )
  }
  MotionLayout( motionSceen = ms) {...}

Adding this section allows you to control gestures with swiping.

  • anchor - The Composable you wish your finger to track
  • side - The side of the anchor Composable your finger will track
  • direction - direction of you motion
  • scale - a scale factor to magnify your motion
  • maxVelocity - The maximum speed you can make progress at (progress per second)
  • maxAccel - The maximum the system can accelerate progress changes (progress per second per second)
  • mode - Spring or velocity mode.
  • touchUp - What happens on lifting your finger
  • springMass - The mass of the Spring in spring mode
  • springStiffness - The stiffness of the spring in spring mode
  • springDamping - The damping of the spring spring mode
  • stopThreshold - The speed threshold below which the animation is ended in spring mode
  • springBoundary - what happens when the spring hits the target (0 or 1) overshoot or bounce

Spring v. Velocity mode

Spring mode simulates a spring pulling to the destination (0 or 1) Velocity mode brings you to rest at the destination

  • in under the duration time
  • keeping the velocity below maxVelocity
  • moving such that it is continuous in change of velocity (no sudden jumps in speed)
  • with a maximum acceleration

possible values

(defaults)

  • anchor : 'id'
  • side : "top", 'left', 'right', 'bottom', 'middle', 'start", 'end'
  • direction : up", "down", "left", "right", "start", "end"
  • scale : 1.0
  • maxVelocity : 4.0
  • maxAccel : 1.2
  • mode : "velocity", "spring"
  • touchUp : "autocomplete", "toStart", "toEnd", "stop", "decelerate", "decelerateComplete", "neverCompleteStart", "neverCompleteEnd"
  • springMass : 1.0
  • springStiffness : 400
  • springDamping : 10
  • stopThreshold : 0.01
  • springBoundary : "overshoot", "bounceStart", "bounceEnd", "bounceBoth"

Notes: It sometimes does not matter if you specify opposite directions (such as up or down) when you specify an anchor because anchor maps the drag to the direct motion of the anchor in that direction.