Copenhagen Android meetup

Copenhagen Android meetup

ConstraintLayout & Animation

B9012970f22b84c5b344ffa6f8a884d5?s=128

Nicolas Roard

November 15, 2017
Tweet

Transcript

  1. ConstraintLayout & Advanced Animations Nicolas Roard @camaelon John Hoford @johnhoford

  2. Overview

  3. Android Studio

  4. ConstraintLayout

  5. Where are we now

  6. Where are we now Android Studio 3.0 just shipped!

  7. Where are we now Android Studio 3.0 just shipped! ConstraintLayout

    1.0.2 stable
  8. Where are we now Android Studio 3.0 just shipped! ConstraintLayout

    1.0.2 stable ConstraintLayout 1.1 beta 3
  9. ConstraintLayout

  10. 1.0 Relative positioning Center positioning & bias Guidelines - helper

    objects Chains Dimension constraints: min/max, Ratio ConstraintSet
  11. ConstraintLayout Relative positioning

  12. ConstraintLayout Relative positioning

  13. ConstraintLayout Center positioning & bias

  14. ConstraintLayout Center positioning & bias

  15. ConstraintLayout Ratio

  16. ConstraintLayout Ratio

  17. B Chains A Chain bi-directional constraints

  18. Different Chain Styles A B C Spread A B C

    Spread Inside A B C Weighted A B C Packed
  19. Different Chain Styles A B C Spread A B C

    Spread Inside A B C Weighted A B C Packed + bias
  20. ConstraintLayout Chains

  21. ConstraintLayout Chains

  22. Guidelines

  23. Guidelines

  24. Gone Behavior

  25. Gone Behavior

  26. Inadapted Margin

  27. Inadapted Margin

  28. Gone Margins

  29. Gone Margins

  30. Example

  31. Example

  32. Barriers Groups : apply visibility to a set of widgets

    Placeholder Percent Dimensions Circular Constraints beta 3 1.1.0
  33. Barriers Groups : apply visibility to a set of widgets

    Placeholder Percent Dimensions Circular Constraints beta 3 1.1.0
  34. Barriers Groups : apply visibility to a set of widgets

    Placeholder Percent Dimensions Circular Constraints beta 3 1.1.0
  35. Barriers

  36. Barriers

  37. Barriers

  38. Barriers 1 2 3 4

  39. Barriers

  40. Barriers

  41. Barriers

  42. Barriers <android.support.constraint.Barrier android:id="@+id/barrier" android:layout_width="wrap_content" android:layout_height="wrap_content" app:barrierDirection=“end” app:constraint_referenced_ids="textView1,textView2,textView3" />

  43. Barriers Groups : apply visibility to a set of widgets

    Placeholder Percent Dimensions Circular Constraints beta 3 1.1.0
  44. Groups textview2 textview3

  45. Groups <android.support.constraint.Group android:id="@+id/group2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:visibility="visible" app:constraint_referenced_ids="textView2,textView3" />

  46. Groups Invisible Gone

  47. Barriers Groups : apply visibility to a set of widgets

    Placeholder Percent Dimensions Circular Constraints beta 3 1.1.0 ?
  48. class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) {

    super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) } fun select(v: View) { TransitionManager.beginDelayedTransition(main_layout) placeholder.setContentId(v.id) main_title.text= v.tag as CharSequence?;""; } } Placeholder
  49. class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) {

    super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) } fun select(v: View) { TransitionManager.beginDelayedTransition(main_layout) placeholder.setContentId(v.id) main_title.text= v.tag as CharSequence?;""; } } Placeholder
  50. Barriers Groups : apply visibility to a set of widgets

    Placeholder Percent Dimensions Circular Constraints beta 3 1.1.0
  51. Percent Dimension • width or height set to 0dp (“match_constraint”)

    • relative to the container • control behavior with: • app:layout_constraintWidth_percent=“0.20” • app:layout_constraintHeight_percent=“0.20”
  52. Barriers Groups : apply visibility to a set of widgets

    Placeholder Percent Dimensions Circular Constraints beta 3 1.1.0
  53. Circular Constraints <Button android:id="@+id/buttonA" ... /> <Button android:id="@+id/buttonB" … app:layout_constraintCircle=“@+id/buttonA"

    app:layout_constraintCircleRadius=“100dp" app:layout_constraintCircleAngle="45" />
  54. Example Andrew Kelly https://medium.com/devnibbles/constraintlayout-circular- positioning-9489b11cb0e5

  55. Example Andrew Kelly https://medium.com/devnibbles/constraintlayout-circular- positioning-9489b11cb0e5

  56. beta 3 1.1.0 dependencies { compile 'com.android.support.constraint:constraint-layout:1.1.0-beta3' }

  57. Case Studies

  58. Case 1

  59. Case 1 1 2 3

  60. Case 1

  61. Case 2

  62. Case 2

  63. Case 2

  64. Case 2

  65. Case 2

  66. Case 2 1. Center connection 2. Bias set to 0

    3. app:layout_constrainedWidth=“true”
  67. Case 2

  68. Case 3

  69. Case 3 1. Horizontal chain between (1) and (2) 2.

    Chain is a packed chain 3. Horizontal bias is set to 0 4. (1) has app:layout_constrainedWidth=“true”
  70. Case 3 1. Horizontal chain between (1) and (2) 2.

    Chain is a packed chain 3. Horizontal bias is set to 0 4. (1) has app:layout_constrainedWidth=“true” 1 2
  71. Case 4 • Flat layouts are good, but components still

    make sense!
  72. Case 4 • Using nested ConstraintLayout can be a powerful

    layout tool • Think: layout components
  73. Direct Manipulation

  74. Direct Manipulation Change parameters 1

  75. Direct Manipulation Change parameters 1 Use TransitionManager 2

  76. Direct Manipulation //Find the view you are over and set

    minimum height View child = layout.getChildAt(current); B
  77. Direct Manipulation //Find the view you are over and set

    minimum height View child = layout.getChildAt(current); B child.setMinimumHeight(400); C
  78. Direct Manipulation //Find the view you are over and set

    minimum height TransitionManager.beginDelayedTransition(layout); A View child = layout.getChildAt(current); B child.setMinimumHeight(400); C
  79. Direct Manipulation //Find the view you are over and set

    minimum height TransitionManager.beginDelayedTransition(layout); A View child = layout.getChildAt(current); B child.setMinimumHeight(400); C
  80. Direct Manipulation //Find the view you are over and set

    minimum height TransitionManager.beginDelayedTransition(layout); A View child = layout.getChildAt(current); B child.setMinimumHeight(400); C
  81. Use of GONE

  82. Use of GONE

  83. More examples Huyen Tue Dao’s talk at ChicagoRoboto: Cool ConstraintLayout

    https://speakerdeck.com/queencodemonkey/chicago-roboto-2017-cool- constraintlayout http://chicagoroboto.com/session-videos/
  84. Guideline Manipulation

  85. Direct Manipulation : Guideline

  86. Direct Manipulation : Guideline Guideline are powerful elements

  87. Direct Manipulation : Guideline Guideline are powerful elements Specify exact

    positioning or percent value
  88. Direct Manipulation : Guideline Guideline are powerful elements Specify exact

    positioning or percent value Parameterize a layout via guidelines
  89. Guidelines can be your Layout Parameters

  90. None
  91. final Guideline guideline = findViewById(R.id.guideline);

  92. final Guideline guideline = findViewById(R.id.guideline); final int end = ((LayoutParams)

    guideline.getLayoutParams()).guideEnd;
  93. final Guideline guideline = findViewById(R.id.guideline); final int end = ((LayoutParams)

    guideline.getLayoutParams()).guideEnd; ValueAnimator anim = ValueAnimator.ofInt(0, end); anim.addUpdateListener((animator) -> { }); anim.start();
  94. final Guideline guideline = findViewById(R.id.guideline); final int end = ((LayoutParams)

    guideline.getLayoutParams()).guideEnd; ValueAnimator anim = ValueAnimator.ofInt(0, end); anim.addUpdateListener((animator) -> { LayoutParams lp = (LayoutParams) guideline.getLayoutParams(); lp.guideEnd = (Integer) animator.getAnimatedValue(); guideline.setLayoutParams(lp); }); anim.start();
  95. final Guideline guideline = findViewById(R.id.guideline); final int end = ((LayoutParams)

    guideline.getLayoutParams()).guideEnd; ValueAnimator anim = ValueAnimator.ofInt(0, end); anim.setDuration(2000); anim.setInterpolator(new BounceInterpolator()); anim.addUpdateListener((animator) -> { LayoutParams lp = (LayoutParams) guideline.getLayoutParams(); lp.guideEnd = (Integer) animator.getAnimatedValue(); guideline.setLayoutParams(lp); }); anim.start();
  96. final Guideline guideline = findViewById(R.id.guideline); final int end = ((LayoutParams)

    guideline.getLayoutParams()).guideEnd; ObjectAnimator anim = ObjectAnimator.ofInt(g, "GuidelineEnd", 0, end); anim.setDuration(2000); anim.setInterpolator(new BounceInterpolator()); anim.start(); New in 1.1
  97. None
  98. None
  99. Parallax Effect

  100. None
  101. base.setOnTouchListener(new View.OnTouchListener() { public boolean onTouch(View view, MotionEvent motionEvent) {

    switch (motionEvent.getActionMasked( )) { case MotionEvent.ACTION_MOVE: break; } A return true; } B });
  102. base.setOnTouchListener(new View.OnTouchListener() { public boolean onTouch(View view, MotionEvent motionEvent) {

    switch (motionEvent.getActionMasked( )) { case MotionEvent.ACTION_MOVE: LayoutParams params = (LayoutParams) guideline.getLayoutParams(); params.guideBegin = (int) motionEvent.getX(); guideline.setLayoutParams(params); break; } A return true; } B });
  103. Guideline Manipulation

  104. Guideline Manipulation

  105. Parallax Effect

  106. Parallax Effect

  107. Parallax Effect

  108. recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() { override fun onScrolled(recyclerView: RecyclerView?, dx: Int,

    dy: Int) { super.onScrolled(recyclerView, dx, dy) a} })
  109. recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() { override fun onScrolled(recyclerView: RecyclerView?, dx: Int,

    dy: Int) { super.onScrolled(recyclerView, dx, dy) val manager = recyclerView!!.layoutManager as LinearLayoutManager val position = manager.findFirstVisibleItemPosition() val lastPosition = manager.findLastVisibleItemPosition() val offsetX = recyclerView.computeHorizontalScrollOffset() for (i in 0..lastPosition - position) { val layout = manager.findViewByPosition(position + i) as ConstraintLayout val guideline = layout.findViewById<Guideline>(R.id.guideline) val params = guideline.layoutParams as ConstraintLayout.LayoutParams val w = recyclerView.width val deltaPos = offsetX - (position + i) * w val percent = deltaPos / w.toFloat() params.guidePercent = Math.max(0.3f, Math.min(0.7f, 0.5f - percent)) guideline.layoutParams = params } a} })
  110. Parallax Effect

  111. Parallax Effect

  112. recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() { override fun onScrolled(recyclerView: RecyclerView?, dx: Int,

    dy: Int) { super.onScrolled(recyclerView, dx, dy) val manager = recyclerView!!.layoutManager as LinearLayoutManager val position = manager.findFirstVisibleItemPosition() val lastPosition = manager.findLastVisibleItemPosition() val offsetX = recyclerView.computeHorizontalScrollOffset() for (i in 0..lastPosition - position) { val layout = manager.findViewByPosition(position + i) as ConstraintLayout val guideline = layout.findViewById<Guideline>(R.id.guideline) val params = guideline.layoutParams as ConstraintLayout.LayoutParams val w = recyclerView.width val deltaPos = offsetX - (position + i) * w val percent = deltaPos / w.toFloat() params.guidePercent = Math.max(0.3f, Math.min(0.7f, 0.5f - percent)) guideline.layoutParams = params } a} })
  113. recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() { override fun onScrolled(recyclerView: RecyclerView?, dx: Int,

    dy: Int) { super.onScrolled(recyclerView, dx, dy) val manager = recyclerView!!.layoutManager as LinearLayoutManager val position = manager.findFirstVisibleItemPosition() val lastPosition = manager.findLastVisibleItemPosition() val offsetX = recyclerView.computeHorizontalScrollOffset() for (i in 0..lastPosition - position) { val layout = manager.findViewByPosition(position + i) as ConstraintLayout val guideline = layout.findViewById<Guideline>(R.id.guideline) val params = guideline.layoutParams as ConstraintLayout.LayoutParams val w = recyclerView.width val deltaPos = offsetX - (position + i) * w val percent = deltaPos / w.toFloat() params.guidePercent = Math.max(0.3f, Math.min(0.7f, 0.5f + percent)) guideline.layoutParams = params } a} })
  114. Parallax Effect: inversion

  115. Parallax Effect: inversion

  116. ConstraintSet

  117. ConstraintSet Contains all your constraints

  118. ConstraintSet Contains all your constraints Programming API

  119. ConstraintSet Contains all your constraints Programming API Clone from live

    layout or XML
  120. Initializing a ConstraintSet Layout resource set.clone(context, R.layout.main) Live layout set.clone(findViewById(R.id.main))

    XML file set.load(context, R.xml.main)
  121. Applying a ConstraintSet set.applyTo(layout)

  122. ConstraintSet

  123. ConstraintSet ConstraintSet mConstraintSet1 = new ConstraintSet(); // create a Constraint

    Set ConstraintSet mConstraintSet2 = new ConstraintSet(); // create a Constraint Set Initialization
  124. ConstraintSet ConstraintSet mConstraintSet1 = new ConstraintSet(); // create a Constraint

    Set ConstraintSet mConstraintSet2 = new ConstraintSet(); // create a Constraint Set mConstraintSet2.clone(context, R.layout.state2); // get constraints from layout setContentView(R.layout.state1); mConstraintLayout = (ConstraintLayout) findViewById(R.id.activity_main); mConstraintSet1.clone(mConstraintLayout); // get constraints from ConstraintSet Initialization onCreate
  125. ConstraintSet ConstraintSet mConstraintSet1 = new ConstraintSet(); // create a Constraint

    Set ConstraintSet mConstraintSet2 = new ConstraintSet(); // create a Constraint Set mConstraintSet2.clone(context, R.layout.state2); // get constraints from layout setContentView(R.layout.state1); mConstraintLayout = (ConstraintLayout) findViewById(R.id.activity_main); mConstraintSet1.clone(mConstraintLayout); // get constraints from ConstraintSet mConstraintSet1.applyTo(mConstraintLayout); Initialization onCreate To change state
  126. ConstraintSet ConstraintSet mConstraintSet1 = new ConstraintSet(); // create a Constraint

    Set ConstraintSet mConstraintSet2 = new ConstraintSet(); // create a Constraint Set mConstraintSet2.clone(context, R.layout.state2); // get constraints from layout setContentView(R.layout.state1); mConstraintLayout = (ConstraintLayout) findViewById(R.id.activity_main); mConstraintSet1.clone(mConstraintLayout); // get constraints from ConstraintSet TransitionManager.beginDelayedTransition(mConstraintLayout); mConstraintSet1.applyTo(mConstraintLayout); Initialization onCreate To change state
  127. Build two layouts

  128. Build two layouts

  129. Build two layouts

  130. Build two layouts

  131. Add Custom Transition static public class MyTransition extends TransitionSet {

    { setDuration(1000); setOrdering(ORDERING_SEQUENTIAL); addTransition(new TransitionSet() { { addTransition(new Fade(Fade.OUT)); addTransition(new ChangeBounds()); } }); addTransition(new Fade(Fade.IN)); } } private void animate(ConstraintLayout cl, ConstraintSet set) { TransitionManager.beginDelayedTransition(cl, new MyTransition()); set.applyTo(cl); }
  132. Add Custom Transition static public class MyTransition extends TransitionSet {

    { setDuration(1000); setOrdering(ORDERING_SEQUENTIAL); addTransition(new TransitionSet() { { addTransition(new Fade(Fade.OUT)); addTransition(new ChangeBounds()); } }); addTransition(new Fade(Fade.IN)); } } private void animate(ConstraintLayout cl, ConstraintSet set) { TransitionManager.beginDelayedTransition(cl, new MyTransition()); set.applyTo(cl); }
  133. Custom Transition 2 static public class MyTransition extends TransitionSet {

    { setDuration(1000); setOrdering(ORDERING_SEQUENTIAL); addTransition(new TransitionSet() { { addTransition(new Fade(Fade.OUT)); addTransition(new ChangeBounds()); addTransition(new Fade(Fade.IN)); } }); } } private void animate(ConstraintLayout cl, ConstraintSet set) { TransitionManager.beginDelayedTransition(cl, new MyTransition()); set.applyTo(cl); }
  134. Custom Transition 2 static public class MyTransition extends TransitionSet {

    { setDuration(1000); setOrdering(ORDERING_SEQUENTIAL); addTransition(new TransitionSet() { { addTransition(new Fade(Fade.OUT)); addTransition(new ChangeBounds()); addTransition(new Fade(Fade.IN)); } }); } } private void animate(ConstraintLayout cl, ConstraintSet set) { TransitionManager.beginDelayedTransition(cl, new MyTransition()); set.applyTo(cl); }
  135. Add Custom Transition static public class MyTransition extends TransitionSet {

    { setDuration(1000); setOrdering(ORDERING_SEQUENTIAL); addTransition(new TransitionSet() { { addTransition(new Fade(Fade.OUT)); ChangeBounds move = new ChangeBounds(); move.setInterpolator(new BounceInterpolator()); addTransition(move); } }); addTransition(new Fade(Fade.IN)); } }
  136. Add Custom Transition static public class MyTransition extends TransitionSet {

    { setDuration(1000); setOrdering(ORDERING_SEQUENTIAL); addTransition(new TransitionSet() { { addTransition(new Fade(Fade.OUT)); ChangeBounds move = new ChangeBounds(); move.setInterpolator(new BounceInterpolator()); addTransition(move); } }); addTransition(new Fade(Fade.IN)); } }
  137. Add Custom Transition Very custom…

  138. Add Custom Transition Very custom…

  139. Collapsible Toolbar

  140. None
  141. None
  142. Collapsible Toolbar CoordinatorLayout AppBarLayout CollapsingToolbarLayout

  143. ConstraintLayout Collapsible Toolbar CoordinatorLayout AppBarLayout

  144. XML <android.support.design.widget.CoordinatorLayout … > <android.support.design.widget.AppBarLayout android:id="@+id/app_bar" android:layout_width="match_parent" android:layout_height="@dimen/app_bar_height" android:fitsSystemWindows=“false" …

    > <android.support.design.widget.CollapsingToolbarLayout android:id=“@+id/toolbar_layout" … > </android.support.design.widget.AppBarLayout> <include layout="@layout/content_scrolling" /> <android.support.design.widget.FloatingActionButton /> </android.support.design.widget.CoordinatorLayout>
  145. <android.support.design.widget.CoordinatorLayout … > <android.support.design.widget.AppBarLayout android:id="@+id/app_bar" android:layout_width="match_parent" android:layout_height="@dimen/app_bar_height" android:fitsSystemWindows=“false" … >

    <android.support.constraint.ConstraintLayout android:id=“@+id/toolbar_layout" … > </android.support.design.widget.AppBarLayout> <include layout="@layout/content_scrolling" /> <android.support.design.widget.FloatingActionButton /> </android.support.design.widget.CoordinatorLayout> XML
  146. <android.support.design.widget.CoordinatorLayout … > <android.support.design.widget.AppBarLayout android:id="@+id/app_bar" android:layout_width="match_parent" android:layout_height="@dimen/app_bar_height" android:fitsSystemWindows=“false" … >

    <include layout=“@+id/toolbar" /> </android.support.design.widget.AppBarLayout> <include layout="@layout/content_scrolling" /> <android.support.design.widget.FloatingActionButton /> </android.support.design.widget.CoordinatorLayout> XML
  147. None
  148. None
  149. None
  150. None
  151. XML <android.support.constraint.ConstraintLayout android:id="@+id/constraintToolbar" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true" android:minHeight="40dp" app:layout_scrollFlags="enterAlways|snap">

  152. XML <android.support.constraint.ConstraintLayout android:id="@+id/constraintToolbar" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true" android:minHeight="40dp" app:layout_scrollFlags="scroll|enterAlways|snap|exitUntilCollapsed">

  153. None
  154. None
  155. Using ConstraintSet …& Kotlin

  156. Open ConstraintSet Closed ConstraintSet open.xml closed.xml

  157. class NoAnimCollapsibleConstraintLayout : ConstraintLayout, AppBarLayout.OnOffsetChangedListener { constructor(context: Context) : this(context,

    null) constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0) constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {} private var mTransitionThreshold = 0.35f private var mLastPosition : Int = 0 private var mToolbarOpen = true private val mOpenToolbarSet: ConstraintSet = ConstraintSet() private val mCloseToolbarSet: ConstraintSet = ConstraintSet() override fun onAttachedToWindow() { super.onAttachedToWindow() if (parent is AppBarLayout) { var appBarLayout = parent as AppBarLayout appBarLayout.addOnOffsetChangedListener(this) mOpenToolbarSet.clone(context, R.layout.open) mCloseToolbarSet.clone(context, R.layout.close) } } override fun onOffsetChanged(appBarLayout: AppBarLayout?, verticalOffset: Int) { if (mLastPosition == verticalOffset) { return } mLastPosition = verticalOffset val progress = Math.abs(verticalOffset / appBarLayout?.getHeight()?.toFloat()!!) val params = getLayoutParams() as AppBarLayout.LayoutParams params.topMargin = -verticalOffset setLayoutParams(params) if (mToolbarOpen && progress > mTransitionThreshold) { mCloseToolbarSet.applyTo(this) mToolbarOpen = false } else if (!mToolbarOpen && progress < mTransitionThreshold) { mOpenToolbarSet.applyTo(this) mToolbarOpen = true } } }
  158. None
  159. None
  160. class NoAnimCollapsibleConstraintLayout : ConstraintLayout, AppBarLayout.OnOffsetChangedListener { constructor(context: Context) : this(context,

    null) constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0) constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {} private var mTransitionThreshold = 0.35f private var mLastPosition : Int = 0 private var mToolbarOpen = true private val mOpenToolbarSet: ConstraintSet = ConstraintSet() private val mCloseToolbarSet: ConstraintSet = ConstraintSet() override fun onAttachedToWindow() { super.onAttachedToWindow() if (parent is AppBarLayout) { var appBarLayout = parent as AppBarLayout appBarLayout.addOnOffsetChangedListener(this) mOpenToolbarSet.clone(context, R.layout.open) mCloseToolbarSet.clone(context, R.layout.close) } } override fun onOffsetChanged(appBarLayout: AppBarLayout?, verticalOffset: Int) { if (mLastPosition == verticalOffset) { return } mLastPosition = verticalOffset val progress = Math.abs(verticalOffset / appBarLayout?.getHeight()?.toFloat()!!) val params = getLayoutParams() as AppBarLayout.LayoutParams params.topMargin = -verticalOffset setLayoutParams(params) if (mToolbarOpen && progress > mTransitionThreshold) { mCloseToolbarSet.applyTo(this) mToolbarOpen = false } else if (!mToolbarOpen && progress < mTransitionThreshold) { mOpenToolbarSet.applyTo(this) mToolbarOpen = true } } } Subclass of ConstraintLayout Implements OnOffsetChangedListener 1 2
  161. class NoAnimCollapsibleConstraintLayout : ConstraintLayout, AppBarLayout.OnOffsetChangedListener { constructor(context: Context) : this(context,

    null) constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0) constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {} private var mTransitionThreshold = 0.35f private var mLastPosition : Int = 0 private var mToolbarOpen = true private val mOpenToolbarSet: ConstraintSet = ConstraintSet() private val mCloseToolbarSet: ConstraintSet = ConstraintSet() override fun onAttachedToWindow() { super.onAttachedToWindow() if (parent is AppBarLayout) { var appBarLayout = parent as AppBarLayout appBarLayout.addOnOffsetChangedListener(this) mOpenToolbarSet.clone(context, R.layout.open) mCloseToolbarSet.clone(context, R.layout.close) } } override fun onOffsetChanged(appBarLayout: AppBarLayout?, verticalOffset: Int) { if (mLastPosition == verticalOffset) { return } mLastPosition = verticalOffset val progress = Math.abs(verticalOffset / appBarLayout?.getHeight()?.toFloat()!!) val params = getLayoutParams() as AppBarLayout.LayoutParams params.topMargin = -verticalOffset setLayoutParams(params) if (mToolbarOpen && progress > mTransitionThreshold) { mCloseToolbarSet.applyTo(this) mToolbarOpen = false } else if (!mToolbarOpen && progress < mTransitionThreshold) { mOpenToolbarSet.applyTo(this) mToolbarOpen = true } } } appBarLayout.addOnOffsetChangedListener(this) mOpenToolbarSet.clone(context, R.layout.open) mCloseToolbarSet.clone(context, R.layout.close) Implements OnOffsetChangedListener 3 Load the ConstraintSet 4
  162. class NoAnimCollapsibleConstraintLayout : ConstraintLayout, AppBarLayout.OnOffsetChangedListener { constructor(context: Context) : this(context,

    null) constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0) constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {} private var mTransitionThreshold = 0.35f private var mLastPosition : Int = 0 private var mToolbarOpen = true private val mOpenToolbarSet: ConstraintSet = ConstraintSet() private val mCloseToolbarSet: ConstraintSet = ConstraintSet() override fun onAttachedToWindow() { super.onAttachedToWindow() if (parent is AppBarLayout) { var appBarLayout = parent as AppBarLayout appBarLayout.addOnOffsetChangedListener(this) mOpenToolbarSet.clone(context, R.layout.open) mCloseToolbarSet.clone(context, R.layout.close) } } override fun onOffsetChanged(appBarLayout: AppBarLayout?, verticalOffset: Int) { if (mLastPosition == verticalOffset) { return } mLastPosition = verticalOffset val progress = Math.abs(verticalOffset / appBarLayout?.getHeight()?.toFloat()!!) val params = getLayoutParams() as AppBarLayout.LayoutParams params.topMargin = -verticalOffset setLayoutParams(params) if (mToolbarOpen && progress > mTransitionThreshold) { mCloseToolbarSet.applyTo(this) mToolbarOpen = false } else if (!mToolbarOpen && progress < mTransitionThreshold) { mOpenToolbarSet.applyTo(this) mToolbarOpen = true } } } Offset the Content 5 val params = getLayoutParams() as AppBarLayout.LayoutParams params.topMargin = -verticalOffset setLayoutParams(params)
  163. class NoAnimCollapsibleConstraintLayout : ConstraintLayout, AppBarLayout.OnOffsetChangedListener { constructor(context: Context) : this(context,

    null) constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0) constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {} private var mTransitionThreshold = 0.35f private var mLastPosition : Int = 0 private var mToolbarOpen = true private val mOpenToolbarSet: ConstraintSet = ConstraintSet() private val mCloseToolbarSet: ConstraintSet = ConstraintSet() override fun onAttachedToWindow() { super.onAttachedToWindow() if (parent is AppBarLayout) { var appBarLayout = parent as AppBarLayout appBarLayout.addOnOffsetChangedListener(this) mOpenToolbarSet.clone(context, R.layout.open) mCloseToolbarSet.clone(context, R.layout.close) } } override fun onOffsetChanged(appBarLayout: AppBarLayout?, verticalOffset: Int) { if (mLastPosition == verticalOffset) { return } mLastPosition = verticalOffset val progress = Math.abs(verticalOffset / appBarLayout?.getHeight()?.toFloat()!!) val params = getLayoutParams() as AppBarLayout.LayoutParams params.topMargin = -verticalOffset setLayoutParams(params) if (mToolbarOpen && progress > mTransitionThreshold) { mCloseToolbarSet.applyTo(this) mToolbarOpen = false } else if (!mToolbarOpen && progress < mTransitionThreshold) { mOpenToolbarSet.applyTo(this) mToolbarOpen = true } } } Toggle the ConstraintSet 6 if (mToolbarOpen && progress > mTransitionThreshold) { mCloseToolbarSet.applyTo(this) mToolbarOpen = false } else if (!mToolbarOpen && progress < mTransitionThreshold) { mOpenToolbarSet.applyTo(this) mToolbarOpen = true }
  164. None
  165. None
  166. Adding some animation…

  167. Animating content

  168. class AnimationHelper(view : View){ var initialValue = 0 var target

    = view init { initialValue = target.left } fun evaluate() { if (initialValue != target.left) { var delta = (initialValue - target.left).toFloat() val anim = ObjectAnimator.ofFloat(target, "translationX", delta, 0f) anim.duration = 400 anim.start() initialValue = target.left } } }
  169. class AnimationHelper(view : View){ var initialValue = 0 var target

    = view init { initialValue = target.left } fun evaluate() { if (initialValue != target.left) { var delta = (initialValue - target.left).toFloat() val anim = ObjectAnimator.ofFloat(target, "translationX", delta, 0f) anim.duration = 400 anim.start() initialValue = target.left } } }
  170. class AnimationHelper(view : View){ var initialValue = 0 var target

    = view init { initialValue = target.left } fun evaluate() { if (initialValue != target.left) { var delta = (initialValue - target.left).toFloat() val anim = ObjectAnimator.ofFloat(target, "translationX", delta, 0f) anim.duration = 400 anim.start() initialValue = target.left } } }
  171. class AnimationHelper(view : View){ var initialValue = 0 var target

    = view init { initialValue = target.left } fun evaluate() { if (initialValue != target.left) { var delta = (initialValue - target.left).toFloat() val anim = ObjectAnimator.ofFloat(target, "translationX", delta, 0f) anim.duration = 400 anim.start() initialValue = target.left } } }
  172. Animating background

  173. Animating the alpha channel mBackground = findViewById(R.id.background) … showImageAnimator =

    ObjectAnimator.ofFloat(mBackground, "alpha", 0f, 1f) showImageAnimator?.duration = 600 … showImageAnimator?.start()
  174. Now all together!

  175. class CollapsibleConstraintLayout : ConstraintLayout, AppBarLayout.OnOffsetChangedListener { constructor(context: Context) : this(context,

    null) constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0) constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {} private var mLastPosition : Int = 0 private var mToolbarOpen = true private var mTransitionThreshold = 0.35f private val mOpenToolbarSet: ConstraintSet = ConstraintSet() private val mCloseToolbarSet: ConstraintSet = ConstraintSet() private var mBackground: ImageView? = null private var mTitle : TextView? = null private var mIcon : ImageView? = null private var mTranslationTitle : AnimationHelper? = null private var mTranslationIcon : AnimationHelper? = null private var showImageAnimator : Animator? = null private var hideImageAnimator : Animator? = null class AnimationHelper(view : View){ var initialValue = 0 var target = view init { initialValue = target.left } fun evaluate() { if (initialValue != target.left) { var delta = (initialValue - target.left).toFloat() val anim = ObjectAnimator.ofFloat(target, "translationX", delta, 0f) anim.duration = 400 anim.start() initialValue = target.left } } } override fun onAttachedToWindow() { super.onAttachedToWindow() if (false && parent is AppBarLayout) { var appBarLayout = parent as AppBarLayout appBarLayout.addOnOffsetChangedListener(this) mOpenToolbarSet.clone(context, R.layout.open) mCloseToolbarSet.clone(context, R.layout.close) mBackground = findViewById(R.id.background) mTitle = findViewById(R.id.name) mIcon = findViewById(R.id.icon) showImageAnimator = ObjectAnimator.ofFloat(mBackground, "alpha", 0f, 1f) showImageAnimator?.duration = 600 hideImageAnimator = ObjectAnimator.ofFloat(mBackground, "alpha", 1f, 0f) hideImageAnimator?.duration = 600 } } override fun onOffsetChanged(appBarLayout: AppBarLayout?, verticalOffset: Int) { if (mLastPosition == verticalOffset) { return } mLastPosition = verticalOffset val progress = Math.abs(verticalOffset / appBarLayout?.getHeight()?.toFloat()!!) val params = getLayoutParams() as AppBarLayout.LayoutParams params.topMargin = -verticalOffset setLayoutParams(params) if (mToolbarOpen && progress > mTransitionThreshold) { mCloseToolbarSet.applyTo(this) hideImageAnimator?.start() mToolbarOpen = false } else if (!mToolbarOpen && progress < mTransitionThreshold) { mOpenToolbarSet.applyTo(this) showImageAnimator?.start() mToolbarOpen = true } } override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { super.onLayout(changed, left, top, right, bottom) if (mTitle != null && mTranslationTitle == null) { mTranslationTitle = AnimationHelper(mTitle!!) } if (mIcon != null && mTranslationIcon == null) { mTranslationIcon = AnimationHelper(mIcon!!) } mTranslationTitle?.evaluate() mTranslationIcon?.evaluate() } } AnimationHelper 1
  176. class CollapsibleConstraintLayout : ConstraintLayout, AppBarLayout.OnOffsetChangedListener { constructor(context: Context) : this(context,

    null) constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0) constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {} private var mLastPosition : Int = 0 private var mToolbarOpen = true private var mTransitionThreshold = 0.35f private val mOpenToolbarSet: ConstraintSet = ConstraintSet() private val mCloseToolbarSet: ConstraintSet = ConstraintSet() private var mBackground: ImageView? = null private var mTitle : TextView? = null private var mIcon : ImageView? = null private var mTranslationTitle : AnimationHelper? = null private var mTranslationIcon : AnimationHelper? = null private var showImageAnimator : Animator? = null private var hideImageAnimator : Animator? = null class AnimationHelper(view : View){ var initialValue = 0 var target = view init { initialValue = target.left } fun evaluate() { if (initialValue != target.left) { var delta = (initialValue - target.left).toFloat() val anim = ObjectAnimator.ofFloat(target, "translationX", delta, 0f) anim.duration = 400 anim.start() initialValue = target.left } } } override fun onAttachedToWindow() { super.onAttachedToWindow() if (false && parent is AppBarLayout) { var appBarLayout = parent as AppBarLayout appBarLayout.addOnOffsetChangedListener(this) mOpenToolbarSet.clone(context, R.layout.open) mCloseToolbarSet.clone(context, R.layout.close) mBackground = findViewById(R.id.background) mTitle = findViewById(R.id.name) mIcon = findViewById(R.id.icon) showImageAnimator = ObjectAnimator.ofFloat(mBackground, "alpha", 0f, 1f) showImageAnimator?.duration = 600 hideImageAnimator = ObjectAnimator.ofFloat(mBackground, "alpha", 1f, 0f) hideImageAnimator?.duration = 600 } } override fun onOffsetChanged(appBarLayout: AppBarLayout?, verticalOffset: Int) { if (mLastPosition == verticalOffset) { return } mLastPosition = verticalOffset val progress = Math.abs(verticalOffset / appBarLayout?.getHeight()?.toFloat()!!) val params = getLayoutParams() as AppBarLayout.LayoutParams params.topMargin = -verticalOffset setLayoutParams(params) if (mToolbarOpen && progress > mTransitionThreshold) { mCloseToolbarSet.applyTo(this) hideImageAnimator?.start() mToolbarOpen = false } else if (!mToolbarOpen && progress < mTransitionThreshold) { mOpenToolbarSet.applyTo(this) showImageAnimator?.start() mToolbarOpen = true } } override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { super.onLayout(changed, left, top, right, bottom) if (mTitle != null && mTranslationTitle == null) { mTranslationTitle = AnimationHelper(mTitle!!) } if (mIcon != null && mTranslationIcon == null) { mTranslationIcon = AnimationHelper(mIcon!!) } mTranslationTitle?.evaluate() mTranslationIcon?.evaluate() } } onLayout 2 override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { super.onLayout(changed, left, top, right, bottom) if (mTitle != null && mTranslationTitle == null) { mTranslationTitle = AnimationHelper(mTitle!!) } if (mIcon != null && mTranslationIcon == null) { mTranslationIcon = AnimationHelper(mIcon!!) } mTranslationTitle?.evaluate() mTranslationIcon?.evaluate() }
  177. class CollapsibleConstraintLayout : ConstraintLayout, AppBarLayout.OnOffsetChangedListener { constructor(context: Context) : this(context,

    null) constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0) constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {} private var mLastPosition : Int = 0 private var mToolbarOpen = true private var mTransitionThreshold = 0.35f private val mOpenToolbarSet: ConstraintSet = ConstraintSet() private val mCloseToolbarSet: ConstraintSet = ConstraintSet() private var mBackground: ImageView? = null private var mTitle : TextView? = null private var mIcon : ImageView? = null private var mTranslationTitle : AnimationHelper? = null private var mTranslationIcon : AnimationHelper? = null private var showImageAnimator : Animator? = null private var hideImageAnimator : Animator? = null class AnimationHelper(view : View){ var initialValue = 0 var target = view init { initialValue = target.left } fun evaluate() { if (initialValue != target.left) { var delta = (initialValue - target.left).toFloat() val anim = ObjectAnimator.ofFloat(target, "translationX", delta, 0f) anim.duration = 400 anim.start() initialValue = target.left } } } override fun onAttachedToWindow() { super.onAttachedToWindow() if (false && parent is AppBarLayout) { var appBarLayout = parent as AppBarLayout appBarLayout.addOnOffsetChangedListener(this) mOpenToolbarSet.clone(context, R.layout.open) mCloseToolbarSet.clone(context, R.layout.close) mBackground = findViewById(R.id.background) mTitle = findViewById(R.id.name) mIcon = findViewById(R.id.icon) showImageAnimator = ObjectAnimator.ofFloat(mBackground, "alpha", 0f, 1f) showImageAnimator?.duration = 600 hideImageAnimator = ObjectAnimator.ofFloat(mBackground, "alpha", 1f, 0f) hideImageAnimator?.duration = 600 } } override fun onOffsetChanged(appBarLayout: AppBarLayout?, verticalOffset: Int) { if (mLastPosition == verticalOffset) { return } mLastPosition = verticalOffset val progress = Math.abs(verticalOffset / appBarLayout?.getHeight()?.toFloat()!!) val params = getLayoutParams() as AppBarLayout.LayoutParams params.topMargin = -verticalOffset setLayoutParams(params) if (mToolbarOpen && progress > mTransitionThreshold) { mCloseToolbarSet.applyTo(this) hideImageAnimator?.start() mToolbarOpen = false } else if (!mToolbarOpen && progress < mTransitionThreshold) { mOpenToolbarSet.applyTo(this) showImageAnimator?.start() mToolbarOpen = true } } override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { super.onLayout(changed, left, top, right, bottom) if (mTitle != null && mTranslationTitle == null) { mTranslationTitle = AnimationHelper(mTitle!!) } if (mIcon != null && mTranslationIcon == null) { mTranslationIcon = AnimationHelper(mIcon!!) } mTranslationTitle?.evaluate() mTranslationIcon?.evaluate() } } onLayout 2 override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { super.onLayout(changed, left, top, right, bottom) if (mTitle != null && mTranslationTitle == null) { mTranslationTitle = AnimationHelper(mTitle!!) } if (mIcon != null && mTranslationIcon == null) { mTranslationIcon = AnimationHelper(mIcon!!) } mTranslationTitle?.evaluate() mTranslationIcon?.evaluate() }
  178. class CollapsibleConstraintLayout : ConstraintLayout, AppBarLayout.OnOffsetChangedListener { constructor(context: Context) : this(context,

    null) constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0) constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {} private var mLastPosition : Int = 0 private var mToolbarOpen = true private var mTransitionThreshold = 0.35f private val mOpenToolbarSet: ConstraintSet = ConstraintSet() private val mCloseToolbarSet: ConstraintSet = ConstraintSet() private var mBackground: ImageView? = null private var mTitle : TextView? = null private var mIcon : ImageView? = null private var mTranslationTitle : AnimationHelper? = null private var mTranslationIcon : AnimationHelper? = null private var showImageAnimator : Animator? = null private var hideImageAnimator : Animator? = null class AnimationHelper(view : View){ var initialValue = 0 var target = view init { initialValue = target.left } fun evaluate() { if (initialValue != target.left) { var delta = (initialValue - target.left).toFloat() val anim = ObjectAnimator.ofFloat(target, "translationX", delta, 0f) anim.duration = 400 anim.start() initialValue = target.left } } } override fun onAttachedToWindow() { super.onAttachedToWindow() if (false && parent is AppBarLayout) { var appBarLayout = parent as AppBarLayout appBarLayout.addOnOffsetChangedListener(this) mOpenToolbarSet.clone(context, R.layout.open) mCloseToolbarSet.clone(context, R.layout.close) mBackground = findViewById(R.id.background) mTitle = findViewById(R.id.name) mIcon = findViewById(R.id.icon) showImageAnimator = ObjectAnimator.ofFloat(mBackground, "alpha", 0f, 1f) showImageAnimator?.duration = 600 hideImageAnimator = ObjectAnimator.ofFloat(mBackground, "alpha", 1f, 0f) hideImageAnimator?.duration = 600 } } override fun onOffsetChanged(appBarLayout: AppBarLayout?, verticalOffset: Int) { if (mLastPosition == verticalOffset) { return } mLastPosition = verticalOffset val progress = Math.abs(verticalOffset / appBarLayout?.getHeight()?.toFloat()!!) val params = getLayoutParams() as AppBarLayout.LayoutParams params.topMargin = -verticalOffset setLayoutParams(params) if (mToolbarOpen && progress > mTransitionThreshold) { mCloseToolbarSet.applyTo(this) hideImageAnimator?.start() mToolbarOpen = false } else if (!mToolbarOpen && progress < mTransitionThreshold) { mOpenToolbarSet.applyTo(this) showImageAnimator?.start() mToolbarOpen = true } } override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { super.onLayout(changed, left, top, right, bottom) if (mTitle != null && mTranslationTitle == null) { mTranslationTitle = AnimationHelper(mTitle!!) } if (mIcon != null && mTranslationIcon == null) { mTranslationIcon = AnimationHelper(mIcon!!) } mTranslationTitle?.evaluate() mTranslationIcon?.evaluate() } } onLayout 2 override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { super.onLayout(changed, left, top, right, bottom) if (mTitle != null && mTranslationTitle == null) { mTranslationTitle = AnimationHelper(mTitle!!) } if (mIcon != null && mTranslationIcon == null) { mTranslationIcon = AnimationHelper(mIcon!!) } mTranslationTitle?.evaluate() mTranslationIcon?.evaluate() }
  179. class CollapsibleConstraintLayout : ConstraintLayout, AppBarLayout.OnOffsetChangedListener { constructor(context: Context) : this(context,

    null) constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0) constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {} private var mLastPosition : Int = 0 private var mToolbarOpen = true private var mTransitionThreshold = 0.35f private val mOpenToolbarSet: ConstraintSet = ConstraintSet() private val mCloseToolbarSet: ConstraintSet = ConstraintSet() private var mBackground: ImageView? = null private var mTitle : TextView? = null private var mIcon : ImageView? = null private var mTranslationTitle : AnimationHelper? = null private var mTranslationIcon : AnimationHelper? = null private var showImageAnimator : Animator? = null private var hideImageAnimator : Animator? = null class AnimationHelper(view : View){ var initialValue = 0 var target = view init { initialValue = target.left } fun evaluate() { if (initialValue != target.left) { var delta = (initialValue - target.left).toFloat() val anim = ObjectAnimator.ofFloat(target, "translationX", delta, 0f) anim.duration = 400 anim.start() initialValue = target.left } } } override fun onAttachedToWindow() { super.onAttachedToWindow() if (false && parent is AppBarLayout) { var appBarLayout = parent as AppBarLayout appBarLayout.addOnOffsetChangedListener(this) mOpenToolbarSet.clone(context, R.layout.open) mCloseToolbarSet.clone(context, R.layout.close) mBackground = findViewById(R.id.background) mTitle = findViewById(R.id.name) mIcon = findViewById(R.id.icon) showImageAnimator = ObjectAnimator.ofFloat(mBackground, "alpha", 0f, 1f) showImageAnimator?.duration = 600 hideImageAnimator = ObjectAnimator.ofFloat(mBackground, "alpha", 1f, 0f) hideImageAnimator?.duration = 600 } } override fun onOffsetChanged(appBarLayout: AppBarLayout?, verticalOffset: Int) { if (mLastPosition == verticalOffset) { return } mLastPosition = verticalOffset val progress = Math.abs(verticalOffset / appBarLayout?.getHeight()?.toFloat()!!) val params = getLayoutParams() as AppBarLayout.LayoutParams params.topMargin = -verticalOffset setLayoutParams(params) if (mToolbarOpen && progress > mTransitionThreshold) { mCloseToolbarSet.applyTo(this) hideImageAnimator?.start() mToolbarOpen = false } else if (!mToolbarOpen && progress < mTransitionThreshold) { mOpenToolbarSet.applyTo(this) showImageAnimator?.start() mToolbarOpen = true } } override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { super.onLayout(changed, left, top, right, bottom) if (mTitle != null && mTranslationTitle == null) { mTranslationTitle = AnimationHelper(mTitle!!) } if (mIcon != null && mTranslationIcon == null) { mTranslationIcon = AnimationHelper(mIcon!!) } mTranslationTitle?.evaluate() mTranslationIcon?.evaluate() } } animating background alpha 3
  180. None
  181. None
  182. Collapsible Layout Summary • Use ConstraintLayout / ConstraintSet for flexibility

    • Piggy-back on CoordinatorLayout / AppBarLayout • Kotlin! • ObjectAnimator for property animation
  183. Helpers & Decorators

  184. Helpers ConstraintHelper

  185. Helpers ConstraintHelper View 1 View 2 View 3

  186. Helpers ConstraintHelper Barrier Group

  187. Example StaggeredAnimationGroup from Bartosz Lipiński https://github.com/blipinsk/StaggeredAnimationGroup

  188. Example StaggeredAnimationGroup from Bartosz Lipiński https://github.com/blipinsk/StaggeredAnimationGroup

  189. Decorators ConstraintHelper Decorator

  190. Decorator

  191. Decorator ConstraintHelper + View

  192. Decorator ConstraintHelper + View Gather draw operations in a single

    place
  193. Decorator ConstraintHelper + View Gather draw operations in a single

    place Draw something depending on multiple views
  194. Decorator public class MetaballsDecorator extends Decorator { public void updatePostLayout(ConstraintLayout

    container) { int[] ids = getReferencedIds(); final int count = ids.length; for (int i = 0; i < count; i++) { View view = container.getViewById(ids[i]); // do something } } @Override public void onDraw(Canvas canvas) { // do something } }
  195. Decorator Canvas ImageViews

  196. Decorator Canvas ImageViews

  197. Decorator Canvas ImageViews

  198. Decorator: Metaballs

  199. Decorator: Metaballs

  200. Decorator: Metaballs

  201. Decorator: Metaballs

  202. Let’s sum it all up…

  203. Let’s sum it all up… ConstraintLayout allows very flexible, declarative

    positioning
  204. Let’s sum it all up… ConstraintLayout allows very flexible, declarative

    positioning You can directly modify parameters to change your layout
  205. Let’s sum it all up… ConstraintLayout allows very flexible, declarative

    positioning You can directly modify parameters to change your layout Guidelines are a great entry point for customizing your layout
  206. Let’s sum it all up… ConstraintLayout allows very flexible, declarative

    positioning You can directly modify parameters to change your layout Guidelines are a great entry point for customizing your layout ConstraintSet allows you to easily manage multiple states of your layout
  207. Let’s sum it all up… ConstraintLayout allows very flexible, declarative

    positioning You can directly modify parameters to change your layout Guidelines are a great entry point for customizing your layout ConstraintSet allows you to easily manage multiple states of your layout TransitionManager will animate a lot for you automatically
  208. Let’s sum it all up… ConstraintLayout allows very flexible, declarative

    positioning You can directly modify parameters to change your layout Guidelines are a great entry point for customizing your layout ConstraintSet allows you to easily manage multiple states of your layout TransitionManager will animate a lot for you automatically Property animations still very useful!
  209. Let’s sum it all up… ConstraintLayout allows very flexible, declarative

    positioning You can directly modify parameters to change your layout Guidelines are a great entry point for customizing your layout ConstraintSet allows you to easily manage multiple states of your layout TransitionManager will animate a lot for you automatically Property animations still very useful! Decorators are great for capturing cross-views rendering
  210. Thank you! Nicolas Roard @camaelon John Hoford @johnhoford

  211. Documentation • http://www.constraintlayout.com • https://developer.android.com/reference/android/support/ constraint/package-summary.html • https://developer.android.com/training/constraint-layout/index.html • https://codelabs.developers.google.com/codelabs/constraint-layout

    • https://medium.com/google-developers/building-interfaces-with- constraintlayout-3958fa38a9f7
  212. Documentation • http://www.constraintlayout.com • https://developer.android.com/reference/android/support/ constraint/package-summary.html • https://developer.android.com/training/constraint-layout/index.html • https://codelabs.developers.google.com/codelabs/constraint-layout

    • https://medium.com/google-developers/building-interfaces-with- constraintlayout-3958fa38a9f7