Skip to content

Compose MotionLayout JSON Syntax

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

Overview

MotionLayout in ConstraintLayout-Compose 1.0.0 only support a JSON syntax. MotionLayout in ConstraintLayout-Compose 1.1.0 support DSL & JSON syntax. The Functionality operates identically to the 2.1 MotionLayout equivalent.

Basic Sections

{
  Header:{}
  Design:{}
  Variables:{}
  ConstraintSets:{}
  Transitions: {
       // Transition Named default (special name)
      default:{
          KeyFrames:{
              KeyPositions: {}
              KeyAttributes: {}
              KeyCycles: {}
          }
      }
  }
}

Header

This allows you to export the current string for Realtime remote editing via Link

 Header:{
    exportAs: 'main screen'
 }

Design

This allows you to create widgets (currently only text and button)

Design: {
    toto: {
    type: 'button',
    text: 'plop'
    }
}

Variables

This allows you to define variables that can be used multiple times across the entire string.

Variables: {
    simple1: 8
    increment1 : { from: 90, step: 10 }
    increment2 : { from: 1, to: 36, prefix: 'h' },
    fromtag1: { tag: 'box' }
  
},
  • simple1 can be used in many places and will be replaced by 8
  • increment1 value will change (increase by 10) each time it is used (especially useful in combination with Generate: )
  • increment2 will have the values h1 to h36
  • fromtag1 will map to all ids of the views created with with a tag.

For example if composables were created like this:

  for (i in 1..36) {
                Box(modifier = Modifier
                    .layoutId("id$i", "box")
                    .background(colors[i % colors.size]))
            }

fromtag1 will be [id1,id2,...id36]

Generate

Generate section can be used to generate a collection of Constraint objects when used with variables. For example:

            {
                Variables: {
                  angle: { from: 0, step: 10 },
                  rotation: { from: 'startRotation', step: 10 },
                  distance: 100,
                  mylist: { tag: 'box' }
                },
                Generate: {
                  mylist: {
                    width: 200,
                    height: 40,
                    circular: ['parent', 'angle', 'distance'],
                    pivotX: 0.1,
                    pivotY: 0.1,
                    translationX: 225,
                    rotationZ: 'rotation'
                  }
                }
            }

This (above) will generate a collection constraints for each Composable created with the tag 'box'. The will have circular constraints (typically circular:[viewid, angle, distance]) but the angle will be different for each widget created. The first starting at 'startRotation' (which is undefined at this point) When constructing the ConstraintSet overridVariables would have to contain startRotation:

    var cs1 = ConstraintSet(baseConstraintSet, overrideVariables = "{ startRotation: 0 }")
    var cs2 = ConstraintSet(baseConstraintSet, overrideVariables = "{ startRotation: 90 }")

Constructing the ConstraintSet without oveeridVariables will result in and exception

ConstraintSets

The pattern is typically

setId1:{  // setId1 is the name of the ConstraintSet equivalent to <ConstraintSet android:id="@id/setId1" ...>
     widgetId1{ ...} // widgetId1 is the id of the widget equivalent of <Constraint android:id="@id/..." ...>
     widgetId2{ ...}
     widgetId3{ ...}
      }
setId12:{ 
     widgetId1{ ...}
     widgetId2{ ...}
     widgetId3{ ...}
      }
}

Example:

    ConstraintSets: {
                  start: {
                    a: {
                      width: 40,
                      height: 40,
                      start: ['parent', 'start', 16],
                      bottom: ['parent', 'bottom', 16]
                    }
                  },
                  end: {
                    a: {
                      width: 40,
                      height: 40,
                      end: ['parent', 'end', 16],
                      top: ['parent', 'top', 16]
                    }
                  }
                }

Constraint

Support the following key words

  • width
  • height
  • start
  • end
  • top
  • bottom
  • left
  • right
  • center
  • centerHorizontally
  • centerVertically
  • alpha
  • scaleX
  • scaleY
  • translationX
  • translationY
  • translationZ
  • pivotX
  • pivotY
  • rotationX
  • rotationY
  • rotationZ
  • visibility (= visible, invisible, gone)
  • custom - custom properties
  • motion { ... } - motion properties

MotionProperties

Within a Constraint section you can have a MotionSection The MotionSection of the starting ConstraintSet modifies a Transitions behavior

motion: {
    pathArc : 'startHorizontal',
    stagger: 3,
    quantize: [6, 'overshoot', 32]
    easing: 'overshoot'
}

pathArc

This has the Motion of the Composable move in a quarter ellipse It has 4 possible values:

  • none - travel in default monotonic spline
  • startHorizontal - indicate the it starts going horizontal arching till it ends vertical
  • startVertical - indicate the it starts going vertical arching till it ends horizontal
  • flip - indicate the it starts going vertical and alternate between horizontal and vertical at each KeyPosition

stagger

The stagger sets the order of this Composable in the staggered moves.
 To use this you add "staggered" to the transition  where 0.0 means not staggering and 1.0 means 1 at a time.
  Transitions: {
           default: {
               staggered: 0.4
           }
   }

quantize

Quantize cause the transition of this composable to happen in steps. quantize: 10 means it will make 12 jumps in motion at progress 0.0 then 0.1, 0.2, etc. till it progress hits 1.0. (Like a watch ticking)

