1/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.support.design.widget;
18
19import android.content.Context;
20import android.content.res.TypedArray;
21import android.os.Build;
22import android.os.Parcel;
23import android.os.Parcelable;
24import android.support.annotation.IntDef;
25import android.support.annotation.NonNull;
26import android.support.annotation.Nullable;
27import android.support.annotation.VisibleForTesting;
28import android.support.design.R;
29import android.support.v4.os.ParcelableCompat;
30import android.support.v4.os.ParcelableCompatCreatorCallbacks;
31import android.support.v4.view.AbsSavedState;
32import android.support.v4.view.ViewCompat;
33import android.support.v4.view.WindowInsetsCompat;
34import android.util.AttributeSet;
35import android.view.View;
36import android.view.ViewGroup;
37import android.view.animation.Interpolator;
38import android.widget.LinearLayout;
39
40import java.lang.annotation.Retention;
41import java.lang.annotation.RetentionPolicy;
42import java.lang.ref.WeakReference;
43import java.util.ArrayList;
44import java.util.List;
45
46/**
47 * AppBarLayout is a vertical {@link LinearLayout} which implements many of the features of
48 * material designs app bar concept, namely scrolling gestures.
49 * <p>
50 * Children should provide their desired scrolling behavior through
51 * {@link LayoutParams#setScrollFlags(int)} and the associated layout xml attribute:
52 * {@code app:layout_scrollFlags}.
53 *
54 * <p>
55 * This view depends heavily on being used as a direct child within a {@link CoordinatorLayout}.
56 * If you use AppBarLayout within a different {@link ViewGroup}, most of it's functionality will
57 * not work.
58 * <p>
59 * AppBarLayout also requires a separate scrolling sibling in order to know when to scroll.
60 * The binding is done through the {@link ScrollingViewBehavior} behavior class, meaning that you
61 * should set your scrolling view's behavior to be an instance of {@link ScrollingViewBehavior}.
62 * A string resource containing the full class name is available.
63 *
64 * <pre>
65 * &lt;android.support.design.widget.CoordinatorLayout
66 *         xmlns:android=&quot;http://schemas.android.com/apk/res/android";
67 *         xmlns:app=&quot;http://schemas.android.com/apk/res-auto";
68 *         android:layout_width=&quot;match_parent&quot;
69 *         android:layout_height=&quot;match_parent&quot;&gt;
70 *
71 *     &lt;android.support.v4.widget.NestedScrollView
72 *             android:layout_width=&quot;match_parent&quot;
73 *             android:layout_height=&quot;match_parent&quot;
74 *             app:layout_behavior=&quot;@string/appbar_scrolling_view_behavior&quot;&gt;
75 *
76 *         &lt;!-- Your scrolling content --&gt;
77 *
78 *     &lt;/android.support.v4.widget.NestedScrollView&gt;
79 *
80 *     &lt;android.support.design.widget.AppBarLayout
81 *             android:layout_height=&quot;wrap_content&quot;
82 *             android:layout_width=&quot;match_parent&quot;&gt;
83 *
84 *         &lt;android.support.v7.widget.Toolbar
85 *                 ...
86 *                 app:layout_scrollFlags=&quot;scroll|enterAlways&quot;/&gt;
87 *
88 *         &lt;android.support.design.widget.TabLayout
89 *                 ...
90 *                 app:layout_scrollFlags=&quot;scroll|enterAlways&quot;/&gt;
91 *
92 *     &lt;/android.support.design.widget.AppBarLayout&gt;
93 *
94 * &lt;/android.support.design.widget.CoordinatorLayout&gt;
95 * </pre>
96 *
97 * @see <a href="http://www.google.com/design/spec/layout/structure.html#structure-app-bar">
98 *     http://www.google.com/design/spec/layout/structure.html#structure-app-bar</a>
99 */
100@CoordinatorLayout.DefaultBehavior(AppBarLayout.Behavior.class)
101public class AppBarLayout extends LinearLayout {
102
103    private static final int PENDING_ACTION_NONE = 0x0;
104    private static final int PENDING_ACTION_EXPANDED = 0x1;
105    private static final int PENDING_ACTION_COLLAPSED = 0x2;
106    private static final int PENDING_ACTION_ANIMATE_ENABLED = 0x4;
107
108    /**
109     * Interface definition for a callback to be invoked when an {@link AppBarLayout}'s vertical
110     * offset changes.
111     */
112    public interface OnOffsetChangedListener {
113        /**
114         * Called when the {@link AppBarLayout}'s layout offset has been changed. This allows
115         * child views to implement custom behavior based on the offset (for instance pinning a
116         * view at a certain y value).
117         *
118         * @param appBarLayout the {@link AppBarLayout} which offset has changed
119         * @param verticalOffset the vertical offset for the parent {@link AppBarLayout}, in px
120         */
121        void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset);
122    }
123
124    private static final int INVALID_SCROLL_RANGE = -1;
125
126    private int mTotalScrollRange = INVALID_SCROLL_RANGE;
127    private int mDownPreScrollRange = INVALID_SCROLL_RANGE;
128    private int mDownScrollRange = INVALID_SCROLL_RANGE;
129
130    private boolean mHaveChildWithInterpolator;
131
132    private int mPendingAction = PENDING_ACTION_NONE;
133
134    private WindowInsetsCompat mLastInsets;
135
136    private List<OnOffsetChangedListener> mListeners;
137
138    private boolean mCollapsible;
139    private boolean mCollapsed;
140
141    private final int[] mTmpStatesArray = new int[2];
142
143    public AppBarLayout(Context context) {
144        this(context, null);
145    }
146
147    public AppBarLayout(Context context, AttributeSet attrs) {
148        super(context, attrs);
149        setOrientation(VERTICAL);
150
151        ThemeUtils.checkAppCompatTheme(context);
152
153        if (Build.VERSION.SDK_INT >= 21) {
154            // Use the bounds view outline provider so that we cast a shadow, even without a
155            // background
156            ViewUtilsLollipop.setBoundsViewOutlineProvider(this);
157
158            // If we're running on API 21+, we should reset any state list animator from our
159            // default style
160            ViewUtilsLollipop.setStateListAnimatorFromAttrs(this, attrs, 0,
161                    R.style.Widget_Design_AppBarLayout);
162        }
163
164        final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.AppBarLayout,
165                0, R.style.Widget_Design_AppBarLayout);
166        setBackgroundDrawable(a.getDrawable(R.styleable.AppBarLayout_android_background));
167        if (a.hasValue(R.styleable.AppBarLayout_expanded)) {
168            setExpanded(a.getBoolean(R.styleable.AppBarLayout_expanded, false));
169        }
170        if (Build.VERSION.SDK_INT >= 21 && a.hasValue(R.styleable.AppBarLayout_elevation)) {
171            ViewUtilsLollipop.setDefaultAppBarLayoutStateListAnimator(
172                    this, a.getDimensionPixelSize(R.styleable.AppBarLayout_elevation, 0));
173        }
174        a.recycle();
175
176        ViewCompat.setOnApplyWindowInsetsListener(this,
177                new android.support.v4.view.OnApplyWindowInsetsListener() {
178                    @Override
179                    public WindowInsetsCompat onApplyWindowInsets(View v,
180                            WindowInsetsCompat insets) {
181                        return onWindowInsetChanged(insets);
182                    }
183                });
184    }
185
186    /**
187     * Add a listener that will be called when the offset of this {@link AppBarLayout} changes.
188     *
189     * @param listener The listener that will be called when the offset changes.]
190     *
191     * @see #removeOnOffsetChangedListener(OnOffsetChangedListener)
192     */
193    public void addOnOffsetChangedListener(OnOffsetChangedListener listener) {
194        if (mListeners == null) {
195            mListeners = new ArrayList<>();
196        }
197        if (listener != null && !mListeners.contains(listener)) {
198            mListeners.add(listener);
199        }
200    }
201
202    /**
203     * Remove the previously added {@link OnOffsetChangedListener}.
204     *
205     * @param listener the listener to remove.
206     */
207    public void removeOnOffsetChangedListener(OnOffsetChangedListener listener) {
208        if (mListeners != null && listener != null) {
209            mListeners.remove(listener);
210        }
211    }
212
213    @Override
214    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
215        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
216        invalidateScrollRanges();
217    }
218
219    @Override
220    protected void onLayout(boolean changed, int l, int t, int r, int b) {
221        super.onLayout(changed, l, t, r, b);
222        invalidateScrollRanges();
223
224        mHaveChildWithInterpolator = false;
225        for (int i = 0, z = getChildCount(); i < z; i++) {
226            final View child = getChildAt(i);
227            final LayoutParams childLp = (LayoutParams) child.getLayoutParams();
228            final Interpolator interpolator = childLp.getScrollInterpolator();
229
230            if (interpolator != null) {
231                mHaveChildWithInterpolator = true;
232                break;
233            }
234        }
235
236        updateCollapsible();
237    }
238
239    private void updateCollapsible() {
240        boolean haveCollapsibleChild = false;
241        for (int i = 0, z = getChildCount(); i < z; i++) {
242            if (((LayoutParams) getChildAt(i).getLayoutParams()).isCollapsible()) {
243                haveCollapsibleChild = true;
244                break;
245            }
246        }
247        setCollapsibleState(haveCollapsibleChild);
248    }
249
250    private void invalidateScrollRanges() {
251        // Invalidate the scroll ranges
252        mTotalScrollRange = INVALID_SCROLL_RANGE;
253        mDownPreScrollRange = INVALID_SCROLL_RANGE;
254        mDownScrollRange = INVALID_SCROLL_RANGE;
255    }
256
257    @Override
258    public void setOrientation(int orientation) {
259        if (orientation != VERTICAL) {
260            throw new IllegalArgumentException("AppBarLayout is always vertical and does"
261                    + " not support horizontal orientation");
262        }
263        super.setOrientation(orientation);
264    }
265
266    /**
267     * Sets whether this {@link AppBarLayout} is expanded or not, animating if it has already
268     * been laid out.
269     *
270     * <p>As with {@link AppBarLayout}'s scrolling, this method relies on this layout being a
271     * direct child of a {@link CoordinatorLayout}.</p>
272     *
273     * @param expanded true if the layout should be fully expanded, false if it should
274     *                 be fully collapsed
275     *
276     * @attr ref android.support.design.R.styleable#AppBarLayout_expanded
277     */
278    public void setExpanded(boolean expanded) {
279        setExpanded(expanded, ViewCompat.isLaidOut(this));
280    }
281
282    /**
283     * Sets whether this {@link AppBarLayout} is expanded or not.
284     *
285     * <p>As with {@link AppBarLayout}'s scrolling, this method relies on this layout being a
286     * direct child of a {@link CoordinatorLayout}.</p>
287     *
288     * @param expanded true if the layout should be fully expanded, false if it should
289     *                 be fully collapsed
290     * @param animate Whether to animate to the new state
291     *
292     * @attr ref android.support.design.R.styleable#AppBarLayout_expanded
293     */
294    public void setExpanded(boolean expanded, boolean animate) {
295        mPendingAction = (expanded ? PENDING_ACTION_EXPANDED : PENDING_ACTION_COLLAPSED)
296                | (animate ? PENDING_ACTION_ANIMATE_ENABLED : 0);
297        requestLayout();
298    }
299
300    @Override
301    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
302        return p instanceof LayoutParams;
303    }
304
305    @Override
306    protected LayoutParams generateDefaultLayoutParams() {
307        return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
308    }
309
310    @Override
311    public LayoutParams generateLayoutParams(AttributeSet attrs) {
312        return new LayoutParams(getContext(), attrs);
313    }
314
315    @Override
316    protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
317        if (p instanceof LinearLayout.LayoutParams) {
318            return new LayoutParams((LinearLayout.LayoutParams) p);
319        } else if (p instanceof MarginLayoutParams) {
320            return new LayoutParams((MarginLayoutParams) p);
321        }
322        return new LayoutParams(p);
323    }
324
325    private boolean hasChildWithInterpolator() {
326        return mHaveChildWithInterpolator;
327    }
328
329    /**
330     * Returns the scroll range of all children.
331     *
332     * @return the scroll range in px
333     */
334    public final int getTotalScrollRange() {
335        if (mTotalScrollRange != INVALID_SCROLL_RANGE) {
336            return mTotalScrollRange;
337        }
338
339        int range = 0;
340        for (int i = 0, z = getChildCount(); i < z; i++) {
341            final View child = getChildAt(i);
342            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
343            final int childHeight = child.getMeasuredHeight();
344            final int flags = lp.mScrollFlags;
345
346            if ((flags & LayoutParams.SCROLL_FLAG_SCROLL) != 0) {
347                // We're set to scroll so add the child's height
348                range += childHeight + lp.topMargin + lp.bottomMargin;
349
350                if ((flags & LayoutParams.SCROLL_FLAG_EXIT_UNTIL_COLLAPSED) != 0) {
351                    // For a collapsing scroll, we to take the collapsed height into account.
352                    // We also break straight away since later views can't scroll beneath
353                    // us
354                    range -= ViewCompat.getMinimumHeight(child);
355                    break;
356                }
357            } else {
358                // As soon as a view doesn't have the scroll flag, we end the range calculation.
359                // This is because views below can not scroll under a fixed view.
360                break;
361            }
362        }
363        return mTotalScrollRange = Math.max(0, range - getTopInset());
364    }
365
366    private boolean hasScrollableChildren() {
367        return getTotalScrollRange() != 0;
368    }
369
370    /**
371     * Return the scroll range when scrolling up from a nested pre-scroll.
372     */
373    private int getUpNestedPreScrollRange() {
374        return getTotalScrollRange();
375    }
376
377    /**
378     * Return the scroll range when scrolling down from a nested pre-scroll.
379     */
380    private int getDownNestedPreScrollRange() {
381        if (mDownPreScrollRange != INVALID_SCROLL_RANGE) {
382            // If we already have a valid value, return it
383            return mDownPreScrollRange;
384        }
385
386        int range = 0;
387        for (int i = getChildCount() - 1; i >= 0; i--) {
388            final View child = getChildAt(i);
389            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
390            final int childHeight = child.getMeasuredHeight();
391            final int flags = lp.mScrollFlags;
392
393            if ((flags & LayoutParams.FLAG_QUICK_RETURN) == LayoutParams.FLAG_QUICK_RETURN) {
394                // First take the margin into account
395                range += lp.topMargin + lp.bottomMargin;
396                // The view has the quick return flag combination...
397                if ((flags & LayoutParams.SCROLL_FLAG_ENTER_ALWAYS_COLLAPSED) != 0) {
398                    // If they're set to enter collapsed, use the minimum height
399                    range += ViewCompat.getMinimumHeight(child);
400                } else if ((flags & LayoutParams.SCROLL_FLAG_EXIT_UNTIL_COLLAPSED) != 0) {
401                    // Only enter by the amount of the collapsed height
402                    range += childHeight - ViewCompat.getMinimumHeight(child);
403                } else {
404                    // Else use the full height
405                    range += childHeight;
406                }
407            } else if (range > 0) {
408                // If we've hit an non-quick return scrollable view, and we've already hit a
409                // quick return view, return now
410                break;
411            }
412        }
413        return mDownPreScrollRange = Math.max(0, range);
414    }
415
416    /**
417     * Return the scroll range when scrolling down from a nested scroll.
418     */
419    private int getDownNestedScrollRange() {
420        if (mDownScrollRange != INVALID_SCROLL_RANGE) {
421            // If we already have a valid value, return it
422            return mDownScrollRange;
423        }
424
425        int range = 0;
426        for (int i = 0, z = getChildCount(); i < z; i++) {
427            final View child = getChildAt(i);
428            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
429            int childHeight = child.getMeasuredHeight();
430            childHeight += lp.topMargin + lp.bottomMargin;
431
432            final int flags = lp.mScrollFlags;
433
434            if ((flags & LayoutParams.SCROLL_FLAG_SCROLL) != 0) {
435                // We're set to scroll so add the child's height
436                range += childHeight;
437
438                if ((flags & LayoutParams.SCROLL_FLAG_EXIT_UNTIL_COLLAPSED) != 0) {
439                    // For a collapsing exit scroll, we to take the collapsed height into account.
440                    // We also break the range straight away since later views can't scroll
441                    // beneath us
442                    range -= ViewCompat.getMinimumHeight(child) + getTopInset();
443                    break;
444                }
445            } else {
446                // As soon as a view doesn't have the scroll flag, we end the range calculation.
447                // This is because views below can not scroll under a fixed view.
448                break;
449            }
450        }
451        return mDownScrollRange = Math.max(0, range);
452    }
453
454    private void dispatchOffsetUpdates(int offset) {
455        // Iterate backwards through the list so that most recently added listeners
456        // get the first chance to decide
457        if (mListeners != null) {
458            for (int i = 0, z = mListeners.size(); i < z; i++) {
459                final OnOffsetChangedListener listener = mListeners.get(i);
460                if (listener != null) {
461                    listener.onOffsetChanged(this, offset);
462                }
463            }
464        }
465    }
466
467    final int getMinimumHeightForVisibleOverlappingContent() {
468        final int topInset = getTopInset();
469        final int minHeight = ViewCompat.getMinimumHeight(this);
470        if (minHeight != 0) {
471            // If this layout has a min height, use it (doubled)
472            return (minHeight * 2) + topInset;
473        }
474
475        // Otherwise, we'll use twice the min height of our last child
476        final int childCount = getChildCount();
477        final int lastChildMinHeight = childCount >= 1
478                ? ViewCompat.getMinimumHeight(getChildAt(childCount - 1)) : 0;
479        if (lastChildMinHeight != 0) {
480            return (lastChildMinHeight * 2) + topInset;
481        }
482
483        // If we reach here then we don't have a min height explicitly set. Instead we'll take a
484        // guess at 1/3 of our height being visible
485        return getHeight() / 3;
486    }
487
488    @Override
489    protected int[] onCreateDrawableState(int extraSpace) {
490        final int[] extraStates = mTmpStatesArray;
491        final int[] states = super.onCreateDrawableState(extraSpace + extraStates.length);
492
493        extraStates[0] = mCollapsible ? R.attr.state_collapsible : -R.attr.state_collapsible;
494        extraStates[1] = mCollapsible && mCollapsed
495                ? R.attr.state_collapsed : -R.attr.state_collapsed;
496
497        return mergeDrawableStates(states, extraStates);
498    }
499
500    private void setCollapsibleState(boolean collapsible) {
501        if (mCollapsible != collapsible) {
502            mCollapsible = collapsible;
503            refreshDrawableState();
504        }
505    }
506
507    private void setCollapsedState(boolean collapsed) {
508        if (mCollapsed != collapsed) {
509            mCollapsed = collapsed;
510            refreshDrawableState();
511        }
512    }
513
514    /**
515     * @deprecated target elevation is now deprecated. AppBarLayout's elevation is now
516     * controlled via a {@link android.animation.StateListAnimator}. If a target
517     * elevation is set, either by this method or the {@code app:elevation} attibute,
518     * a new state list animator is created which uses the given {@code elevation} value.
519     *
520     * @attr ref android.support.design.R.styleable#AppBarLayout_elevation
521     */
522    @Deprecated
523    public void setTargetElevation(float elevation) {
524        if (Build.VERSION.SDK_INT >= 21) {
525            ViewUtilsLollipop.setDefaultAppBarLayoutStateListAnimator(this, elevation);
526        }
527    }
528
529    /**
530     * @deprecated target elevation is now deprecated. AppBarLayout's elevation is now
531     * controlled via a {@link android.animation.StateListAnimator}. This method now
532     * always returns 0.
533     */
534    @Deprecated
535    public float getTargetElevation() {
536        return 0;
537    }
538
539    private int getPendingAction() {
540        return mPendingAction;
541    }
542
543    private void resetPendingAction() {
544        mPendingAction = PENDING_ACTION_NONE;
545    }
546
547    @VisibleForTesting
548    final int getTopInset() {
549        return mLastInsets != null ? mLastInsets.getSystemWindowInsetTop() : 0;
550    }
551
552    private WindowInsetsCompat onWindowInsetChanged(final WindowInsetsCompat insets) {
553        WindowInsetsCompat newInsets = null;
554
555        if (ViewCompat.getFitsSystemWindows(this)) {
556            // If we're set to fit system windows, keep the insets
557            newInsets = insets;
558        }
559
560        // If our insets have changed, keep them and invalidate the scroll ranges...
561        if (newInsets != mLastInsets) {
562            mLastInsets = newInsets;
563            invalidateScrollRanges();
564        }
565
566        return insets;
567    }
568
569    public static class LayoutParams extends LinearLayout.LayoutParams {
570
571        /** @hide */
572        @IntDef(flag=true, value={
573                SCROLL_FLAG_SCROLL,
574                SCROLL_FLAG_EXIT_UNTIL_COLLAPSED,
575                SCROLL_FLAG_ENTER_ALWAYS,
576                SCROLL_FLAG_ENTER_ALWAYS_COLLAPSED,
577                SCROLL_FLAG_SNAP
578        })
579        @Retention(RetentionPolicy.SOURCE)
580        public @interface ScrollFlags {}
581
582        /**
583         * The view will be scroll in direct relation to scroll events. This flag needs to be
584         * set for any of the other flags to take effect. If any sibling views
585         * before this one do not have this flag, then this value has no effect.
586         */
587        public static final int SCROLL_FLAG_SCROLL = 0x1;
588
589        /**
590         * When exiting (scrolling off screen) the view will be scrolled until it is
591         * 'collapsed'. The collapsed height is defined by the view's minimum height.
592         *
593         * @see ViewCompat#getMinimumHeight(View)
594         * @see View#setMinimumHeight(int)
595         */
596        public static final int SCROLL_FLAG_EXIT_UNTIL_COLLAPSED = 0x2;
597
598        /**
599         * When entering (scrolling on screen) the view will scroll on any downwards
600         * scroll event, regardless of whether the scrolling view is also scrolling. This
601         * is commonly referred to as the 'quick return' pattern.
602         */
603        public static final int SCROLL_FLAG_ENTER_ALWAYS = 0x4;
604
605        /**
606         * An additional flag for 'enterAlways' which modifies the returning view to
607         * only initially scroll back to it's collapsed height. Once the scrolling view has
608         * reached the end of it's scroll range, the remainder of this view will be scrolled
609         * into view. The collapsed height is defined by the view's minimum height.
610         *
611         * @see ViewCompat#getMinimumHeight(View)
612         * @see View#setMinimumHeight(int)
613         */
614        public static final int SCROLL_FLAG_ENTER_ALWAYS_COLLAPSED = 0x8;
615
616        /**
617         * Upon a scroll ending, if the view is only partially visible then it will be snapped
618         * and scrolled to it's closest edge. For example, if the view only has it's bottom 25%
619         * displayed, it will be scrolled off screen completely. Conversely, if it's bottom 75%
620         * is visible then it will be scrolled fully into view.
621         */
622        public static final int SCROLL_FLAG_SNAP = 0x10;
623
624        /**
625         * Internal flags which allows quick checking features
626         */
627        static final int FLAG_QUICK_RETURN = SCROLL_FLAG_SCROLL | SCROLL_FLAG_ENTER_ALWAYS;
628        static final int FLAG_SNAP = SCROLL_FLAG_SCROLL | SCROLL_FLAG_SNAP;
629        static final int COLLAPSIBLE_FLAGS = SCROLL_FLAG_EXIT_UNTIL_COLLAPSED
630                | SCROLL_FLAG_ENTER_ALWAYS_COLLAPSED;
631
632        int mScrollFlags = SCROLL_FLAG_SCROLL;
633        Interpolator mScrollInterpolator;
634
635        public LayoutParams(Context c, AttributeSet attrs) {
636            super(c, attrs);
637            TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.AppBarLayout_Layout);
638            mScrollFlags = a.getInt(R.styleable.AppBarLayout_Layout_layout_scrollFlags, 0);
639            if (a.hasValue(R.styleable.AppBarLayout_Layout_layout_scrollInterpolator)) {
640                int resId = a.getResourceId(
641                        R.styleable.AppBarLayout_Layout_layout_scrollInterpolator, 0);
642                mScrollInterpolator = android.view.animation.AnimationUtils.loadInterpolator(
643                        c, resId);
644            }
645            a.recycle();
646        }
647
648        public LayoutParams(int width, int height) {
649            super(width, height);
650        }
651
652        public LayoutParams(int width, int height, float weight) {
653            super(width, height, weight);
654        }
655
656        public LayoutParams(ViewGroup.LayoutParams p) {
657            super(p);
658        }
659
660        public LayoutParams(MarginLayoutParams source) {
661            super(source);
662        }
663
664        public LayoutParams(LinearLayout.LayoutParams source) {
665            super(source);
666        }
667
668        public LayoutParams(LayoutParams source) {
669            super(source);
670            mScrollFlags = source.mScrollFlags;
671            mScrollInterpolator = source.mScrollInterpolator;
672        }
673
674        /**
675         * Set the scrolling flags.
676         *
677         * @param flags bitwise int of {@link #SCROLL_FLAG_SCROLL},
678         *             {@link #SCROLL_FLAG_EXIT_UNTIL_COLLAPSED}, {@link #SCROLL_FLAG_ENTER_ALWAYS},
679         *             {@link #SCROLL_FLAG_ENTER_ALWAYS_COLLAPSED} and {@link #SCROLL_FLAG_SNAP }.
680         *
681         * @see #getScrollFlags()
682         *
683         * @attr ref android.support.design.R.styleable#AppBarLayout_Layout_layout_scrollFlags
684         */
685        public void setScrollFlags(@ScrollFlags int flags) {
686            mScrollFlags = flags;
687        }
688
689        /**
690         * Returns the scrolling flags.
691         *
692         * @see #setScrollFlags(int)
693         *
694         * @attr ref android.support.design.R.styleable#AppBarLayout_Layout_layout_scrollFlags
695         */
696        @ScrollFlags
697        public int getScrollFlags() {
698            return mScrollFlags;
699        }
700
701        /**
702         * Set the interpolator to when scrolling the view associated with this
703         * {@link LayoutParams}.
704         *
705         * @param interpolator the interpolator to use, or null to use normal 1-to-1 scrolling.
706         *
707         * @attr ref android.support.design.R.styleable#AppBarLayout_Layout_layout_scrollInterpolator
708         * @see #getScrollInterpolator()
709         */
710        public void setScrollInterpolator(Interpolator interpolator) {
711            mScrollInterpolator = interpolator;
712        }
713
714        /**
715         * Returns the {@link Interpolator} being used for scrolling the view associated with this
716         * {@link LayoutParams}. Null indicates 'normal' 1-to-1 scrolling.
717         *
718         * @attr ref android.support.design.R.styleable#AppBarLayout_Layout_layout_scrollInterpolator
719         * @see #setScrollInterpolator(Interpolator)
720         */
721        public Interpolator getScrollInterpolator() {
722            return mScrollInterpolator;
723        }
724
725        /**
726         * Returns true if the scroll flags are compatible for 'collapsing'
727         */
728        private boolean isCollapsible() {
729            return (mScrollFlags & SCROLL_FLAG_SCROLL) == SCROLL_FLAG_SCROLL
730                    && (mScrollFlags & COLLAPSIBLE_FLAGS) != 0;
731        }
732    }
733
734    /**
735     * The default {@link Behavior} for {@link AppBarLayout}. Implements the necessary nested
736     * scroll handling with offsetting.
737     */
738    public static class Behavior extends HeaderBehavior<AppBarLayout> {
739        private static final int MAX_OFFSET_ANIMATION_DURATION = 600; // ms
740        private static final int INVALID_POSITION = -1;
741
742        /**
743         * Callback to allow control over any {@link AppBarLayout} dragging.
744         */
745        public static abstract class DragCallback {
746            /**
747             * Allows control over whether the given {@link AppBarLayout} can be dragged or not.
748             *
749             * <p>Dragging is defined as a direct touch on the AppBarLayout with movement. This
750             * call does not affect any nested scrolling.</p>
751             *
752             * @return true if we are in a position to scroll the AppBarLayout via a drag, false
753             *         if not.
754             */
755            public abstract boolean canDrag(@NonNull AppBarLayout appBarLayout);
756        }
757
758        private int mOffsetDelta;
759
760        private boolean mSkipNestedPreScroll;
761        private boolean mWasNestedFlung;
762
763        private ValueAnimatorCompat mOffsetAnimator;
764
765        private int mOffsetToChildIndexOnLayout = INVALID_POSITION;
766        private boolean mOffsetToChildIndexOnLayoutIsMinHeight;
767        private float mOffsetToChildIndexOnLayoutPerc;
768
769        private WeakReference<View> mLastNestedScrollingChildRef;
770        private DragCallback mOnDragCallback;
771
772        public Behavior() {}
773
774        public Behavior(Context context, AttributeSet attrs) {
775            super(context, attrs);
776        }
777
778        @Override
779        public boolean onStartNestedScroll(CoordinatorLayout parent, AppBarLayout child,
780                View directTargetChild, View target, int nestedScrollAxes) {
781            // Return true if we're nested scrolling vertically, and we have scrollable children
782            // and the scrolling view is big enough to scroll
783            final boolean started = (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0
784                    && child.hasScrollableChildren()
785                    && parent.getHeight() - directTargetChild.getHeight() <= child.getHeight();
786
787            if (started && mOffsetAnimator != null) {
788                // Cancel any offset animation
789                mOffsetAnimator.cancel();
790            }
791
792            // A new nested scroll has started so clear out the previous ref
793            mLastNestedScrollingChildRef = null;
794
795            return started;
796        }
797
798        @Override
799        public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child,
800                View target, int dx, int dy, int[] consumed) {
801            if (dy != 0 && !mSkipNestedPreScroll) {
802                int min, max;
803                if (dy < 0) {
804                    // We're scrolling down
805                    min = -child.getTotalScrollRange();
806                    max = min + child.getDownNestedPreScrollRange();
807                } else {
808                    // We're scrolling up
809                    min = -child.getUpNestedPreScrollRange();
810                    max = 0;
811                }
812                consumed[1] = scroll(coordinatorLayout, child, dy, min, max);
813            }
814        }
815
816        @Override
817        public void onNestedScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child,
818                View target, int dxConsumed, int dyConsumed,
819                int dxUnconsumed, int dyUnconsumed) {
820            if (dyUnconsumed < 0) {
821                // If the scrolling view is scrolling down but not consuming, it's probably be at
822                // the top of it's content
823                scroll(coordinatorLayout, child, dyUnconsumed,
824                        -child.getDownNestedScrollRange(), 0);
825                // Set the expanding flag so that onNestedPreScroll doesn't handle any events
826                mSkipNestedPreScroll = true;
827            } else {
828                // As we're no longer handling nested scrolls, reset the skip flag
829                mSkipNestedPreScroll = false;
830            }
831        }
832
833        @Override
834        public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, AppBarLayout abl,
835                View target) {
836            if (!mWasNestedFlung) {
837                // If we haven't been flung then let's see if the current view has been set to snap
838                snapToChildIfNeeded(coordinatorLayout, abl);
839            }
840
841            // Reset the flags
842            mSkipNestedPreScroll = false;
843            mWasNestedFlung = false;
844            // Keep a reference to the previous nested scrolling child
845            mLastNestedScrollingChildRef = new WeakReference<>(target);
846        }
847
848        @Override
849        public boolean onNestedFling(final CoordinatorLayout coordinatorLayout,
850                final AppBarLayout child, View target, float velocityX, float velocityY,
851                boolean consumed) {
852            boolean flung = false;
853
854            if (!consumed) {
855                // It has been consumed so let's fling ourselves
856                flung = fling(coordinatorLayout, child, -child.getTotalScrollRange(),
857                        0, -velocityY);
858            } else {
859                // If we're scrolling up and the child also consumed the fling. We'll fake scroll
860                // upto our 'collapsed' offset
861                if (velocityY < 0) {
862                    // We're scrolling down
863                    final int targetScroll = -child.getTotalScrollRange()
864                            + child.getDownNestedPreScrollRange();
865                    if (getTopBottomOffsetForScrollingSibling() < targetScroll) {
866                        // If we're currently not expanded more than the target scroll, we'll
867                        // animate a fling
868                        animateOffsetTo(coordinatorLayout, child, targetScroll, velocityY);
869                        flung = true;
870                    }
871                } else {
872                    // We're scrolling up
873                    final int targetScroll = -child.getUpNestedPreScrollRange();
874                    if (getTopBottomOffsetForScrollingSibling() > targetScroll) {
875                        // If we're currently not expanded less than the target scroll, we'll
876                        // animate a fling
877                        animateOffsetTo(coordinatorLayout, child, targetScroll, velocityY);
878                        flung = true;
879                    }
880                }
881            }
882
883            mWasNestedFlung = flung;
884            return flung;
885        }
886
887        /**
888         * Set a callback to control any {@link AppBarLayout} dragging.
889         *
890         * @param callback the callback to use, or {@code null} to use the default behavior.
891         */
892        public void setDragCallback(@Nullable DragCallback callback) {
893            mOnDragCallback = callback;
894        }
895
896        private void animateOffsetTo(final CoordinatorLayout coordinatorLayout,
897                final AppBarLayout child, final int offset, float velocity) {
898            final int distance = Math.abs(getTopBottomOffsetForScrollingSibling() - offset);
899
900            final int duration;
901            velocity = Math.abs(velocity);
902            if (velocity > 0) {
903                duration = 3 * Math.round(1000 * (distance / velocity));
904            } else {
905                final float distanceRatio = (float) distance / child.getHeight();
906                duration = (int) ((distanceRatio + 1) * 150);
907            }
908
909            animateOffsetWithDuration(coordinatorLayout, child, offset, duration);
910        }
911
912        private void animateOffsetWithDuration(final CoordinatorLayout coordinatorLayout,
913                final AppBarLayout child, final int offset, final int duration) {
914            final int currentOffset = getTopBottomOffsetForScrollingSibling();
915            if (currentOffset == offset) {
916                if (mOffsetAnimator != null && mOffsetAnimator.isRunning()) {
917                    mOffsetAnimator.cancel();
918                }
919                return;
920            }
921
922            if (mOffsetAnimator == null) {
923                mOffsetAnimator = ViewUtils.createAnimator();
924                mOffsetAnimator.setInterpolator(AnimationUtils.DECELERATE_INTERPOLATOR);
925                mOffsetAnimator.setUpdateListener(new ValueAnimatorCompat.AnimatorUpdateListener() {
926                    @Override
927                    public void onAnimationUpdate(ValueAnimatorCompat animator) {
928                        setHeaderTopBottomOffset(coordinatorLayout, child,
929                                animator.getAnimatedIntValue());
930                    }
931                });
932            } else {
933                mOffsetAnimator.cancel();
934            }
935
936            mOffsetAnimator.setDuration(Math.min(duration, MAX_OFFSET_ANIMATION_DURATION));
937            mOffsetAnimator.setIntValues(currentOffset, offset);
938            mOffsetAnimator.start();
939        }
940
941        private int getChildIndexOnOffset(AppBarLayout abl, final int offset) {
942            for (int i = 0, count = abl.getChildCount(); i < count; i++) {
943                View child = abl.getChildAt(i);
944                if (child.getTop() <= -offset && child.getBottom() >= -offset) {
945                    return i;
946                }
947            }
948            return -1;
949        }
950
951        private void snapToChildIfNeeded(CoordinatorLayout coordinatorLayout, AppBarLayout abl) {
952            final int offset = getTopBottomOffsetForScrollingSibling();
953            final int offsetChildIndex = getChildIndexOnOffset(abl, offset);
954            if (offsetChildIndex >= 0) {
955                final View offsetChild = abl.getChildAt(offsetChildIndex);
956                final LayoutParams lp = (LayoutParams) offsetChild.getLayoutParams();
957                final int flags = lp.getScrollFlags();
958
959                if ((flags & LayoutParams.FLAG_SNAP) == LayoutParams.FLAG_SNAP) {
960                    // We're set the snap, so animate the offset to the nearest edge
961                    int snapTop = -offsetChild.getTop();
962                    int snapBottom = -offsetChild.getBottom();
963
964                    if (offsetChildIndex == abl.getChildCount() - 1) {
965                        // If this is the last child, we need to take the top inset into account
966                        snapBottom += abl.getTopInset();
967                    }
968
969                    if (checkFlag(flags, LayoutParams.SCROLL_FLAG_EXIT_UNTIL_COLLAPSED)) {
970                        // If the view is set only exit until it is collapsed, we'll abide by that
971                        snapBottom += ViewCompat.getMinimumHeight(offsetChild);
972                    } else if (checkFlag(flags, LayoutParams.FLAG_QUICK_RETURN
973                            | LayoutParams.SCROLL_FLAG_ENTER_ALWAYS)) {
974                        // If it's set to always enter collapsed, it actually has two states. We
975                        // select the state and then snap within the state
976                        final int seam = snapBottom + ViewCompat.getMinimumHeight(offsetChild);
977                        if (offset < seam) {
978                            snapTop = seam;
979                        } else {
980                            snapBottom = seam;
981                        }
982                    }
983
984                    final int newOffset = offset < (snapBottom + snapTop) / 2
985                            ? snapBottom
986                            : snapTop;
987                    animateOffsetTo(coordinatorLayout, abl,
988                            MathUtils.constrain(newOffset, -abl.getTotalScrollRange(), 0), 0);
989                }
990            }
991        }
992
993        private static boolean checkFlag(final int flags, final int check) {
994            return (flags & check) == check;
995        }
996
997        @Override
998        public boolean onMeasureChild(CoordinatorLayout parent, AppBarLayout child,
999                int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec,
1000                int heightUsed) {
1001            final CoordinatorLayout.LayoutParams lp =
1002                    (CoordinatorLayout.LayoutParams) child.getLayoutParams();
1003            if (lp.height == CoordinatorLayout.LayoutParams.WRAP_CONTENT) {
1004                // If the view is set to wrap on it's height, CoordinatorLayout by default will
1005                // cap the view at the CoL's height. Since the AppBarLayout can scroll, this isn't
1006                // what we actually want, so we measure it ourselves with an unspecified spec to
1007                // allow the child to be larger than it's parent
1008                parent.onMeasureChild(child, parentWidthMeasureSpec, widthUsed,
1009                        MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), heightUsed);
1010                return true;
1011            }
1012
1013            // Let the parent handle it as normal
1014            return super.onMeasureChild(parent, child, parentWidthMeasureSpec, widthUsed,
1015                    parentHeightMeasureSpec, heightUsed);
1016        }
1017
1018        @Override
1019        public boolean onLayoutChild(CoordinatorLayout parent, AppBarLayout abl,
1020                int layoutDirection) {
1021            boolean handled = super.onLayoutChild(parent, abl, layoutDirection);
1022
1023            final int pendingAction = abl.getPendingAction();
1024            if (pendingAction != PENDING_ACTION_NONE) {
1025                final boolean animate = (pendingAction & PENDING_ACTION_ANIMATE_ENABLED) != 0;
1026                if ((pendingAction & PENDING_ACTION_COLLAPSED) != 0) {
1027                    final int offset = -abl.getUpNestedPreScrollRange();
1028                    if (animate) {
1029                        animateOffsetTo(parent, abl, offset, 0);
1030                    } else {
1031                        setHeaderTopBottomOffset(parent, abl, offset);
1032                    }
1033                } else if ((pendingAction & PENDING_ACTION_EXPANDED) != 0) {
1034                    if (animate) {
1035                        animateOffsetTo(parent, abl, 0, 0);
1036                    } else {
1037                        setHeaderTopBottomOffset(parent, abl, 0);
1038                    }
1039                }
1040            } else if (mOffsetToChildIndexOnLayout >= 0) {
1041                View child = abl.getChildAt(mOffsetToChildIndexOnLayout);
1042                int offset = -child.getBottom();
1043                if (mOffsetToChildIndexOnLayoutIsMinHeight) {
1044                    offset += ViewCompat.getMinimumHeight(child);
1045                } else {
1046                    offset += Math.round(child.getHeight() * mOffsetToChildIndexOnLayoutPerc);
1047                }
1048                setTopAndBottomOffset(offset);
1049            }
1050
1051            // Finally reset any pending states
1052            abl.resetPendingAction();
1053            mOffsetToChildIndexOnLayout = INVALID_POSITION;
1054
1055            // We may have changed size, so let's constrain the top and bottom offset correctly,
1056            // just in case we're out of the bounds
1057            setTopAndBottomOffset(
1058                    MathUtils.constrain(getTopAndBottomOffset(), -abl.getTotalScrollRange(), 0));
1059
1060            // Make sure we dispatch the offset update
1061            abl.dispatchOffsetUpdates(getTopAndBottomOffset());
1062
1063            return handled;
1064        }
1065
1066        @Override
1067        boolean canDragView(AppBarLayout view) {
1068            if (mOnDragCallback != null) {
1069                // If there is a drag callback set, it's in control
1070                return mOnDragCallback.canDrag(view);
1071            }
1072
1073            // Else we'll use the default behaviour of seeing if it can scroll down
1074            if (mLastNestedScrollingChildRef != null) {
1075                // If we have a reference to a scrolling view, check it
1076                final View scrollingView = mLastNestedScrollingChildRef.get();
1077                return scrollingView != null && scrollingView.isShown()
1078                        && !ViewCompat.canScrollVertically(scrollingView, -1);
1079            } else {
1080                // Otherwise we assume that the scrolling view hasn't been scrolled and can drag.
1081                return true;
1082            }
1083        }
1084
1085        @Override
1086        void onFlingFinished(CoordinatorLayout parent, AppBarLayout layout) {
1087            // At the end of a manual fling, check to see if we need to snap to the edge-child
1088            snapToChildIfNeeded(parent, layout);
1089        }
1090
1091        @Override
1092        int getMaxDragOffset(AppBarLayout view) {
1093            return -view.getDownNestedScrollRange();
1094        }
1095
1096        @Override
1097        int getScrollRangeForDragFling(AppBarLayout view) {
1098            return view.getTotalScrollRange();
1099        }
1100
1101        @Override
1102        int setHeaderTopBottomOffset(CoordinatorLayout coordinatorLayout,
1103                AppBarLayout header, int newOffset, int minOffset, int maxOffset) {
1104            final int curOffset = getTopBottomOffsetForScrollingSibling();
1105            int consumed = 0;
1106
1107            if (minOffset != 0 && curOffset >= minOffset && curOffset <= maxOffset) {
1108                // If we have some scrolling range, and we're currently within the min and max
1109                // offsets, calculate a new offset
1110                newOffset = MathUtils.constrain(newOffset, minOffset, maxOffset);
1111                AppBarLayout appBarLayout = (AppBarLayout) header;
1112                if (curOffset != newOffset) {
1113                    final int interpolatedOffset = appBarLayout.hasChildWithInterpolator()
1114                            ? interpolateOffset(appBarLayout, newOffset)
1115                            : newOffset;
1116
1117                    final boolean offsetChanged = setTopAndBottomOffset(interpolatedOffset);
1118
1119                    // Update how much dy we have consumed
1120                    consumed = curOffset - newOffset;
1121                    // Update the stored sibling offset
1122                    mOffsetDelta = newOffset - interpolatedOffset;
1123
1124                    if (!offsetChanged && appBarLayout.hasChildWithInterpolator()) {
1125                        // If the offset hasn't changed and we're using an interpolated scroll
1126                        // then we need to keep any dependent views updated. CoL will do this for
1127                        // us when we move, but we need to do it manually when we don't (as an
1128                        // interpolated scroll may finish early).
1129                        coordinatorLayout.dispatchDependentViewsChanged(appBarLayout);
1130                    }
1131
1132                    // Dispatch the updates to any listeners
1133                    appBarLayout.dispatchOffsetUpdates(getTopAndBottomOffset());
1134
1135                    // Update the AppBarLayout's drawable state (for any elevation changes)
1136                    updateAppBarLayoutDrawableState(appBarLayout, newOffset,
1137                            newOffset < curOffset ? -1 : 1);
1138                }
1139            } else {
1140                // Reset the offset delta
1141                mOffsetDelta = 0;
1142            }
1143
1144            return consumed;
1145        }
1146
1147        private int interpolateOffset(AppBarLayout layout, final int offset) {
1148            final int absOffset = Math.abs(offset);
1149
1150            for (int i = 0, z = layout.getChildCount(); i < z; i++) {
1151                final View child = layout.getChildAt(i);
1152                final AppBarLayout.LayoutParams childLp = (LayoutParams) child.getLayoutParams();
1153                final Interpolator interpolator = childLp.getScrollInterpolator();
1154
1155                if (absOffset >= child.getTop() && absOffset <= child.getBottom()) {
1156                    if (interpolator != null) {
1157                        int childScrollableHeight = 0;
1158                        final int flags = childLp.getScrollFlags();
1159                        if ((flags & LayoutParams.SCROLL_FLAG_SCROLL) != 0) {
1160                            // We're set to scroll so add the child's height plus margin
1161                            childScrollableHeight += child.getHeight() + childLp.topMargin
1162                                    + childLp.bottomMargin;
1163
1164                            if ((flags & LayoutParams.SCROLL_FLAG_EXIT_UNTIL_COLLAPSED) != 0) {
1165                                // For a collapsing scroll, we to take the collapsed height
1166                                // into account.
1167                                childScrollableHeight -= ViewCompat.getMinimumHeight(child);
1168                            }
1169                        }
1170
1171                        if (ViewCompat.getFitsSystemWindows(child)) {
1172                            childScrollableHeight -= layout.getTopInset();
1173                        }
1174
1175                        if (childScrollableHeight > 0) {
1176                            final int offsetForView = absOffset - child.getTop();
1177                            final int interpolatedDiff = Math.round(childScrollableHeight *
1178                                    interpolator.getInterpolation(
1179                                            offsetForView / (float) childScrollableHeight));
1180
1181                            return Integer.signum(offset) * (child.getTop() + interpolatedDiff);
1182                        }
1183                    }
1184
1185                    // If we get to here then the view on the offset isn't suitable for interpolated
1186                    // scrolling. So break out of the loop
1187                    break;
1188                }
1189            }
1190
1191            return offset;
1192        }
1193
1194        private void updateAppBarLayoutDrawableState(final AppBarLayout layout,
1195                final int offset, final int direction) {
1196            final View child = getAppBarChildOnOffset(layout, offset);
1197            if (child != null) {
1198                final AppBarLayout.LayoutParams childLp = (LayoutParams) child.getLayoutParams();
1199                final int flags = childLp.getScrollFlags();
1200                boolean collapsed = false;
1201
1202                if ((flags & LayoutParams.SCROLL_FLAG_SCROLL) != 0) {
1203                    final int minHeight = ViewCompat.getMinimumHeight(child);
1204
1205                    if (direction > 0 && (flags & (LayoutParams.SCROLL_FLAG_ENTER_ALWAYS
1206                            | LayoutParams.SCROLL_FLAG_ENTER_ALWAYS_COLLAPSED)) != 0) {
1207                        // We're set to enter always collapsed so we are only collapsed when
1208                        // being scrolled down, and in a collapsed offset
1209                        collapsed = -offset >= child.getBottom() - minHeight - layout.getTopInset();
1210                    } else if ((flags & LayoutParams.SCROLL_FLAG_EXIT_UNTIL_COLLAPSED) != 0) {
1211                        // We're set to exit until collapsed, so any offset which results in
1212                        // the minimum height (or less) being shown is collapsed
1213                        collapsed = -offset >= child.getBottom() - minHeight - layout.getTopInset();
1214                    }
1215                }
1216
1217                layout.setCollapsedState(collapsed);
1218            }
1219        }
1220
1221        private static View getAppBarChildOnOffset(final AppBarLayout layout, final int offset) {
1222            final int absOffset = Math.abs(offset);
1223            for (int i = 0, z = layout.getChildCount(); i < z; i++) {
1224                final View child = layout.getChildAt(i);
1225                if (absOffset >= child.getTop() && absOffset <= child.getBottom()) {
1226                    return child;
1227                }
1228            }
1229            return null;
1230        }
1231
1232        @Override
1233        int getTopBottomOffsetForScrollingSibling() {
1234            return getTopAndBottomOffset() + mOffsetDelta;
1235        }
1236
1237        @Override
1238        public Parcelable onSaveInstanceState(CoordinatorLayout parent, AppBarLayout appBarLayout) {
1239            final Parcelable superState = super.onSaveInstanceState(parent, appBarLayout);
1240            final int offset = getTopAndBottomOffset();
1241
1242            // Try and find the first visible child...
1243            for (int i = 0, count = appBarLayout.getChildCount(); i < count; i++) {
1244                View child = appBarLayout.getChildAt(i);
1245                final int visBottom = child.getBottom() + offset;
1246
1247                if (child.getTop() + offset <= 0 && visBottom >= 0) {
1248                    final SavedState ss = new SavedState(superState);
1249                    ss.firstVisibleChildIndex = i;
1250                    ss.firstVisibileChildAtMinimumHeight =
1251                            visBottom == ViewCompat.getMinimumHeight(child);
1252                    ss.firstVisibileChildPercentageShown = visBottom / (float) child.getHeight();
1253                    return ss;
1254                }
1255            }
1256
1257            // Else we'll just return the super state
1258            return superState;
1259        }
1260
1261        @Override
1262        public void onRestoreInstanceState(CoordinatorLayout parent, AppBarLayout appBarLayout,
1263                Parcelable state) {
1264            if (state instanceof SavedState) {
1265                final SavedState ss = (SavedState) state;
1266                super.onRestoreInstanceState(parent, appBarLayout, ss.getSuperState());
1267                mOffsetToChildIndexOnLayout = ss.firstVisibleChildIndex;
1268                mOffsetToChildIndexOnLayoutPerc = ss.firstVisibileChildPercentageShown;
1269                mOffsetToChildIndexOnLayoutIsMinHeight = ss.firstVisibileChildAtMinimumHeight;
1270            } else {
1271                super.onRestoreInstanceState(parent, appBarLayout, state);
1272                mOffsetToChildIndexOnLayout = INVALID_POSITION;
1273            }
1274        }
1275
1276        protected static class SavedState extends AbsSavedState {
1277            int firstVisibleChildIndex;
1278            float firstVisibileChildPercentageShown;
1279            boolean firstVisibileChildAtMinimumHeight;
1280
1281            public SavedState(Parcel source, ClassLoader loader) {
1282                super(source, loader);
1283                firstVisibleChildIndex = source.readInt();
1284                firstVisibileChildPercentageShown = source.readFloat();
1285                firstVisibileChildAtMinimumHeight = source.readByte() != 0;
1286            }
1287
1288            public SavedState(Parcelable superState) {
1289                super(superState);
1290            }
1291
1292            @Override
1293            public void writeToParcel(Parcel dest, int flags) {
1294                super.writeToParcel(dest, flags);
1295                dest.writeInt(firstVisibleChildIndex);
1296                dest.writeFloat(firstVisibileChildPercentageShown);
1297                dest.writeByte((byte) (firstVisibileChildAtMinimumHeight ? 1 : 0));
1298            }
1299
1300            public static final Parcelable.Creator<SavedState> CREATOR =
1301                    ParcelableCompat.newCreator(new ParcelableCompatCreatorCallbacks<SavedState>() {
1302                        @Override
1303                        public SavedState createFromParcel(Parcel source, ClassLoader loader) {
1304                            return new SavedState(source, loader);
1305                        }
1306
1307                        @Override
1308                        public SavedState[] newArray(int size) {
1309                            return new SavedState[size];
1310                        }
1311                    });
1312        }
1313    }
1314
1315    /**
1316     * Behavior which should be used by {@link View}s which can scroll vertically and support
1317     * nested scrolling to automatically scroll any {@link AppBarLayout} siblings.
1318     */
1319    public static class ScrollingViewBehavior extends HeaderScrollingViewBehavior {
1320
1321        public ScrollingViewBehavior() {}
1322
1323        public ScrollingViewBehavior(Context context, AttributeSet attrs) {
1324            super(context, attrs);
1325
1326            final TypedArray a = context.obtainStyledAttributes(attrs,
1327                    R.styleable.ScrollingViewBehavior_Layout);
1328            setOverlayTop(a.getDimensionPixelSize(
1329                    R.styleable.ScrollingViewBehavior_Layout_behavior_overlapTop, 0));
1330            a.recycle();
1331        }
1332
1333        @Override
1334        public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
1335            // We depend on any AppBarLayouts
1336            return dependency instanceof AppBarLayout;
1337        }
1338
1339        @Override
1340        public boolean onDependentViewChanged(CoordinatorLayout parent, View child,
1341                View dependency) {
1342            offsetChildAsNeeded(parent, child, dependency);
1343            return false;
1344        }
1345
1346        private void offsetChildAsNeeded(CoordinatorLayout parent, View child, View dependency) {
1347            final CoordinatorLayout.Behavior behavior =
1348                    ((CoordinatorLayout.LayoutParams) dependency.getLayoutParams()).getBehavior();
1349            if (behavior instanceof Behavior) {
1350                // Offset the child, pinning it to the bottom the header-dependency, maintaining
1351                // any vertical gap, and overlap
1352                final Behavior ablBehavior = (Behavior) behavior;
1353                final int offset = ablBehavior.getTopBottomOffsetForScrollingSibling();
1354                ViewCompat.offsetTopAndBottom(child, (dependency.getBottom() - child.getTop())
1355                        + ablBehavior.mOffsetDelta
1356                        + getVerticalLayoutGap()
1357                        - getOverlapPixelsForOffset(dependency));
1358            }
1359        }
1360
1361
1362        @Override
1363        float getOverlapRatioForOffset(final View header) {
1364            if (header instanceof AppBarLayout) {
1365                final AppBarLayout abl = (AppBarLayout) header;
1366                final int totalScrollRange = abl.getTotalScrollRange();
1367                final int preScrollDown = abl.getDownNestedPreScrollRange();
1368                final int offset = getAppBarLayoutOffset(abl);
1369
1370                if (preScrollDown != 0 && (totalScrollRange + offset) <= preScrollDown) {
1371                    // If we're in a pre-scroll down. Don't use the offset at all.
1372                    return 0;
1373                } else {
1374                    final int availScrollRange = totalScrollRange - preScrollDown;
1375                    if (availScrollRange != 0) {
1376                        // Else we'll use a interpolated ratio of the overlap, depending on offset
1377                        return 1f + (offset / (float) availScrollRange);
1378                    }
1379                }
1380            }
1381            return 0f;
1382        }
1383
1384        private static int getAppBarLayoutOffset(AppBarLayout abl) {
1385            final CoordinatorLayout.Behavior behavior =
1386                    ((CoordinatorLayout.LayoutParams) abl.getLayoutParams()).getBehavior();
1387            if (behavior instanceof Behavior) {
1388                return ((Behavior) behavior).getTopBottomOffsetForScrollingSibling();
1389            }
1390            return 0;
1391        }
1392
1393        @Override
1394        View findFirstDependency(List<View> views) {
1395            for (int i = 0, z = views.size(); i < z; i++) {
1396                View view = views.get(i);
1397                if (view instanceof AppBarLayout) {
1398                    return view;
1399                }
1400            }
1401            return null;
1402        }
1403
1404        @Override
1405        int getScrollRange(View v) {
1406            if (v instanceof AppBarLayout) {
1407                return ((AppBarLayout) v).getTotalScrollRange();
1408            } else {
1409                return super.getScrollRange(v);
1410            }
1411        }
1412    }
1413}
1414