Slide 1

Slide 1 text

Advanced & Practical MotionLayout Jason Pearson @kaeawc bit.ly/advanced-motionlayout-talk

Slide 2

Slide 2 text

Agenda • Quick Introduction • Provide answers to two questions: - What can MotionLayout do? - Why is this useful to you & your team?

Slide 3

Slide 3 text

–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."

Slide 4

Slide 4 text

–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."

Slide 5

Slide 5 text

ConstraintLayout Animations @layout/activity_main

Slide 6

Slide 6 text

ConstraintLayout Animations @layout/activity_main

Slide 7

Slide 7 text

ConstraintLayout Animations @layout/activity_main

Slide 8

Slide 8 text

ConstraintLayout Animations @layout/activity_main

Slide 9

Slide 9 text

ConstraintLayout Animations @layout/activity_main

Slide 10

Slide 10 text

@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) } }

Slide 11

Slide 11 text

@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) } }

Slide 12

Slide 12 text

@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) } }

Slide 13

Slide 13 text

@layout/activity_main MainActivity.kt fun onClicked() { TransitionManager.beginDelayedTransition(root_view) ConstraintSet().apply { load(baseContext, R.layout.keyframe_two) applyTo(root_view) } }

Slide 14

Slide 14 text

What is MotionLayout? • Extended class of ConstraintLayout • Library of helper functions to coordinate animation

Slide 15

Slide 15 text

@layout/activity_main What is MotionLayout?

Slide 16

Slide 16 text

@layout/activity_main What is MotionLayout?

Slide 17

Slide 17 text

@layout/activity_main What is MotionLayout?

Slide 18

Slide 18 text

What is MotionLayout?

Slide 19

Slide 19 text

Slide 20

Slide 20 text

@xml/motion_scene

Slide 21

Slide 21 text

@xml/motion_scene

Slide 22

Slide 22 text

@xml/motion_scene

Slide 23

Slide 23 text

@xml/motion_scene

Slide 24

Slide 24 text

@xml/motion_scene

Slide 25

Slide 25 text

@xml/motion_scene

Slide 26

Slide 26 text

@xml/motion_scene

Slide 27

Slide 27 text

@xml/motion_scene

Slide 28

Slide 28 text

@xml/motion_scene

Slide 29

Slide 29 text

@xml/motion_scene

Slide 30

Slide 30 text

@xml/motion_scene

Slide 31

Slide 31 text

@xml/motion_scene

Slide 32

Slide 32 text

Constraints

Slide 33

Slide 33 text

Derived ConstraintSets • Introduced in ConstraintLayout 2.0 alpha 5

Slide 34

Slide 34 text

Derived ConstraintSets @xml/motion_scene

Slide 35

Slide 35 text

@xml/motion_scene

Slide 36

Slide 36 text

@xml/motion_scene

Slide 37

Slide 37 text

@xml/motion_scene

Slide 38

Slide 38 text

@xml/motion_scene

Slide 39

Slide 39 text

@xml/motion_scene

Slide 40

Slide 40 text

@xml/motion_scene

Slide 41

Slide 41 text

@xml/motion_scene

Slide 42

Slide 42 text

@xml/motion_scene

Slide 43

Slide 43 text

@xml/motion_scene

Slide 44

Slide 44 text

@xml/motion_scene

Slide 45

Slide 45 text

@xml/motion_scene

Slide 46

Slide 46 text

@xml/motion_scene

Slide 47

Slide 47 text

@xml/motion_scene

Slide 48

Slide 48 text

@xml/motion_scene

Slide 49

Slide 49 text

@xml/motion_scene

Slide 50

Slide 50 text

@xml/motion_scene

Slide 51

Slide 51 text

Constraints • ConstraintLayout bounds can position or resize child views. • There are other Sub Elements that can be applied within a Constraint.

Slide 52

Slide 52 text

@xml/motion_scene Constraints - Sub Elements

Slide 53

Slide 53 text

@xml/motion_scene Constraints - Sub Elements

Slide 54

Slide 54 text

@xml/motion_scene Constraints - Path Motion Arc

Slide 55

Slide 55 text

@xml/motion_scene Constraints - Path Motion Arc

Slide 56

Slide 56 text

@xml/motion_scene Constraints - Path Motion Arc

Slide 57

Slide 57 text

Constraints - Progress • PropertySet sub element allows a parent to pass its progress to a child MotionLayout • app:motionProgress

Slide 58

Slide 58 text

@xml/header_scene Constraints - Progress

Slide 59

Slide 59 text

@xml/header_scene Constraints - Progress

Slide 60

Slide 60 text

@xml/header_scene Constraints - Progress

Slide 61

Slide 61 text

@xml/header_scene @xml/scrolling_scene Constraints - Progress

Slide 62

Slide 62 text

@xml/header_scene @xml/scrolling_scene Constraints - Progress

Slide 63

Slide 63 text

@xml/header_scene @xml/scrolling_scene Constraints - Progress

Slide 64

