Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Advanced & Practical MotionLayout - droidcon SF...

Advanced & Practical MotionLayout - droidcon SF 2019

ConstraintLayout 2.0 has been steadily releasing new features that give Android developers far better animation tools than we've ever had before. In 2019 we can no longer avoid designing and developing applications that have beautiful motion, that should be a standard experience that users expect.

We will cover how to practically use MotionLayout to deliver on complex design asks. This will cover reusing Constraints between multiple Transitions, how to apply multiple effects at different points in a Transition, switching between Transitions programmatically or automatically, how to link the progress of MotionLayout to other widgets, and especially how complex interactions with a RecyclerView can be straightforward. Whether you're a senior UI developer who knows the intricacies of Animators or someone who has shied away from animation on Android, this talk should give you working knowledge of how MotionLayout can make a difference in your app and team.

Jason Pearson

November 26, 2019
Tweet

More Decks by Jason Pearson

Other Decks in Programming

Transcript

  1. src/ ├── java/ └── res/ ├── drawable ├── layout │

    └── ├── mipmap └── values Creating our first MotionScene activity_main.xml
  2. Creating our first MotionScene src/ ├── java/ └── res/ ├──

    drawable ├── layout │ └── ├── mipmap ├── values └── xml activity_main.xml
  3. src/ ├── java/ └── res/ ├── drawable ├── layout │

    └── ├── mipmap ├── values └── xml └── motion_scene.xml activity_main.xml Creating our first MotionScene xml motion_scene
  4. @xml/motion_scene <?xml version="1.0" encoding="utf-8"?> <MotionScene xmlns:android="…" xmlns:app="…" > </MotionScene> @layout/activity_main

    <?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.motion.widget.MotionLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" app:layoutDescription="…" /> Creating our first MotionScene @layout/activity_main
  5. @xml/motion_scene <?xml version="1.0" encoding="utf-8"?> <MotionScene xmlns:android="…" xmlns:app="…" > </MotionScene> @layout/activity_main

    <?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.motion.widget.MotionLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" app:layoutDescription="…" /> Creating our first MotionScene @layout/activity_main
  6. @xml/motion_scene <?xml version="1.0" encoding="utf-8"?> <MotionScene xmlns:android="…" xmlns:app="…" > </MotionScene> @layout/activity_main

    <?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.motion.widget.MotionLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" app:layoutDescription="@xml/motion_scene" /> Creating our first MotionScene @layout/activity_main
  7. <MotionScene> <ConstraintSet android:id="@+id/start"> <Constraint android:id="@+id/square" android:layout_width="100dp" android:layout_height="100dp" app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent"

    /> </ConstraintSet> <ConstraintSet android:id="@+id/end"> </ConstraintSet> </MotionScene> Adding second ConstraintSet @xml/motion_scene motion_scene.xml ConstraintSet start ConstraintSet end
  8. <MotionScene> <ConstraintSet android:id="@+id/start"> <Constraint android:id="@+id/square" android:layout_width="100dp" android:layout_height="100dp" app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent"

    /> </ConstraintSet> <ConstraintSet android:id="@+id/end"> <Constraint android:id="@+id/square" android:layout_width="100dp" android:layout_height="100dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" /> </ConstraintSet> </MotionScene> Adding second ConstraintSet @xml/motion_scene motion_scene.xml ConstraintSet start ConstraintSet end
  9. <MotionScene> <ConstraintSet android:id="@+id/start"> <Constraint android:id="@+id/square" android:layout_width="100dp" android:layout_height="100dp" app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent"

    /> </ConstraintSet> <ConstraintSet android:id="@+id/end"> <Constraint android:id="@+id/square" android:layout_width="100dp" android:layout_height="100dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" /> </ConstraintSet> </MotionScene> Adding second ConstraintSet @xml/motion_scene motion_scene.xml ConstraintSet start ConstraintSet end
  10. @xml/motion_scene <MotionScene> <Transition /> <ConstraintSet android:id="@+id/start"> <Constraint android:id="@+id/square" android:layout_width="100dp" android:layout_height="100dp"

    app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" /> </ConstraintSet> <ConstraintSet android:id="@+id/end"> <Constraint android:id="@+id/square" android:layout_width="100dp" android:layout_height="100dp" app:layout_constraintBottom_toBottomOf="parent" Adding first Transition @xml/motion_scene motion_scene.xml ConstraintSet start ConstraintSet end Transition
  11. @xml/motion_scene <MotionScene> <Transition > </Transition> <ConstraintSet android:id="@+id/start"> <Constraint android:id="@+id/square" android:layout_width="100dp"

    android:layout_height="100dp" app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" /> </ConstraintSet> <ConstraintSet android:id="@+id/end"> <Constraint android:id="@+id/square" android:layout_width="100dp" Adding first Transition @xml/motion_scene motion_scene.xml ConstraintSet start ConstraintSet end Transition
  12. @xml/motion_scene <MotionScene> <Transition app:constraintSetStart="@id/start" > </Transition> <ConstraintSet android:id="@+id/start"> <Constraint android:id="@+id/square"

    android:layout_width="100dp" android:layout_height="100dp" app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" /> </ConstraintSet> Adding first Transition @xml/motion_scene motion_scene.xml ConstraintSet start ConstraintSet end Transition start
  13. @xml/motion_scene <MotionScene> <Transition app:constraintSetStart="@id/start" app:constraintSetEnd="@id/end" > </Transition> <ConstraintSet android:id="@+id/start"> <Constraint

    android:id="@+id/square" android:layout_width="100dp" android:layout_height="100dp" app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" /> </ConstraintSet> Adding first Transition @xml/motion_scene motion_scene.xml ConstraintSet start ConstraintSet end Transition start end
  14. @xml/motion_scene <MotionScene> <Transition app:constraintSetStart="@id/start" app:constraintSetEnd="@id/end" > <OnClick /> </Transition> <ConstraintSet

    android:id="@+id/start"> <Constraint android:id="@+id/square" android:layout_width="100dp" android:layout_height="100dp" app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" Adding first Transition @xml/motion_scene motion_scene.xml ConstraintSet start ConstraintSet end Transition OnClick start end
  15. @xml/motion_scene <MotionScene> <Transition app:constraintSetStart="@id/start" app:constraintSetEnd="@id/end" > <OnClick app:targetId="@id/square" /> </Transition>

    <ConstraintSet android:id="@+id/start"> <Constraint android:id="@+id/square" android:layout_width="100dp" android:layout_height="100dp" app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent" Adding first Transition @xml/motion_scene motion_scene.xml ConstraintSet start ConstraintSet end Transition OnClick square start end
  16. @xml/motion_scene <MotionScene> <Transition app:constraintSetStart="@id/start" app:constraintSetEnd="@id/end" > <OnClick app:targetId="@id/square" app:clickAction="transitionToEnd" />

    </Transition> <ConstraintSet android:id="@+id/start"> <Constraint android:id="@+id/square" android:layout_width="100dp" android:layout_height="100dp" app:layout_constraintTop_toTopOf="parent" Adding first Transition @xml/motion_scene motion_scene.xml ConstraintSet start ConstraintSet end Transition OnClick square start end
  17. @xml/motion_scene <MotionScene> <Transition app:constraintSetStart="@id/start" app:constraintSetEnd="@id/end" > <OnClick app:targetId="@id/square" app:clickAction="transitionToEnd" />

    </Transition> <ConstraintSet android:id="@+id/start"> <Constraint android:id="@+id/square" android:layout_width="100dp" android:layout_height="100dp" app:layout_constraintTop_toTopOf="parent" Adding first Transition @xml/motion_scene motion_scene.xml ConstraintSet start ConstraintSet end Transition OnClick square start end
  18. @xml/motion_scene <MotionScene> <Transition app:constraintSetStart="@id/start" app:constraintSetEnd="@id/end" > <OnClick app:targetId="@id/square" app:clickAction="transitionToEnd" />

    </Transition> <ConstraintSet android:id="@+id/start" … /> <ConstraintSet android:id="@+id/end" … /> </MotionScene> @xml/motion_scene motion_scene.xml ConstraintSet start ConstraintSet end Transition OnClick square start end OnClick
  19. motion_scene_with_click.xml motion_scene_with_swipe.xml OnSwipe OnClick ConstraintSet start ConstraintSet end Transition OnClick

    square start end Transition OnSwipe square start end start end ConstraintSet start ConstraintSet end
  20. @xml/motion_scene_with_swipe <MotionScene> <Transition app:constraintSetStart="@id/start" app:constraintSetEnd="@id/end" > <OnSwipe app:touchAnchorId="@id/square" app:touchAnchorSide="bottom" app:dragDirection="dragDown"

    /> </Transition> <ConstraintSet android:id="@+id/start" … /> <ConstraintSet android:id="@+id/end" … /> </MotionScene> @xml/motion_scene_with_click <MotionScene> <Transition app:constraintSetStart="@id/start" app:constraintSetEnd="@id/end" > <OnClick app:targetId="@id/square" app:clickAction="transitionToEnd" /> </Transition> <ConstraintSet android:id="@+id/start" … /> <ConstraintSet android:id="@+id/end" … /> </MotionScene> @xml/motion_scene_with_click @xml/motion_scene_with_swipe OnSwipe OnClick
  21. @xml/motion_scene_with_swipe <MotionScene> <Transition app:constraintSetStart="@id/start" app:constraintSetEnd="@id/end" > <OnSwipe app:touchAnchorId="@id/square" app:touchAnchorSide="bottom" app:dragDirection="dragDown"

    /> </Transition> <ConstraintSet android:id="@+id/start" … /> <ConstraintSet android:id="@+id/end" … /> </MotionScene> @xml/motion_scene_with_click <MotionScene> <Transition app:constraintSetStart="@id/start" app:constraintSetEnd="@id/end" > <OnClick app:targetId="@id/square" app:clickAction="transitionToEnd" /> </Transition> <ConstraintSet android:id="@+id/start" … /> <ConstraintSet android:id="@+id/end" … /> </MotionScene> OnSwipe OnClick @xml/motion_scene_with_click @xml/motion_scene_with_swipe
  22. @xml/motion_scene <MotionScene> <Transition app:constraintSetStart="@id/start" app:constraintSetEnd="@id/end" app:motionInterpolator="easeInOut" > <OnClick-…-/> </Transition> <ConstraintSet-android:id="@+id/start">

    <Constraint-android:id="@id/square"> <Layout-…-/> <Motion-app:transitionEasing="standard"> </Constraint> <Constraint-android:id="@id/otherSquare"> @xml/motion_scene Interpolation
  23. Adding another state swiping_scene.xml ConstraintSet start ConstraintSet end ConstraintSet middle

    Transition swipe_a OnSwipe square start middle Transition swipe_b OnSwipe square middle end
  24. <ConstraintSet android:id="@+id/start" > <Constraint android:id="@id/square" > <Layout android:layout_width="100dp" android:layout_height="100dp" app:layout_constraintTop_toTopOf="parent"

    app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" > </Constraint> </ConstraintSet> Sub Elements
  25. <ConstraintSet android:id="@+id/start" > <Constraint android:id="@id/square" > <Layout /> <PropertySet />

    <Transform /> <Motion /> <CustomAttribute /> </Constraint> </ConstraintSet> Sub Elements
  26. <ConstraintSet android:id="@+id/start" > <Constraint android:id="@id/sample_text" > <CustomAttribute app:attributeName="text" app:customStringValue="Hi #DCSF2019!

    " /> </Constraint> </ConstraintSet> <ConstraintSet android:id="@+id/end" > <Constraint android:id="@id/sample_text" > <CustomAttribute app:attributeName="text" app:customStringValue=" MotionLayout " /> </Constraint> </ConstraintSet> Changing Text
  27. <ConstraintSet android:id="@+id/start" > <Constraint android:id="@id/sample_text" > <CustomAttribute app:attributeName="text" app:customStringValue="Hi #DCSF2019!

    " /> </Constraint> </ConstraintSet> <ConstraintSet android:id="@+id/end" > <Constraint android:id="@id/sample_text" > <CustomAttribute app:attributeName="text" app:customStringValue=" MotionLayout " /> </Constraint> </ConstraintSet> Changing Text
  28. @xml/motion_scene <MotionScene> <Transition app:constraintSetStart="@id/start" app:constraintSetEnd="@id/end" app:motionInterpolator="easeInOut" > <OnClick-…-/> </Transition> <ConstraintSet-android:id="@+id/start">

    <Constraint-android:id="@id/square"> <Layout-…-/> <Motion-app:transitionEasing="standard"> </Constraint> <Constraint-android:id="@id/otherSquare"> @xml/motion_scene Sub Elements :-Motion
  29. <Transition app:constraintSetStart="@id/start" app:constraintSetEnd="@id/end" app:motionInterpolator="easeInOut" > <OnClick-…-/> </Transition> <ConstraintSet-android:id="@+id/start"> <Constraint-android:id="@id/square"> <Layout-…-/>

    <Motion-app:transitionEasing="standard"> </Constraint> </ConstraintSet> <ConstraintSet android:id="@+id/end"-…-/> </MotionScene> Sub Elements :-Motion @xml/motion_scene
  30. Sub Elements: Transform swiping_scene.xml ConstraintSet start ConstraintSet end Transition toggle

    OnClick square start end KeyFrameSet Transform rotate 360 rotateX 360
  31. Sub Elements: Transform swiping_scene.xml ConstraintSet start ConstraintSet end Transition toggle

    OnClick square start end KeyFrameSet KeyFrame 33 KeyFrame 66 translateX -64dp translateX 64dp Transform rotate 360 rotateX 360
  32. KeyFrameSet Sub Elements: Transform swiping_scene.xml ConstraintSet start ConstraintSet end Transition

    toggle OnClick square start end KeyFrameSet KeyFrame 33 KeyFrame 66 translateX -64dp translateX 64dp Transform rotate 360 rotateX 360
  33. KeyFrameSets KeyFrameSet 100 0 25 50 75 KeyFrames let you

    specify a change at a point in time during transition
  34. KeyFrameSets KeyFrameSet 100 0 25 50 75 KeyFrames let you

    specify a change at a point in time during transition
  35. KeyFrameSets KeyFrameSet 100 0 25 50 75 KeyFrames let you

    specify a change at a point in time during transition
  36. KeyFrameSets KeyFrameSet 100 0 25 50 75 KeyFrames let you

    specify a change at a point in time during transition
  37. KeyFrameSets KeyFrameSet 100 0 25 50 75 KeyFrames let you

    specify a change at a point in time during transition
  38. KeyFrameSets KeyFrameSet 100 0 25 50 75 KeyFrames let you

    specify a change at a point in time during transition
  39. KeyFrameSets KeyFrameSet 100 0 25 50 75 KeyFrames let you

    specify a change at a point in time during transition
  40. KeyFrameSets KeyFrameSet 100 0 25 50 75 KeyFrames let you

    specify a change at a point in time during transition
  41. KeyFrameSets: Key Attribute <Transition> <OnClick-/> <KeyFrameSet> <KeyAttribute app:motionTarget="@id/square" app:framePosition="33" android:translationX="-64dp"

    /> <KeyAttribute app:motionTarget="@id/square" app:framePosition="66" android:translationX="64dp" /> </KeyFrameSet> </Transition>
  42. Swiping Items @layout/activity_swipe_items <androidx.recyclerview.widget.RecyclerView android:id="@+id/email_list" android:layout_width="match_parent" @layout/email_item <androidx.constraintlayout.motion.widget.MotionLayout app:layoutDescription="@xml/swipe_item_scene" >

    <View android:id="@+id/archive_button" …/> <View android:id="@+id/foreground" …/> <ImageView android:id="@+id/thumbnail" …/> <TextView android:id="@+id/name" …/> </androidx.constraintlayout.motion.widget.MotionLayout> @xml/swipe_item_scene
  43. Swiping Items <MotionScene> <Transition app:constraintSetStart="@id/start" app:constraintSetEnd="@id/end" > <OnSwipe app:touchAnchorId="@id/foreground" app:touchAnchorSide="right"

    app:dragDirection="dragLeft" app:onTouchUp="decelerateAndComplete" /> </Transition> <ConstraintSet android:id="@+id/start"> <Constraint android:id="@+id/foreground"> <Transform android:translationY="0dp" /> @xml/swipe_item_scene
  44. Swiping Items <MotionScene> <Transition app:constraintSetStart="@id/start" app:constraintSetEnd="@id/end" > <OnSwipe app:touchAnchorId="@id/foreground" app:touchAnchorSide="right"

    app:dragDirection="dragLeft" app:onTouchUp="decelerateAndComplete" /> </Transition> <ConstraintSet android:id="@+id/start"> <Constraint android:id="@+id/foreground"> <Transform android:translationY="0dp" /> @xml/swipe_item_scene
  45. Swiping Items <MotionScene> <Transition app:constraintSetStart="@id/start" app:constraintSetEnd="@id/end" > <OnSwipe app:touchAnchorId="@id/foreground" app:touchAnchorSide="right"

    app:dragDirection="dragLeft" app:onTouchUp="decelerateAndComplete" /> </Transition> <ConstraintSet android:id="@+id/start"> <Constraint android:id="@+id/foreground"> <Transform android:translationY="0dp" /> @xml/swipe_item_scene
  46. Swiping Items There is no change in horizontal position of

    any side in the foreground view, therefore MotionLayout cannot determine how to interpolate between the two states
  47. Swiping Items <OnSwipe app:dragDirection="dragLeft" /> Or do not specify a

    touchAnchor and then there is no need to create clever hidden views.
  48. Collapsing Toolbar @xml/collapse_on_scroll_scene <MotionScene> <Transition app:constraintSetStart="@id/start" app:constraintSetEnd="@id/end" > <OnSwipe app:targetId="@id/header_layout"

    app:touchAnchorSide="bottom" app:dragDirection="dragUp" /> </Transition> <ConstraintSet android:id="@+id/start" … /> <ConstraintSet android:id="@+id/end" … /> </MotionScene>
  49. <MotionScene> <Transition …/> <ConstraintSet android:id="@+id/start"> <Constraint android:id="@id/header_layout" android:layout_width="match_parent" android:layout_height="128dp" app:layout_constraintTop_toTopOf="parent"/>

    <Constraint android:id="@id/email_list" android:layout_width="match_parent" android:layout_height="0dp" app:layout_constraintTop_toBottomOf="@id/header_layout" app:layout_constraintBottom_toBottomOf="parent"/> </ConstraintSet> <ConstraintSet android:id="@+id/end" app:deriveConstraintsFrom="@id/start"> <Constraint android:id="@id/header_layout" Collapsing Toolbar @xml/collapse_on_scroll_scene
  50. android:layout_height="128dp" app:layout_constraintTop_toTopOf="parent"/> <Constraint android:id="@id/todo_list" android:layout_width="match_parent" android:layout_height="0dp" app:layout_constraintTop_toBottomOf="@id/header_layout" app:layout_constraintBottom_toBottomOf="parent"/> </ConstraintSet> <ConstraintSet

    android:id="@+id/end" app:deriveConstraintsFrom="@id/start"> <Constraint android:id="@id/header_layout" android:layout_width="match_parent" android:layout_height="80dp" app:layout_constraintTop_toTopOf="parent" /> </ConstraintSet> </MotionScene> Collapsing Toolbar @xml/collapse_on_scroll_scene
  51. android:layout_height="128dp" app:layout_constraintTop_toTopOf="parent"/> <Constraint android:id="@id/todo_list" android:layout_width="match_parent" android:layout_height="0dp" app:layout_constraintTop_toBottomOf="@id/header_layout" app:layout_constraintBottom_toBottomOf="parent"/> </ConstraintSet> <ConstraintSet

    android:id="@+id/end" app:deriveConstraintsFrom="@id/start"> <Constraint android:id="@id/header_layout" android:layout_width="match_parent" android:layout_height="80dp" app:layout_constraintTop_toTopOf="parent" /> </ConstraintSet> </MotionScene> Collapsing Toolbar - Derived Constraints @xml/collapse_on_scroll_scene
  52. <MotionScene> <Transition app:constraintSetStart="@id/start" app:constraintSetEnd="@id/end" /> <ConstraintSet android:id="@+id/start"> <Constraint android:id="@id/header_title"> </Constraint>

    </ConstraintSet> <ConstraintSet android:id="@+id/end"> <Constraint android:id="@id/header_title"> </Constraint> </ConstraintSet> </MotionScene> Nested MotionLayout Header - Scaling Text @xml/collapsing_header_scene
  53. <MotionScene> <Transition …/> <ConstraintSet android:id="@+id/start"> <Constraint android:id="@id/header_title"> </Constraint> </ConstraintSet> <ConstraintSet

    android:id="@+id/end"> <Constraint android:id="@id/header_title"> </Constraint> </ConstraintSet> </MotionScene> @xml/collapsing_header_scene Nested MotionLayout Header - Scaling Text
  54. Scaling Text <ConstraintSet android:id="@+id/start"> <Constraint android:id="@id/sample_text"> <CustomAttribute app:attributeName="textSize" app:customFloatValue="64" />

    </Constraint> </ConstraintSet> <ConstraintSet android:id="@+id/end"> <Constraint android:id="@id/sample_text"> <CustomAttribute app:attributeName="textSize" app:customFloatValue="32" /> </Constraint> </ConstraintSet>
  55. <ConstraintSet android:id="@+id/start" > <Constraint android:id="@id/sample_text" > <CustomAttribute app:attributeName="textSize" app:customFloatValue="64" />

    </Constraint> </ConstraintSet> <ConstraintSet android:id="@+id/end" > <Constraint android:id="@id/sample_text" > <CustomAttribute app:attributeName="textSize" app:customFloatValue="32" /> </Constraint> </ConstraintSet> Scaling Text
  56. <ConstraintSet android:id="@+id/start" > <Constraint android:id="@id/sample_t <Transform android:scaleX="1" android:scaleX="1" /> </Constraint>

    </ConstraintSet> <ConstraintSet android:id="@+id/end" > <Constraint android:id="@id/sample_t <Transform android:scaleX="0.5" android:scaleX="0.5" /> </Constraint> </ConstraintSet> <ConstraintSet android:id="@+id/start" > <Constraint android:id="@id/sample_text" > <CustomAttribute app:attributeName="textSize" app:customFloatValue="64" /> </Constraint> </ConstraintSet> <ConstraintSet android:id="@+id/end" > <Constraint android:id="@id/sample_text" > <CustomAttribute app:attributeName="textSize" app:customFloatValue="32" /> </Constraint> </ConstraintSet> Scaling Text
  57. <MotionScene> <Transition …/> <ConstraintSet android:id="@+id/start"> <Constraint android:id="@id/header_title"> <Transform android:scaleX="1.0" android:scaleY="1.0"

    /> </Constraint> </ConstraintSet> <ConstraintSet android:id="@+id/end"> <Constraint android:id="@id/header_title"> <Transform android:scaleX="0.7" android:scaleY="0.7" /> </Constraint> </ConstraintSet> </MotionScene> Nested MotionLayout Header @xml/collapsing_header_scene
  58. <MotionScene> <Transition …/> <ConstraintSet android:id="@+id/start"> <Constraint android:id="@id/header_layout"> <Layout android:layout_width="match_parent" android:layout_height="128dp"

    app:layout_constraintTop_toTopOf="parent" /> <PropertySet app:motionProgress="0" /> <Transform android:elevation="0dp" /> </Constraint> <Constraint android:id="@id/todo_list"> <Layout android:layout_width="match_parent" android:layout_height="0dp" Nested MotionLayout @xml/collapse_on_scroll_scene
  59. <MotionScene> <Transition …/> <ConstraintSet android:id="@+id/start"> <Constraint android:id="@id/header_layout"> <Layout android:layout_width="match_parent" android:layout_height="128dp"

    app:layout_constraintTop_toTopOf="parent" /> <PropertySet app:motionProgress="0" /> <Transform android:elevation="0dp" /> </Constraint> <Constraint android:id="@id/todo_list"> <Layout android:layout_width="match_parent" android:layout_height="0dp" app:layout_constraintTop_toBottomOf="@id/header_layout" app:layout_constraintBottom_toBottomOf="parent" /> </Constraint> </ConstraintSet> <ConstraintSet android:id="@+id/end" Nested MotionLayout @xml/collapse_on_scroll_scene
  60. <Layout android:layout_width="match_parent" android:layout_height="0dp" app:layout_constraintTop_toBottomOf="@id/header_layout" app:layout_constraintBottom_toBottomOf="parent" /> </Constraint> </ConstraintSet> <ConstraintSet android:id="@+id/end"

    app:deriveConstraintsFrom="@id/start"> <Constraint android:id="@id/header_layout"> <Layout android:layout_width="match_parent" android:layout_height="80dp" app:layout_constraintTop_toTopOf="parent" /> <PropertySet app:motionProgress="1"/> <Transform android:elevation="4dp" /> </Constraint> </ConstraintSet> </MotionScene> Nested MotionLayout @xml/collapse_on_scroll_scene
  61. </Constraint> </ConstraintSet> <ConstraintSet android:id="@+id/end" app:deriveConstraintsFrom="@id/start"> <Constraint android:id="@id/header_layout"> <Layout android:layout_width="match_parent" android:layout_height="80dp"

    app:layout_constraintTop_toTopOf="parent" /> <PropertySet app:motionProgress="1"/> <Transform android:elevation="4dp" /> </Constraint> </ConstraintSet> </MotionScene> Nested MotionLayout @xml/collapse_on_scroll_scene
  62. </ConstraintSet> <ConstraintSet android:id="@+id/end" app:deriveConstraintsFrom="@id/start"> <Constraint android:id="@id/header_layout"> <Layout android:layout_width="match_parent" android:layout_height="80dp" app:layout_constraintTop_toTopOf="parent"

    /> <PropertySet app:motionProgress="1"/> <Transform android:elevation="4dp" /> </Constraint> </ConstraintSet> </MotionScene> Nested MotionLayout @xml/collapse_on_scroll_scene
  63. <MotionScene> <Transition app:constraintSetStart="@id/start" app:constraintSetEnd="@id/end" /> <ConstraintSet android:id="@+id/start"> <Constraint android:id="@id/header_title"> <Layout

    android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="16dp" android:layout_marginBottom="16dp" app:layout_constraintStart_toStartOf="parent" app:layout_constraintBottom_toBottomOf="parent"/> </Constraint> </ConstraintSet> <ConstraintSet android:id="@+id/end" app:deriveConstraintsFrom="@id/start"> <Constraint android:id="@id/header_title"> <Transform android:scaleX="0.7" android:scaleY="0.7" Changing Text in MotionLayout @xml/collapsing_header_scene
  64. tionScene> <Transition app:constraintSetStart="@id/start" app:constraintSetEnd="@id/end" /> <ConstraintSet android:id="@+id/start"> <Constraint android:id="@id/header_title"> <Layout

    android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="16dp" android:layout_marginBottom="16dp" app:layout_constraintStart_toStartOf="parent" app:layout_constraintBottom_toBottomOf="parent"/> </Constraint> </ConstraintSet> <ConstraintSet android:id="@+id/end" app:deriveConstraintsFrom="@id/start"> <Constraint android:id="@id/header_title"> Changing Text in MotionLayout @xml/collapsing_header_scene
  65. <Transition app:constraintSetStart="@id/start" app:constraintSetEnd="@id/end" /> <ConstraintSet android:id="@+id/start"> <Constraint android:id="@id/header_title"> <Layout android:layout_width="wrap_content"

    android:layout_height="wrap_content" android:layout_marginStart="16dp" android:layout_marginBottom="16dp" app:layout_constraintStart_toStartOf="parent" app:layout_constraintBottom_toBottomOf="parent"/> </Constraint> </ConstraintSet> <ConstraintSet android:id="@+id/end" app:deriveConstraintsFrom="@id/start"> <Constraint android:id="@id/header_title"> <Transform android:scaleX="0.7" Changing Text in MotionLayout @xml/collapsing_header_scene
  66. <Transition app:constraintSetStart="@id/start" app:constraintSetEnd="@id/end" /> <ConstraintSet android:id="@+id/start"> <Constraint android:id="@id/header_title"> <Layout android:layout_width="match_parent"

    android:layout_height="wrap_content" android:layout_marginStart="16dp" android:layout_marginBottom="16dp" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintBottom_toBottomOf="parent"/> </Constraint> </ConstraintSet> <ConstraintSet android:id="@+id/end" app:deriveConstraintsFrom="@id/start"> <Constraint android:id="@id/header_title"> <Transform Changing Text in MotionLayout @xml/collapsing_header_scene As a bonus we can delete constraints when a view is using match_parent
  67. <Transition app:constraintSetStart="@id/start" app:constraintSetEnd="@id/end" /> <ConstraintSet android:id="@+id/start"> <Constraint android:id="@id/header_title"> <Layout android:layout_width="match_parent"

    android:layout_height="wrap_content" android:layout_marginStart="16dp" android:layout_marginBottom="16dp" app:layout_constraintBottom_toBottomOf="parent"/> </Constraint> </ConstraintSet> <ConstraintSet android:id="@+id/end" app:deriveConstraintsFrom="@id/start"> <Constraint android:id="@id/header_title"> <Transform android:scaleX="0.7" android:scaleY="0.7" Changing Text in MotionLayout @xml/collapsing_header_scene As a bonus we can delete constraints when a view is using match_parent
  68. <ConstraintSet android:id="@+id/start"> <Constraint android:id="@+id/thumbnail"> <PropertySet android:alpha="1" /> </Constraint> </ConstraintSet> <ConstraintSet

    android:id="@+id/end"> <Constraint android:id="@+id/thumbnail"> <PropertySet android:alpha="0" /> </Constraint> <Constraint android:id="@id/header_title"> <Transform android:scaleX="0.7" android:scaleY="0.7" /> </Constraint> </ConstraintSet> @xml/collapsing_header_scene Visibility
  69. <ConstraintSet android:id="@+id/start"> <Constraint android:id="@+id/thumbnail"> <PropertySet android:alpha="1" /> </Constraint> </ConstraintSet> <ConstraintSet

    android:id="@+id/end"> <Constraint android:id="@+id/thumbnail"> <PropertySet android:alpha="0" /> </Constraint> <Constraint android:id="@id/header_title"> <Transform android:scaleX="0.7" android:scaleY="0.7" /> </Constraint> </ConstraintSet> @xml/collapsing_header_scene Visibility
  70. <ConstraintSet android:id="@+id/start"> <Constraint android:id="@+id/thumbnail"> <PropertySet app:visibilityMode="ignore" android:alpha="1" /> </Constraint> </ConstraintSet>

    <ConstraintSet android:id="@+id/end"> <Constraint android:id="@+id/thumbnail"> <PropertySet app:visibilityMode="ignore" android:alpha="0" /> </Constraint> <Constraint android:id="@id/header_title"> <Transform android:scaleX="0.7" android:scaleY="0.7" /> </Constraint> </ConstraintSet> @xml/collapsing_header_scene Visibility
  71. Changing Constraints Programmatically • MotionLayout is just an extension of

    ConstraintLayout • After making changes to a ConstraintSet we must call motionLayout.updateState(constraintSetId, constraintSet) motionLayout.setTransition(start, end) motionLayout.transitionToEnd()
  72. constraintSet.constrainWidth(viewId, boundaries.width) constraintSet.constrainHeight(viewId, boundaries.height) constraintSet.connect(viewId, TOP, R.id.boundaries, TOP, 0) constraintSet.connect(viewId,

    START, R.id.boundaries, START, 0) constraintSet.connect(viewId, END, R.id.boundaries, END, 0) constraintSet.connect(viewId, BOTTOM, R.id.boundaries, BOTTOM, 0) constraintSet.setElevation(view.id, 1f) motion_layout.updateState(R.id.zoomed_in, constraintSet) Changing Constraints Programmatically PhotoGridActivity.kt
  73. constraintSet.constrainWidth(viewId, boundaries.width) constraintSet.constrainHeight(viewId, boundaries.height) constraintSet.connect(viewId, TOP, R.id.boundaries, TOP, 0) constraintSet.connect(viewId,

    START, R.id.boundaries, START, 0) constraintSet.connect(viewId, END, R.id.boundaries, END, 0) constraintSet.connect(viewId, BOTTOM, R.id.boundaries, BOTTOM, 0) constraintSet.setElevation(view.id, 1f) motion_layout.updateState(R.id.zoomed_in, constraintSet) Changing Constraints Programmatically PhotoGridActivity.kt
  74. constraintSet.connect(viewId, TOP, R.id.boundaries, TOP, 0) constraintSet.connect(viewId, START, R.id.boundaries, START, 0)

    constraintSet.connect(viewId, END, R.id.boundaries, END, 0) constraintSet.connect(viewId, BOTTOM, R.id.boundaries, BOTTOM, 0) constraintSet.setElevation(view.id, 1f) motion_layout.updateState(R.id.zoomed_in, constraintSet) Changing Constraints Programmatically PhotoGridActivity.kt
  75. <ConstraintSet android:id="@+id/grid" /> <ConstraintSet android:id="@+id/zoomed_in" /> constraintSet.connect(viewId, TOP, R.id.boundaries, TOP,

    0) constraintSet.connect(viewId, START, R.id.boundaries, START, 0) constraintSet.connect(viewId, END, R.id.boundaries, END, 0) constraintSet.connect(viewId, BOTTOM, R.id.boundaries, BOTTOM, 0) constraintSet.setElevation(view.id, 1f) motion_layout.updateState(R.id.zoomed_in, constraintSet) Changing Constraints Programmatically PhotoGridActivity.kt @xml/grid_zoom_scene
  76. <ConstraintSet android:id="@+id/grid" /> <ConstraintSet android:id="@+id/zoomed_in" /> constraintSet.connect(viewId, TOP, R.id.boundaries, TOP,

    0) constraintSet.connect(viewId, START, R.id.boundaries, START, 0) constraintSet.connect(viewId, END, R.id.boundaries, END, 0) constraintSet.connect(viewId, BOTTOM, R.id.boundaries, BOTTOM, 0) constraintSet.setElevation(view.id, 1f) motion_layout.updateState(R.id.zoomed_in, constraintSet) Changing Constraints Programmatically PhotoGridActivity.kt @xml/grid_zoom_scene
  77. RecyclerView & MotionLayout Placeholder • Calculate ViewHolder View position •

    Update a placeholder view within the starting ConstraintSet • Show hidden MotionLayout with placeholder
  78. • Previously the only option to turn on and off

    autoTransition was via reflection, but as of beta2 it is part of the Transition public API. • However I haven't found practical use cases for this yet... maybe you will! Changing Transitions on the fly
  79. Debugging MotionExtensions.kt fun MotionLayout.needsTransition(destination: Int): Boolean { val end =

    endState return when (currentState) { // If moving, are we moving to the destination? -1 -> end != destination // Not yet moving startState -> true // If at the end state, did we get to the destination? end -> end != destination // Unknown state else -> false }
  80. Debugging MotionExtensions.kt fun Int.resName(context: Context?): String { return if (this

    == -1) { "moving" } else try { context?.resources?.getResourceEntryName(this) ?: "unknown" } catch (ex: Throwable) { "unknown" } }
  81. MotionExtensions.kt fun MotionLayout.after(completion: () -> Unit) { this.setTransitionListener(object: MotionLayout.TransitionListener {

    override fun onTransitionTrigger( layout: MotionLayout?, startId: Int, positive: Boolean, progress: Float) {} override fun onTransitionStarted( layout: MotionLayout?, startId: Int, endId: Int) { } override fun onTransitionChange( layout: MotionLayout?, startId: Int, endId: Int, progress: Float) { } Debugging
  82. override fun onTransitionTrigger( layout: MotionLayout?, startId: Int, positive: Boolean, progress:

    Float) {} override fun onTransitionStarted( layout: MotionLayout?, startId: Int, endId: Int) { } override fun onTransitionChange( layout: MotionLayout?, startId: Int, endId: Int, progress: Float) { } override fun onTransitionCompleted( layout: MotionLayout?, currentId: Int) { completion() } // Already removed in beta2 release override fun allowsTransition(transition: MotionScene.Transition?): Boolean = true }) } Debugging
  83. Constraints - Limitations • Some problems cannot be solved with

    derived Constraints. • Creating a very large number of ConstraintSets and Transitions is infeasible.
  84. Constraints - Limitations N!/2 States Each requires a handwritten ConstraintSet

    This 2x2 board has 12 states A 3x3 board would have 181440 states
  85. TabLayout & Many NestedScrollViews MainActivity.kt override fun onCreate(savedInstanceState: Bundle?) {

    super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) view_pager?.apply { adapter = MainAdapter(supportFragmentManager) tab_layout?.setupWithViewPager(this) addOnPageChangeListener(object: ViewPager.OnPageChangeListener { override fun onPageScrollStateChanged(state: Int) {} override fun onPageScrolled(...) {} override fun onPageSelected(position: Int) { if (root_layout?.progress != 0f) { root_layout?.transitionToStart() } } }) } } internal fun onVerticalScroll(scroll: Int) { root_layout?.progress = Math.min(scroll / 1000f, 1f) }
  86. Why You Should Try MotionLayout • Reduces context switching •

    Makes some hard animations much easier • Allows us to make animations reentrant, continuous, & smooth
  87. Challenges in using MotionLayout • It’s still in beta •

    Lack of official learning materials • Debugging is a bit of trial and error https://codelabs.developers.google.com/codelabs/motion-layout/ But its so much easier now with MotionEditor