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

Advanced & Practical MotionLayout

Advanced & Practical MotionLayout

This talk covers how to practically approach and use MotionLayout to deliver on complex design asks. This will cover reusing Constraints between multiple Transitions, 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

May 22, 2019
Tweet

More Decks by Jason Pearson

Other Decks in Technology

Transcript

  1. Agenda • Quick Introduction • Provide answers to two questions:

    - What can MotionLayout do? - Why is this useful to you & your team?
  2. –Jinyan Cao @ Robinhood May 22, 2017 "ConstraintLayout seems to

    be the hot new thing nowadays. Flattening your view hierarchy, improving performance, supporting arbitrary bounding rules..." "... one other benefit of ConstraintLayout that most people are unaware of and the official documentation curiously doesn’t mention anything about: performing cool animations on your ConstraintLayout views with very little code."
  3. –Jinyan Cao @ Robinhood May 22, 2017 "ConstraintLayout seems to

    be the hot new thing nowadays. Flattening your view hierarchy, improving performance, supporting arbitrary bounding rules..." "... one other benefit of ConstraintLayout that most people are unaware of and the official documentation curiously doesn’t mention anything about: performing cool animations on your ConstraintLayout views with very little code."
  4. ConstraintLayout Animations @layout/activity_main <androidx.constraintlayout.widget.ConstraintLayout android:id=“@+id/root_view” > <View android:id="@id/square" android:layout_width="100dp" android:layout_height="100dp"

    android:background="@color/indigo" app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout>
  5. ConstraintLayout Animations @layout/activity_main <androidx.constraintlayout.widget.ConstraintLayout android:id=“@+id/root_view” > <View android:id="@id/square" android:layout_width="100dp" android:layout_height="100dp"

    android:background="@color/indigo" app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout>
  6. ConstraintLayout Animations @layout/activity_main <androidx.constraintlayout.widget.ConstraintLayout android:id=“@+id/root_view” > <View android:id="@id/square" android:layout_width="100dp" android:layout_height="100dp"

    android:background="@color/indigo" app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout>
  7. ConstraintLayout Animations @layout/activity_main <androidx.constraintlayout.widget.ConstraintLayout android:id=“@+id/root_view” > <View android:id="@id/square" android:layout_width="100dp" android:layout_height="100dp"

    android:background="@color/indigo" app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout>
  8. ConstraintLayout Animations @layout/activity_main <androidx.constraintlayout.widget.ConstraintLayout android:id=“@+id/root_view” > <View android:id="@id/square" android:layout_width="100dp" android:layout_height="100dp"

    android:background="@color/indigo" app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout>
  9. @layout/activity_main MainActivity.kt fun onClicked() { ConstraintSet().apply { constrainWidth(square.id, 100.dp()) constrainHeight(square.id,

    100.dp()) connect(square.id, START, 0, START, 0) connect(square.id, END, 0, END, 0) connect(square.id, BOTTOM, 0, BOTTOM, 0) applyTo(root_view) } }
  10. @layout/activity_main MainActivity.kt fun onClicked() { TransitionManager.beginDelayedTransition(root_view) ConstraintSet().apply { constrainWidth(square.id, 100.dp())

    constrainHeight(square.id, 100.dp()) connect(square.id, START, 0, START, 0) connect(square.id, END, 0, END, 0) connect(square.id, BOTTOM, 0, BOTTOM, 0) applyTo(root_view) } }
  11. @layout/activity_main MainActivity.kt fun onClicked() { TransitionManager.beginDelayedTransition(root_view) ConstraintSet().apply { constrainWidth(square.id, 100.dp())

    constrainHeight(square.id, 100.dp()) connect(square.id, START, 0, START, 0) connect(square.id, END, 0, END, 0) connect(square.id, BOTTOM, 0, BOTTOM, 0) applyTo(root_view) } }
  12. @layout/activity_main <androidx.constraintlayout.motion.widget.MotionLayout android:id="@+id/root_view" app:layoutDescription="@xml/motion_scene" > <View android:id="@id/square" android:layout_width="100dp" android:layout_height="100dp" android:background="@color/indigo"

    app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" /> </androidx.constraintlayout.motion.widget.MotionLayout> What is MotionLayout?
  13. <?xml version="1.0" encoding="utf-8"?> <MotionScene> <ConstraintSet android:id="@+id/start"/> <Constraint android:id="@id/square" android:layout_width="100dp" android:layout_height="100dp"

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

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

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

    </ConstraintSet> <ConstraintSet android:id="@+id/end"/> <Constraint android:id="@id/square" android:layout_width="100dp" android:layout_height="100dp" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintBottom_toBottomOf="parent" /> </ConstraintSet> @xml/motion_scene
  17. <Transition motion:constraintSetStart="@+id/start" motion:constraintSetEnd="@+id/end" > <ConstraintSet android:id="@+id/start"/> <Constraint android:id="@id/square" app:layout_constraintTop_toTopOf="parent" />

    </ConstraintSet> <ConstraintSet android:id="@+id/end"/> <Constraint android:id="@id/square" app:layout_constraintBottom_toBottomOf="parent" /> </ConstraintSet> @xml/motion_scene
  18. <Transition motion:constraintSetStart="@id/start" motion:constraintSetEnd="@id/end" > <OnClick /> </Transition> <ConstraintSet android:id="@+id/start"/> <Constraint

    android:id="@id/square" app:layout_constraintTop_toTopOf="parent" /> </ConstraintSet> <ConstraintSet android:id="@+id/end"/> <Constraint android:id="@id/square" app:layout_constraintBottom_toBottomOf="parent" /> </ConstraintSet> @xml/motion_scene
  19. <Transition motion:constraintSetStart="@id/start" motion:constraintSetEnd="@id/end" > <OnClick motion:targetId="@id/square" motion:clickAction="toggle" /> </Transition> <ConstraintSet

    android:id="@+id/start"/> <Constraint android:id="@id/square" app:layout_constraintTop_toTopOf="parent" /> </ConstraintSet> <ConstraintSet android:id="@+id/end"/> <Constraint android:id="@id/square" app:layout_constraintBottom_toBottomOf="parent" /> </ConstraintSet> @xml/motion_scene
  20. <Transition motion:constraintSetStart="@id/start" motion:constraintSetEnd="@id/end" > <OnClick motion:targetId="@id/square" motion:clickAction="toggle" /> </Transition> <ConstraintSet

    android:id="@+id/start"/> <Constraint android:id="@id/square" app:layout_constraintTop_toTopOf="parent" /> </ConstraintSet> <ConstraintSet android:id="@+id/end"/> <Constraint android:id="@id/square" app:layout_constraintBottom_toBottomOf="parent" /> </ConstraintSet> @xml/motion_scene
  21. <Transition motion:constraintSetStart="@id/start" motion:constraintSetEnd="@id/end" > <OnClick motion:targetId="@id/square" motion:clickAction="toggle" /> </Transition> <ConstraintSet

    android:id="@+id/start"/> <Constraint android:id="@id/square" app:layout_constraintTop_toTopOf="parent" /> </ConstraintSet> <ConstraintSet android:id="@+id/end"/> <Constraint android:id="@id/square" app:layout_constraintBottom_toBottomOf="parent" /> </ConstraintSet> @xml/motion_scene
  22. <Transition motion:constraintSetStart="@id/start" motion:constraintSetEnd="@id/end" > <OnClick motion:targetId="@id/square" motion:clickAction="toggle" /> </Transition> <ConstraintSet

    android:id="@+id/start"/> <Constraint android:id="@id/square" app:layout_constraintTop_toTopOf="parent" /> </ConstraintSet> <ConstraintSet android:id="@+id/end"/> <Constraint android:id="@id/square" app:layout_constraintBottom_toBottomOf="parent" /> </ConstraintSet> @xml/motion_scene
  23. Derived ConstraintSets @xml/motion_scene <MotionScene> <ConstraintSet android:id="@+id/start" > <Constraint android:id="@id/square" android:layout_width="100dp"

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

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

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

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

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

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

    app:layout_constraintTop_toTopOf="parent" /> </ConstraintSet> <ConstraintSet android:id="@+id/middle" app:deriveConstraintsFrom="@id/start" > <Constraint android:id="@id/square" android:layout_width="100dp" android:layout_height="100dp" app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent" > <Transform android:rotate="45" > </Constraint> </ConstraintSet> <ConstraintSet android:id="@+id/end" app:deriveConstraintsFrom="@id/middle" > <Constraint android:id="@id/square" android:layout_width="100dp" android:layout_height="100dp" app:layout_constraintBottom_toBottomOf="parent" /> </ConstraintSet>
  30. @xml/motion_scene <Transition motion:constraintSetStart="@id/start" motion:constraintSetEnd="@id/middle" > <OnSwipe motion:touchAnchorId="@id/square" motion:touchAnchorSide="top" motion:dragDirection="dragDown" />

    </Transition> <ConstraintSet android:id="@+id/start" .../> <ConstraintSet android:id="@+id/middle" .../> <ConstraintSet android:id="@+id/end" .../>
  31. @xml/motion_scene <Transition motion:constraintSetStart="@id/start" motion:constraintSetEnd="@id/middle" > <OnSwipe motion:touchAnchorId="@id/square" motion:touchAnchorSide="top" motion:dragDirection="dragDown" />

    </Transition> <ConstraintSet android:id="@+id/start" .../> <ConstraintSet android:id="@+id/middle" .../> <ConstraintSet android:id="@+id/end" .../>
  32. @xml/motion_scene <Transition motion:constraintSetStart="@id/start" motion:constraintSetEnd="@id/middle" > <OnSwipe motion:touchAnchorId="@id/square" motion:touchAnchorSide="top" motion:dragDirection="dragDown" />

    </Transition> <ConstraintSet android:id="@+id/start" .../> <ConstraintSet android:id="@+id/middle" .../> <ConstraintSet android:id="@+id/end" .../>
  33. @xml/motion_scene <Transition motion:constraintSetStart="@id/start" motion:constraintSetEnd="@id/middle" > <OnSwipe motion:touchAnchorId="@id/square" motion:touchAnchorSide="top" motion:dragDirection="dragDown" />

    </Transition> <ConstraintSet android:id="@+id/start" .../> <ConstraintSet android:id="@+id/middle" .../> <ConstraintSet android:id="@+id/end" .../>
  34. @xml/motion_scene <Transition motion:constraintSetStart="@id/start" motion:constraintSetEnd="@id/middle" > <OnSwipe motion:touchAnchorId="@id/square" motion:touchAnchorSide="top" motion:dragDirection="dragDown" />

    </Transition> <ConstraintSet android:id="@+id/start" .../> <ConstraintSet android:id="@+id/middle" .../> <ConstraintSet android:id="@+id/end" .../>
  35. @xml/motion_scene <Transition motion:constraintSetStart="@id/start" motion:constraintSetEnd="@id/middle" > <OnSwipe motion:touchAnchorId="@id/square" motion:touchAnchorSide="top" motion:dragDirection="dragDown" />

    </Transition> <ConstraintSet android:id="@+id/start" .../> <ConstraintSet android:id="@+id/middle" .../> <ConstraintSet android:id="@+id/end" .../>
  36. @xml/motion_scene <Transition motion:constraintSetStart="@id/start" motion:constraintSetEnd="@id/middle" > <OnSwipe motion:touchAnchorId="@id/square" motion:touchAnchorSide="top" motion:dragDirection="dragDown" />

    </Transition> <Transition motion:constraintSetStart="@id/middle" motion:constraintSetEnd="@id/end" > <OnSwipe motion:touchAnchorId="@id/square" motion:touchAnchorSide="top" motion:dragDirection="dragDown" /> </Transition> <ConstraintSet android:id="@+id/start" .../> <ConstraintSet android:id="@+id/middle" .../> <ConstraintSet android:id="@+id/end" .../>
  37. @xml/motion_scene <Transition motion:constraintSetStart="@id/start" motion:constraintSetEnd="@id/middle" .../> <Transition motion:constraintSetStart="@id/middle" motion:constraintSetEnd="@id/end" .../> <ConstraintSet

    android:id="@+id/start" .../> <!-- 45* rotation !--> <ConstraintSet android:id="@+id/middle" .../> <ConstraintSet android:id="@+id/end" .../>
  38. Constraints • ConstraintLayout bounds can position or resize child views.

    • There are other Sub Elements that can be applied within a Constraint.
  39. @xml/motion_scene <ConstraintSet android:id="@+id/start" > <Constraint android:id="@id/square" > <Layout android:layout_width="150dp" />

    <PropertySet android:alpha="1" /> <Transform android:translationX="36dp" /> <Motion app:pathMotionArc="startHorizontal" /> </ConstraintSet> </ConstraintSet> Constraints - Sub Elements
  40. @xml/motion_scene <ConstraintSet android:id="@+id/start" > <Constraint android:id="@id/square" > <Layout android:layout_width="150dp" />

    <PropertySet android:alpha="1" /> <Transform android:translationX="36dp" /> <Motion app:pathMotionArc="startHorizontal" /> </ConstraintSet> </ConstraintSet> Constraints - Sub Elements
  41. Constraints - Progress • PropertySet sub element allows a parent

    to pass its progress to a child MotionLayout • app:motionProgress
  42. @xml/header_scene <MotionScene> <ConstraintSet android:id="@+id/start" > <Constraint android:id="@id/header_title" > <PropertySet android:scaleX="1.0"

    android:scaleY="1.0" /> </ConstraintSet> </ConstraintSet> <ConstraintSet android:id="@+id/end" > <Constraint android:id="@id/header_title" > <PropertySet android:scaleX="0.7" android:scaleY="0.7" /> </ConstraintSet> </ConstraintSet> </MotionScene> Constraints - Progress
  43. @xml/header_scene @xml/scrolling_scene <ConstraintSet android:id="@+id/expanded" > <Constraint android:id="@id/header_view" android:layout_width="0dp" android:layout_height="104dp" app:layout_constraintStart_toStartOf="parent"

    app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" /> <Constraint android:id="@id/recycler_view" android:layout_width="0dp" android:layout_height="0dp" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toBottomOf="@id/header_view" app:layout_constraintBottom_toBottomOf="parent" /> </ConstraintSet> Constraints - Progress
  44. @xml/header_scene @xml/scrolling_scene <ConstraintSet android:id="@+id/expanded" > <Constraint android:id="@id/header_view" android:layout_width="match_parent" android:layout_height="104dp" app:layout_constraintStart_toStartOf="parent"

    app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" /> <Constraint android:id="@id/recycler_view" android:layout_width="match_parent" android:layout_height="0dp" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toBottomOf="@id/header_view" app:layout_constraintBottom_toBottomOf="parent" /> </ConstraintSet> Constraints - Progress
  45. @xml/header_scene @xml/scrolling_scene <ConstraintSet android:id="@+id/expanded" > <Constraint android:id="@id/header_view" android:layout_width="match_parent" android:layout_height="104dp" app:layout_constraintTop_toTopOf="parent"

    /> <Constraint android:id="@id/recycler_view" android:layout_width="match_parent" android:layout_height="0dp" app:layout_constraintTop_toBottomOf="@id/header_view" app:layout_constraintBottom_toBottomOf="parent" /> </ConstraintSet> Constraints - Progress
  46. @xml/header_scene @xml/scrolling_scene <ConstraintSet android:id="@+id/expanded" > <Constraint android:id="@id/header_view" android:layout_width="match_parent" android:layout_height="104dp" app:layout_constraintTop_toTopOf="parent"

    /> <Constraint android:id="@id/recycler_view" android:layout_width="match_parent" android:layout_height="0dp" app:layout_constraintTop_toBottomOf="@id/header_view" app:layout_constraintBottom_toBottomOf="parent" /> </ConstraintSet> Constraints - Progress
  47. @xml/header_scene @xml/scrolling_scene <ConstraintSet android:id="@+id/expanded" > <Constraint android:id="@id/header_view" android:layout_width="match_parent" android:layout_height="104dp" app:layout_constraintTop_toTopOf="parent"

    /> <Constraint android:id="@id/recycler_view" android:layout_width="match_parent" android:layout_height="0dp" app:layout_constraintTop_toBottomOf="@id/header_view" app:layout_constraintBottom_toBottomOf="parent" /> </ConstraintSet> Constraints - Progress
  48. @xml/header_scene @xml/scrolling_scene <ConstraintSet android:id="@+id/expanded" > <Constraint android:id="@id/header_view" android:layout_width="match_parent" android:layout_height="104dp" app:layout_constraintTop_toTopOf="parent"

    /> <Constraint android:id="@id/recycler_view" android:layout_width="match_parent" android:layout_height="0dp" app:layout_constraintTop_toBottomOf="@id/header_view" app:layout_constraintBottom_toBottomOf="parent" /> </ConstraintSet> Constraints - Progress
  49. @xml/header_scene @xml/scrolling_scene <ConstraintSet android:id="@+id/expanded" > <Constraint android:id="@id/header_view" android:layout_width="match_parent" android:layout_height="104dp" app:layout_constraintTop_toTopOf="parent"

    /> <Constraint android:id="@id/recycler_view" android:layout_width="match_parent" android:layout_height="0dp" app:layout_constraintTop_toBottomOf="@id/header_view" app:layout_constraintBottom_toBottomOf="parent" /> </ConstraintSet>
  50. @xml/header_scene @xml/scrolling_scene <ConstraintSet android:id="@+id/expanded" > <Constraint android:id="@id/header_view" android:layout_width="match_parent" android:layout_height="104dp" app:layout_constraintTop_toTopOf="parent"

    /> <Constraint android:id="@id/recycler_view" android:layout_width="match_parent" android:layout_height="0dp" app:layout_constraintTop_toBottomOf="@id/header_view" app:layout_constraintBottom_toBottomOf="parent" /> </ConstraintSet> <ConstraintSet android:id="@+id/collapsed" > <Constraint android:id="@id/header_view" android:layout_width="match_parent" android:layout_height="56dp" app:layout_constraintTop_toTopOf="parent" /> <Constraint android:id="@id/recycler_view" android:layout_width="match_parent" android:layout_height="0dp" app:layout_constraintTop_toBottomOf="@id/header_view" app:layout_constraintBottom_toBottomOf="parent" /> </ConstraintSet>
  51. @xml/header_scene @xml/scrolling_scene <ConstraintSet android:id="@+id/expanded" > <Constraint android:id="@id/header_view" android:layout_width="match_parent" android:layout_height="104dp" app:layout_constraintTop_toTopOf="parent"

    /> <Constraint android:id="@id/recycler_view" android:layout_width="match_parent" android:layout_height="0dp" app:layout_constraintTop_toBottomOf="@id/header_view" app:layout_constraintBottom_toBottomOf="parent" /> </ConstraintSet> <ConstraintSet android:id="@+id/collapsed" app:deriveConstraintsFrom="@+id/expanded" > <Constraint android:id="@id/header_view" android:layout_width="match_parent" android:layout_height="56dp" app:layout_constraintTop_toTopOf="parent" /> </ConstraintSet>
  52. @xml/header_scene @xml/scrolling_scene <ConstraintSet android:id="@+id/expanded" > <Constraint android:id="@id/header_view" android:layout_width="match_parent" android:layout_height="104dp" app:layout_constraintTop_toTopOf="parent"

    /> <Constraint android:id="@id/recycler_view" android:layout_width="match_parent" android:layout_height="0dp" app:layout_constraintTop_toBottomOf="@id/header_view" app:layout_constraintBottom_toBottomOf="parent" /> </ConstraintSet> <ConstraintSet android:id="@+id/collapsed" app:deriveConstraintsFrom="@+id/expanded" > <Constraint android:id="@id/header_view" android:layout_width="match_parent" android:layout_height="56dp" app:layout_constraintTop_toTopOf="parent" /> </ConstraintSet>
  53. @xml/header_scene @xml/scrolling_scene <ConstraintSet android:id="@+id/expanded" > <Constraint android:id="@id/header_view" android:layout_width="match_parent" android:layout_height="104dp" app:layout_constraintTop_toTopOf="parent"

    > <PropertySet android:motionProgress="0" /> </Constraint> <Constraint android:id="@id/recycler_view" android:layout_width="match_parent" android:layout_height="0dp" app:layout_constraintTop_toBottomOf="@id/header_view" app:layout_constraintBottom_toBottomOf="parent" /> </ConstraintSet> <ConstraintSet android:id="@+id/collapsed" app:deriveConstraintsFrom="@+id/expanded" > <Constraint android:id="@id/header_view" android:layout_width="match_parent" android:layout_height="56dp" app:layout_constraintTop_toTopOf="parent"> <PropertySet android:motionProgress="1" /> </Constraint> </ConstraintSet>
  54. @xml/header_scene @xml/scrolling_scene <ConstraintSet android:id="@+id/expanded" > <Constraint android:id="@id/header_view" > <PropertySet android:motionProgress="0"

    /> </Constraint> <Constraint android:id="@id/recycler_view" /> </ConstraintSet> <ConstraintSet android:id="@+id/collapsed" app:deriveConstraintsFrom="@+id/expanded" > <Constraint android:id="@id/header_view" > <PropertySet android:motionProgress="1" /> </Constraint> </ConstraintSet>
  55. Constraints - Changing Programmatically • MotionLayout is just an extension

    of ConstraintLayout • After making changes to a ConstraintSet we must call MotionLayout.updateState(stateId, constraintSet)
  56. Constraints - Changing Programmatically MainActivity.kt 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)
  57. MainActivity.kt 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) @xml/motion_scene <ConstraintSet android:id="@+id/grid" /> <ConstraintSet android:id="@+id/zoomed_in" />
  58. MainActivity.kt 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) @xml/motion_scene <ConstraintSet android:id="@+id/grid" /> <ConstraintSet android:id="@+id/zoomed_in" />
  59. Constraints - Limitations • Some problems cannot be solved with

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

    This 2x2 board has 12 states A 3x3 board would have 181440 states
  61. @xml/motion_scene <Transition app:constraintSetStart="@id/rotate_2" app:constraintSetEnd="@id/full_1" app:autoTransition="animateToEnd" app:duration="250" app:motionInterpolator="easeOut" > <KeyFrameSet> <KeyAttribute

    app:motionTarget="@id/glass_top" app:framePosition="100" android:rotation="-360" /> <KeyAttribute app:motionTarget="@id/glass_bottom" app:framePosition="100" android:rotation="-360" /> </KeyFrameSet> </Transition> autoTransition property
  62. @xml/motion_scene <Transition app:constraintSetStart="@id/rotate_2" app:constraintSetEnd="@id/full_1" app:autoTransition="animateToEnd" app:duration="250" app:motionInterpolator="easeOut" > <KeyFrameSet> <KeyAttribute

    app:motionTarget="@id/glass_top" app:framePosition="100" android:rotation="-360" /> <KeyAttribute app:motionTarget="@id/glass_bottom" app:framePosition="100" android:rotation="-360" /> </KeyFrameSet> </Transition> KeyFrameSet in Transitions
  63. @xml/motion_scene <Transition app:constraintSetStart="@id/rotate_2" app:constraintSetEnd="@id/full_1" app:autoTransition="animateToEnd" app:duration="250" app:motionInterpolator="easeOut" > <KeyFrameSet> <KeyAttribute

    app:motionTarget="@id/glass_top" app:framePosition="100" android:rotation="-360" /> <KeyAttribute app:motionTarget="@id/glass_bottom" app:framePosition="100" android:rotation="-360" /> </KeyFrameSet> </Transition> KeyFrameSet in Transitions
  64. @xml/motion_scene <Transition app:constraintSetStart="@id/rotate_2" app:constraintSetEnd="@id/full_1" app:autoTransition="animateToEnd" app:duration="250" app:motionInterpolator="easeOut" > <KeyFrameSet> <KeyAttribute

    app:motionTarget="@id/glass_top" app:framePosition="100" android:rotation="-360" /> <KeyAttribute app:motionTarget="@id/glass_bottom" app:framePosition="100" android:rotation="-360" /> </KeyFrameSet> </Transition> KeyFrameSet in Transitions
  65. @xml/motion_scene <Transition app:constraintSetStart="@id/rotate_2" app:constraintSetEnd="@id/full_1" app:autoTransition="animateToEnd" app:duration="250" app:motionInterpolator="easeOut" > <KeyFrameSet> <KeyAttribute

    app:motionTarget="@id/glass_top" app:framePosition="100" android:rotation="-360" /> <KeyAttribute app:motionTarget="@id/glass_bottom" app:framePosition="100" android:rotation="-360" /> </KeyFrameSet> </Transition> KeyFrameSet in Transitions
  66. @xml/motion_scene <Transition app:constraintSetStart="@id/rotate_2" app:constraintSetEnd="@id/full_1" app:autoTransition="animateToEnd" app:duration="250" app:motionInterpolator="easeOut" > <KeyFrameSet> <KeyAttribute

    app:motionTarget="@id/glass_top" app:framePosition="100" android:rotation="-360" /> <KeyAttribute app:motionTarget="@id/glass_bottom" app:framePosition="100" android:rotation="-360" /> </KeyFrameSet> </Transition> KeyFrameSet in Transitions
  67. Changing Transitions on the fly • Only current option to

    turn on and off autoTransition is via reflection. • I've opened an issue on ConstraintLayout requesting access to programmatically modify this flag.
  68. Changing Transitions on the fly • Only current option to

    turn on and off autoTransition is via reflection. • I've opened an issue on ConstraintLayout requesting access to programmatically modify this flag.
  69. MotionLayout & RecyclerView • ConstraintLayout team has demoed their own

    placeholder implementation that uses RecyclerView.Adapter, but it's not yet available. • I've opted to programmatically manipulate ConstraintSets & Transitions to achieve a similar placeholder behavior.
  70. @xml/placeholder_scene <ConstraintSet android:id="@+id/scrolling" > <Constraint android:id="@id/placeholder" android:layout_width="match_parent" android:layout_height="wrap_content" app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent"

    app:layout_constraintEnd_toEndOf="parent" app:layout_constraintBottom_toBottomOf="parent" > <PropertySet android:elevation="0dp" app:visibilityMode="ignore" /> </Constraint> <Constraint android:id="@id/recycler_view" android:layout_width="match_parent" android:layout_height="match_parent" /> </ConstraintSet> MotionLayout & RecyclerView
  71. @xml/placeholder_scene <ConstraintSet android:id="@+id/scrolling" > <Constraint android:id="@id/placeholder" android:layout_width="match_parent" android:layout_height="wrap_content" app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent"

    app:layout_constraintEnd_toEndOf="parent" app:layout_constraintBottom_toBottomOf="parent" > <PropertySet android:elevation="0dp" app:visibilityMode="ignore" /> </Constraint> <Constraint android:id="@id/recycler_view" android:layout_width="match_parent" android:layout_height="match_parent" /> </ConstraintSet> MotionLayout & RecyclerView
  72. @xml/placeholder_scene <ConstraintSet android:id="@+id/scrolling" > <Constraint android:id="@id/placeholder" android:layout_width="match_parent" android:layout_height="wrap_content" app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent"

    app:layout_constraintEnd_toEndOf="parent" app:layout_constraintBottom_toBottomOf="parent" > <PropertySet android:elevation="0dp" app:visibilityMode="ignore" /> </Constraint> <Constraint android:id="@id/recycler_view" android:layout_width="match_parent" android:layout_height="match_parent" /> </ConstraintSet> MotionLayout & RecyclerView
  73. @xml/placeholder_scene <ConstraintSet android:id="@+id/scrolling" > <Constraint android:id="@id/placeholder" android:layout_width="match_parent" android:layout_height="wrap_content" app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent"

    app:layout_constraintEnd_toEndOf="parent" app:layout_constraintBottom_toBottomOf="parent" > <PropertySet android:elevation="0dp" app:visibilityMode="ignore" /> </Constraint> <Constraint android:id="@id/recycler_view" android:layout_width="match_parent" android:layout_height="match_parent" /> </ConstraintSet> MotionLayout & RecyclerView
  74. @xml/placeholder_scene <ConstraintSet android:id="@+id/scrolling" > <Constraint android:id="@id/placeholder" android:layout_width="match_parent" android:layout_height="wrap_content" app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent"

    app:layout_constraintEnd_toEndOf="parent" app:layout_constraintBottom_toBottomOf="parent" > <PropertySet android:elevation="0dp" app:visibilityMode="ignore" /> </Constraint> <Constraint android:id="@id/recycler_view" android:layout_width="match_parent" android:layout_height="match_parent" /> </ConstraintSet> MotionLayout & RecyclerView
  75. @xml/placeholder_scene <ConstraintSet android:id="@+id/scrolling" > <Constraint android:id="@id/placeholder" android:layout_width="match_parent" android:layout_height="wrap_content" app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent"

    app:layout_constraintEnd_toEndOf="parent" app:layout_constraintBottom_toBottomOf="parent" > <PropertySet android:elevation="0dp" app:visibilityMode="ignore" /> </Constraint> <Constraint android:id="@id/recycler_view" android:layout_width="match_parent" android:layout_height="match_parent" /> </ConstraintSet> <ConstraintSet android:id="@+id/selected" app:deriveConstraintsFrom="@id/start" > <Constraint android:id="@id/placeholder" android:layout_width="match_parent" android:layout_height="wrap_content" app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintBottom_toBottomOf="parent" > <PropertySet android:elevation="1dp" app:visibilityMode="ignore" /> </Constraint> </ConstraintSet> MotionLayout & RecyclerView
  76. app:layout_constraintEnd_toEndOf="parent" app:layout_constraintBottom_toBottomOf="parent" > <PropertySet android:elevation="0dp" app:visibilityMode="ignore" /> </Constraint> <Constraint android:id="@id/recycler_view"

    android:layout_width="match_parent" android:layout_height="match_parent" /> </ConstraintSet> <ConstraintSet android:id="@+id/selected" app:deriveConstraintsFrom="@id/start" > <Constraint android:id="@id/placeholder" android:layout_width="match_parent" android:layout_height="wrap_content" app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintBottom_toBottomOf="parent" > <PropertySet android:elevation="1dp" app:visibilityMode="ignore" /> </Constraint> </ConstraintSet> MotionLayout & RecyclerView
  77. app:layout_constraintEnd_toEndOf="parent" app:layout_constraintBottom_toBottomOf="parent" > <PropertySet android:elevation="0dp" app:visibilityMode="ignore" /> </Constraint> <Constraint android:id="@id/recycler_view"

    android:layout_width="match_parent" android:layout_height="match_parent" /> </ConstraintSet> <ConstraintSet android:id="@+id/selected" app:deriveConstraintsFrom="@id/start" > <Constraint android:id="@id/placeholder" android:layout_width="match_parent" android:layout_height="wrap_content" app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintBottom_toBottomOf="parent" > <PropertySet android:elevation="1dp" app:visibilityMode="ignore" /> </Constraint> </ConstraintSet> MotionLayout & RecyclerView
  78. motion_layout?.apply { val cs = getConstraintSet(R.id.scrolling) cs?.constrainWidth(placeholder.id, MATCH_PARENT) cs?.constrainHeight(placeholder.id, WRAP_CONTENT)

    if (topOffset > 0) { cs?.setMargin(placeholder.id, TOP, placeholderTop) cs?.setMargin(placeholder.id, BOTTOM, 0) cs?.setVerticalBias(placeholder.id, 0f) } else { cs?.setMargin(placeholder.id, BOTTOM, placeholderBottom) cs?.setMargin(placeholder.id, TOP, 0) cs?.setVerticalBias(placeholder.id, 1f) } cs?.connect(placeholder.id, START, parentId, START, startMargin) cs?.connect(placeholder.id, END, parentId, END, startMargin) updateState(R.id.scrolling, cs) setTransition(R.id.scrolling, R.id.selected) placeholder?.isVisible = true transitionToEnd() } MotionLayout & RecyclerView
  79. val cs = getConstraintSet(R.id.scrolling) cs?.constrainWidth(placeholder.id, MATCH_PARENT) cs?.constrainHeight(placeholder.id, WRAP_CONTENT) if (topOffset

    > 0) { cs?.setMargin(placeholder.id, TOP, placeholderTop) cs?.setMargin(placeholder.id, BOTTOM, 0) cs?.setVerticalBias(placeholder.id, 0f) } else { cs?.setMargin(placeholder.id, BOTTOM, placeholderBottom) cs?.setMargin(placeholder.id, TOP, 0) cs?.setVerticalBias(placeholder.id, 1f) } cs?.connect(placeholder.id, START, parentId, START, startMargin) cs?.connect(placeholder.id, END, parentId, END, startMargin) updateState(R.id.scrolling, cs) setTransition(R.id.scrolling, R.id.selected) placeholder?.isVisible = true transitionToEnd() MotionLayout & RecyclerView
  80. val cs = getConstraintSet(R.id.scrolling) cs?.constrainWidth(placeholder.id, MATCH_PARENT) cs?.constrainHeight(placeholder.id, WRAP_CONTENT) if (topOffset

    > 0) { cs?.setMargin(placeholder.id, TOP, placeholderTop) cs?.setMargin(placeholder.id, BOTTOM, 0) cs?.setVerticalBias(placeholder.id, 0f) } else { cs?.setMargin(placeholder.id, BOTTOM, placeholderBottom) cs?.setMargin(placeholder.id, TOP, 0) cs?.setVerticalBias(placeholder.id, 1f) } cs?.connect(placeholder.id, START, parentId, START, startMargin) cs?.connect(placeholder.id, END, parentId, END, startMargin) updateState(R.id.scrolling, cs) setTransition(R.id.scrolling, R.id.selected) placeholder?.isVisible = true transitionToEnd() MotionLayout & RecyclerView
  81. val cs = getConstraintSet(R.id.scrolling) cs?.constrainWidth(placeholder.id, MATCH_PARENT) cs?.constrainHeight(placeholder.id, WRAP_CONTENT) if (topOffset

    > 0) { cs?.setMargin(placeholder.id, TOP, placeholderTop) cs?.setMargin(placeholder.id, BOTTOM, 0) cs?.setVerticalBias(placeholder.id, 0f) } else { cs?.setMargin(placeholder.id, BOTTOM, placeholderBottom) cs?.setMargin(placeholder.id, TOP, 0) cs?.setVerticalBias(placeholder.id, 1f) } cs?.connect(placeholder.id, START, parentId, START, startMargin) cs?.connect(placeholder.id, END, parentId, END, startMargin) updateState(R.id.scrolling, cs) setTransition(R.id.scrolling, R.id.selected) placeholder?.isVisible = true transitionToEnd() MotionLayout & RecyclerView
  82. val cs = getConstraintSet(R.id.scrolling) cs?.constrainWidth(placeholder.id, MATCH_PARENT) cs?.constrainHeight(placeholder.id, WRAP_CONTENT) if (topOffset

    > 0) { cs?.setMargin(placeholder.id, TOP, placeholderTop) cs?.setMargin(placeholder.id, BOTTOM, 0) cs?.setVerticalBias(placeholder.id, 0f) } else { cs?.setMargin(placeholder.id, BOTTOM, placeholderBottom) cs?.setMargin(placeholder.id, TOP, 0) cs?.setVerticalBias(placeholder.id, 1f) } cs?.connect(placeholder.id, START, parentId, START, startMargin) cs?.connect(placeholder.id, END, parentId, END, startMargin) updateState(R.id.scrolling, cs) setTransition(R.id.scrolling, R.id.selected) placeholder?.isVisible = true transitionToEnd() MotionLayout & RecyclerView
  83. cs?.constrainHeight(placeholder.id, WRAP_CONTENT) if (topOffset > 0) { cs?.setMargin(placeholder.id, TOP, placeholderTop)

    cs?.setMargin(placeholder.id, BOTTOM, 0) cs?.setVerticalBias(placeholder.id, 0f) } else { cs?.setMargin(placeholder.id, BOTTOM, placeholderBottom) cs?.setMargin(placeholder.id, TOP, 0) cs?.setVerticalBias(placeholder.id, 1f) } cs?.connect(placeholder.id, START, parentId, START, startMargin) cs?.connect(placeholder.id, END, parentId, END, startMargin) updateState(R.id.scrolling, cs) setTransition(R.id.scrolling, R.id.selected) placeholder?.isVisible = true transitionToEnd() MotionLayout & RecyclerView
  84. cs?.setVerticalBias(placeholder.id, 0f) } else { cs?.setMargin(placeholder.id, BOTTOM, placeholderBottom) cs?.setMargin(placeholder.id, TOP,

    0) cs?.setVerticalBias(placeholder.id, 1f) } cs?.connect(placeholder.id, START, parentId, START, startMargin) cs?.connect(placeholder.id, END, parentId, END, startMargin) updateState(R.id.scrolling, cs) setTransition(R.id.scrolling, R.id.selected) placeholder?.isVisible = true transitionToEnd() MotionLayout & RecyclerView
  85. Why You Should Try MotionLayout • Reduces context switching •

    Makes some hard animations much easier • Allows us to make animations reentrant, continuous, & smooth - Watch Nick Butcher's I/O 2019 Motional Intelligence
  86. Challenges in using MotionLayout • It’s still in beta •

    Lack of learning materials • Debugging is a bit of trial and error - waiting for highly anticipated MotionEditor
  87. Can we use it with Jetpack Compose? NOT YET Should

    we ever use Coordinator Layout again? NO Physics based Transitions? NOT YET Is Hinge hiring? YES