Slide 64 text

@xml/header_scene @xml/scrolling_scene Constraints - Progress

Slide 65

Slide 65 text

@xml/header_scene @xml/scrolling_scene Constraints - Progress

Slide 66

Slide 66 text

@xml/header_scene @xml/scrolling_scene Constraints - Progress

Slide 67

Slide 67 text

@xml/header_scene @xml/scrolling_scene Constraints - Progress

Slide 68

Slide 68 text

@xml/header_scene @xml/scrolling_scene

Slide 69

Slide 69 text

@xml/header_scene @xml/scrolling_scene

Slide 70

Slide 70 text

@xml/header_scene @xml/scrolling_scene

Slide 71

Slide 71 text

@xml/header_scene @xml/scrolling_scene

Slide 72

Slide 72 text

@xml/header_scene @xml/scrolling_scene

Slide 73

Slide 73 text

@xml/header_scene @xml/scrolling_scene

Slide 74

Slide 74 text

Constraints - Changing Programmatically • MotionLayout is just an extension of ConstraintLayout • After making changes to a ConstraintSet we must call MotionLayout.updateState(stateId, constraintSet)

Slide 75

Slide 75 text

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)

Slide 76

Slide 76 text

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

Slide 77

Slide 77 text

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

Slide 78

Slide 78 text

Constraints - Limitations • Some problems cannot be solved with derived Constraints. • Creating a very large number of ConstraintSets and Transitions is infeasible.

Slide 79

Slide 79 text

Constraints - Limitations N!/2 States Each requires a handwritten ConstraintSet This 2x2 board has 12 states A 3x3 board would have 181440 states

Slide 80

Slide 80 text

Constraints - Limitations

Slide 81

Slide 81 text

Transitions

Slide 82

Slide 82 text

autoTransition property • Can specify whether to animate or jump to the start or end of a transition

Slide 83

Slide 83 text

autoTransition property

Slide 84

Slide 84 text

@xml/motion_scene autoTransition property

Slide 85

Slide 85 text

@xml/motion_scene KeyFrameSet in Transitions

Slide 86

Slide 86 text

@xml/motion_scene KeyFrameSet in Transitions

Slide 87

Slide 87 text

@xml/motion_scene KeyFrameSet in Transitions

Slide 88

Slide 88 text

@xml/motion_scene KeyFrameSet in Transitions

Slide 89

Slide 89 text

@xml/motion_scene KeyFrameSet in Transitions

Slide 90

Slide 90 text

@xml/motion_scene app:motionInterpolator="easeOut" Interpolators in Transitions

Slide 91

Slide 91 text

@xml/motion_scene app:motionInterpolator="cubic(0.4,0,1,1)" Interpolators in Transitions

Slide 92

Slide 92 text

@xml/motion_scene app:motionInterpolator="cubic(0.4,0,1,1)" https://cubic-bezier.com/#.42,0,1,1 Interpolators in Transitions

Slide 93

Slide 93 text

@xml/motion_scene app:motionInterpolator="cubic(0.4,0,1,1)" https://cubic-bezier.com/#.42,0,1,1 Interpolators in Transitions

Slide 94

Slide 94 text

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.

Slide 95

Slide 95 text

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.

Slide 96

Slide 96 text

Changing Transitions on the fly

Slide 97

Slide 97 text

MotionLayout + RecyclerView

Slide 98

Slide 98 text

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.

Slide 99

Slide 99 text

@xml/placeholder_scene MotionLayout & RecyclerView

Slide 100

Slide 100 text

@xml/placeholder_scene MotionLayout & RecyclerView

Slide 101

Slide 101 text

@xml/placeholder_scene MotionLayout & RecyclerView

Slide 102

Slide 102 text

@xml/placeholder_scene MotionLayout & RecyclerView

Slide 103

Slide 103 text

@xml/placeholder_scene MotionLayout & RecyclerView

Slide 104

Slide 104 text

@xml/placeholder_scene MotionLayout & RecyclerView

Slide 105

Slide 105 text

app:layout_constraintEnd_toEndOf="parent" app:layout_constraintBottom_toBottomOf="parent" > MotionLayout & RecyclerView

Slide 106

Slide 106 text

app:layout_constraintEnd_toEndOf="parent" app:layout_constraintBottom_toBottomOf="parent" > MotionLayout & RecyclerView

Slide 107

Slide 107 text

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

Slide 108

Slide 108 text

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

Slide 109

Slide 109 text

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

Slide 110

Slide 110 text

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

Slide 111

Slide 111 text

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

Slide 112

Slide 112 text

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

Slide 113

Slide 113 text

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

Slide 114

Slide 114 text

MotionLayout & RecyclerView

Slide 115

Slide 115 text

Summary

Slide 116

Slide 116 text

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

Slide 117

Slide 117 text

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

Slide 118

Slide 118 text

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

Slide 119

Slide 119 text

Questions? Is Hinge hiring? YES bit.ly/advanced-motionlayout-talk