Quantize can take a list of 3 values quantize: [steps, easing, phase]

  • steps is the number of jumps
  • easing is the shape of the jump (See easing for all options on easing)
  • phase is when to start the jumps within that 1/steps sections of the progress

for example quantize: [6, 'overshoot', 0] The jumps can be designed to be non discreet yet quantized. (like click stops on a dial) the above will move in 6 steps overshooting and bouncing back. The 0 is the phase of when the step occurs (almost always = 0)

easing

Easing specifies the rate of change of progress. This takes about 3 different forms

  • named easing curves: easeInOut, easeIn , easeOut, linear, bounce, overshoot or anticipate
  • cubic like you see in css : cubic(0.34, 1.56, 0.64, 1)
  • evenly spaced monotonic spline starting at 0 ending at 1 : spline(0,1.2,1) , spline(0, 0.05, 0.1, 0.8, 0.9, 1)

Custom Properties

For compose custom properties must be extracted and set manually. So for a typical custom property set:

custom: {
    background: '#0000FF',
    textColor: '#FFFFFF',
    textSize: 12
}

You would need to set them using the below logic.

var properties = motionProperties("a")
Text(text = "Hello", modifier = Modifier
    .layoutId(properties.value.id())
    .background(properties.value.color("background"))
    ,color = properties.value.color("textColor")
    ,fontSize = properties.value.fontSize("textSize")
)

Transitions

Transitions is a container for all transitions. Each Transition is given a name. The name "default" is special and defines the initial transition.

Transitions: {
    default: {
        from: 'start',
        to: 'end',
        pathMotionArc: 'startHorizontal',
        duration: 900
        staggered: 0.4,
        onSwipe: {
                anchor: 'box1',
                maxVelocity: 4.2,
                maxAccel: 3,
                direction: 'end',
                side: 'start',
                mode: 'velocity'
         }
        KeyFrames: {
        KeyPositions: [
            {
            target: ['a'],
            frames: [25, 50, 75],
            percentX: [0.4, 0.8, 0.1],
            percentY: [0.4, 0.8, 0.3]
            }
        ],
        KeyCycles: [
            {
                target: ['a'],
                frames: [0, 50, 100],
                period: [0 , 2 , 0],
                rotationX: [0, 45, 0],
                rotationY: [0, 45, 0], 
            }
        ]
    }
}
  • start - id of the ConstraintSet to start at
  • to - id of the ConstraintSet to end at
  • duration - the time the transition will take to happen
  • pathMotionArc - move in quarter ellipse arcs
  • staggered - objects move in as staggered fashion. Ether based on start position or stagger value
  • onSwipe - allow drag gestures to control the transition (See below)
  • KeyFrames - modify points between the transitions

The above is a transition from ConstraintSet "start" to ConstraintSet "end" paths with move in an Arc (quarter ellipse) starting horizontally. It also contains Keframes of type keyCycle and KeyPosition.

OnSwipe

(new for 1.1.0-alpha01)

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

property values

  • 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"

KeyFrames

We currently only support 3 types of keyFrames. All types of key frames will have the following:

  • target - id's of the key widgets that these key positions apply
  • frames - The position on the frames of each of the keys

For people familiar with MotionLayout in 2.x this is a more compact expression. For each target, for each frame a keyFrame is generated. i.e.

                target: ['a', 'b'],
                frames: [0, 50, 100],
                 // will generate 6 key frames

KeyPositions

  • percentWidth
  • percentHeight
  • sizePercent
  • percentX
  • percentY

KeyAttributes

  • frame - The widgets to transform
  • target - the key frames
  • curveFit - interpolate between points using linear interpolation or a monotonic spline
  • alpha - how transparent the widget should be.
  • rotationZ - post layout rotate the widget
  • rotationX - post layout rotate about a horizontal axis
  • rotationY - post layout rotate about a vertical axis
  • pivotX - post layout the point around which to rotate
  • pivotY - post layout the point around which to rotate
  • pathRotate - The angle with respect to the path of the widget to rotate
  • scaleX - post layout
  • scaleY - post layout
  • translationX - post layout move the widget to the right
  • translationY - post layout move widget down
  • translationZ - post layout move widget towards the viewer expanding its shadow

(Not implemented yet pivotTarget, easing, progress )

KeyCycles

  • offset - the center of the wave
  • phase - the phase angle of the wave in this area
  • period - the number of waves to generate in this area
  • frame - The widgets to transform
  • target - the key frames
  • curveFit - interpolate between points using linear interpolation or a monotonic spline
  • alpha - how transparent the widget should be.
  • rotationZ - post layout rotate the widget
  • rotationX - post layout rotate about a horizontal axis
  • rotationY - post layout rotate about a vertical axis
  • pivotX - post layout the point around which to rotate
  • pivotY - post layout the point around which to rotate
  • pathRotate - The angle with respect to the path of the widget to rotate
  • scaleX - post layout
  • scaleY - post layout
  • translationX - post layout move the widget to the right
  • translationY - post layout move widget down
  • translationZ - post layout move widget towards the viewer expanding its shadow

(Not implemented yet pivotTarget, easing, progress ,waveShape )