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

Advanced Animations & ConstraintLayout

Advanced Animations & ConstraintLayout

Advanced animations with ConstraintLayout

Nicolas Roard

November 06, 2017
Tweet

More Decks by Nicolas Roard

Other Decks in Programming

Transcript

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

    View Slide

  2. Overview

    View Slide

  3. Android Studio

    View Slide

  4. ConstraintLayout

    View Slide

  5. Where are we now

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  9. ConstraintLayout

    View Slide

  10. ConstraintLayout
    Relative positioning

    View Slide

  11. ConstraintLayout
    Relative positioning

    View Slide

  12. ConstraintLayout
    Center positioning & bias

    View Slide

  13. ConstraintLayout
    Center positioning & bias

    View Slide

  14. ConstraintLayout
    Ratio

    View Slide

  15. ConstraintLayout
    Ratio

    View Slide

  16. ConstraintLayout
    Chains

    View Slide

  17. ConstraintLayout
    Chains

    View Slide

  18. ConstraintLayout
    Guidelines

    View Slide

  19. ConstraintLayout
    Guidelines

    View Slide

  20. Constraints Vocabulary
    • Relative positioning
    • Center + bias
    • Dimension (e.g. ratio)
    • Group behavior (chains)
    • Helper objects (guidelines)

    View Slide

  21. Direct Manipulation

    View Slide

  22. Direct Manipulation
    Change parameters
    1

    View Slide

  23. Direct Manipulation
    Change parameters
    1
    Use TransitionManager
    2

    View Slide

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

    View Slide

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

    View Slide

  26. 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

    View Slide

  27. 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

    View Slide

  28. 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

    View Slide

  29. Use of GONE

    View Slide

  30. Use of GONE

    View Slide

  31. 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/

    View Slide

  32. Guideline Manipulation

    View Slide

  33. Direct Manipulation : Guideline

    View Slide

  34. Direct Manipulation : Guideline
    Guideline are powerful elements

    View Slide

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

    View Slide

  36. Direct Manipulation : Guideline
    Guideline are powerful elements
    Specify exact positioning or percent value
    Parameterize a layout via guidelines

    View Slide

  37. Guidelines can be your
    Layout Parameters

    View Slide

  38. View Slide

  39. final Guideline guideline = findViewById(R.id.guideline);

    View Slide

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

    View Slide

  41. 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();

    View Slide

  42. 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();

    View Slide

  43. 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();

    View Slide

  44. 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

    View Slide

  45. View Slide

  46. View Slide

  47. Parallax Effect

    View Slide

  48. View Slide

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

    View Slide

  50. 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
    });

    View Slide

  51. Guideline Manipulation

    View Slide

  52. Guideline Manipulation

    View Slide

  53. Parallax Effect

    View Slide

  54. Parallax Effect

    View Slide

  55. Parallax Effect

    View Slide

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

    View Slide

  57. 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(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}
    })

    View Slide

  58. Parallax Effect

    View Slide

  59. Parallax Effect

    View Slide

  60. 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(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}
    })

    View Slide

  61. 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(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}
    })

    View Slide

  62. Parallax Effect: inversion

    View Slide

  63. Parallax Effect: inversion

    View Slide

  64. ConstraintSet

    View Slide

  65. ConstraintSet
    Contains all your constraints

    View Slide

  66. ConstraintSet
    Contains all your constraints
    Programming API

    View Slide

  67. ConstraintSet
    Contains all your constraints
    Programming API
    Clone from live layout or XML

    View Slide

  68. 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)

    View Slide

  69. Applying a ConstraintSet
    set.applyTo(layout)

    View Slide

  70. ConstraintSet

    View Slide

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

    View Slide

  72. 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

    View Slide

  73. 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

    View Slide

  74. 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

    View Slide

  75. Build two layouts

    View Slide

  76. Build two layouts

    View Slide

  77. Build two layouts

    View Slide

  78. Build two layouts

    View Slide

  79. 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);
    }

    View Slide

  80. 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);
    }

    View Slide

  81. 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);
    }

    View Slide

  82. 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);
    }

    View Slide

  83. 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));
    }
    }

    View Slide

  84. 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));
    }
    }

    View Slide

  85. Add Custom Transition
    Very custom…

    View Slide

  86. Add Custom Transition
    Very custom…

    View Slide

  87. Collapsible Toolbar

    View Slide

  88. View Slide

  89. View Slide

  90. Collapsible Toolbar
    CoordinatorLayout
    AppBarLayout
    CollapsingToolbarLayout

    View Slide

  91. ConstraintLayout
    Collapsible Toolbar
    CoordinatorLayout
    AppBarLayout

    View Slide

  92. XML

    android:id="@+id/app_bar"
    android:layout_width="match_parent"
    android:layout_height="@dimen/app_bar_height"
    android:fitsSystemWindows=“false" … >
    android:id=“@+id/toolbar_layout" … >




    View Slide


  93. android:id="@+id/app_bar"
    android:layout_width="match_parent"
    android:layout_height="@dimen/app_bar_height"
    android:fitsSystemWindows=“false" … >
    android:id=“@+id/toolbar_layout" … >




    XML

    View Slide


  94. android:id="@+id/app_bar"
    android:layout_width="match_parent"
    android:layout_height="@dimen/app_bar_height"
    android:fitsSystemWindows=“false" … >





    XML

    View Slide

  95. View Slide

  96. View Slide

  97. View Slide

  98. View Slide

  99. XML
    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">

    View Slide

  100. XML
    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">

    View Slide

  101. View Slide

  102. View Slide

  103. Using ConstraintSet
    …& Kotlin

    View Slide

  104. Open ConstraintSet Closed ConstraintSet
    open.xml closed.xml

    View Slide

  105. 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
    }
    }
    }

    View Slide

  106. View Slide

  107. View Slide

  108. 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

    View Slide

  109. 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

    View Slide

  110. 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)

    View Slide

  111. 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
    }

    View Slide

  112. View Slide

  113. View Slide

  114. Adding some animation…

    View Slide

  115. Animating content

    View Slide

  116. 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
    }
    }
    }

    View Slide

  117. 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
    }
    }
    }

    View Slide

  118. 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
    }
    }
    }

    View Slide

  119. 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
    }
    }
    }

    View Slide

  120. Animating background

    View Slide

  121. Animating the alpha channel
    mBackground = findViewById(R.id.background)

    showImageAnimator = ObjectAnimator.ofFloat(mBackground, "alpha", 0f, 1f)
    showImageAnimator?.duration = 600

    showImageAnimator?.start()

    View Slide

  122. Now all together!

    View Slide

  123. 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

    View Slide

  124. 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()
    }

    View Slide

  125. 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()
    }

    View Slide

  126. 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()
    }

    View Slide

  127. 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

    View Slide

  128. View Slide

  129. View Slide

  130. Collapsible Layout Summary
    • Use ConstraintLayout / ConstraintSet for flexibility
    • Piggy-back on CoordinatorLayout / AppBarLayout
    • Kotlin!
    • ObjectAnimator for property animation

    View Slide

  131. Helpers & Decorators

    View Slide

  132. Helpers
    ConstraintHelper

    View Slide

  133. Helpers
    ConstraintHelper
    View 1
    View 2
    View 3

    View Slide

  134. Helpers
    ConstraintHelper
    Barrier Group

    View Slide

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

    View Slide

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

    View Slide

  137. Decorators
    ConstraintHelper
    Decorator

    View Slide

  138. Decorator

    View Slide

  139. Decorator
    ConstraintHelper + View

    View Slide

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

    View Slide

  141. Decorator
    ConstraintHelper + View
    Gather draw operations in a single place
    Draw something depending on multiple views

    View Slide

  142. 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
    }
    }

    View Slide

  143. Decorator
    Canvas
    ImageViews

    View Slide

  144. Decorator
    Canvas
    ImageViews

    View Slide

  145. Decorator
    Canvas
    ImageViews

    View Slide

  146. Decorator:
    Metaballs

    View Slide

  147. Decorator:
    Metaballs

    View Slide

  148. Decorator:
    Metaballs

    View Slide

  149. Decorator:
    Metaballs

    View Slide

  150. Let’s sum it all up…

    View Slide

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

    View Slide

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

    View Slide

  153. 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

    View Slide

  154. 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

    View Slide

  155. 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

    View Slide

  156. 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!

    View Slide

  157. 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

    View Slide

  158. Thank you!
    Nicolas Roard
    @camaelon
    John Hoford
    @johnhoford
    .droidconSF 2017

    View Slide

  159. 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

    View Slide

  160. 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

    View Slide