AppBarLayout.java revision 6ba61c5c79fe025036593c9daf79cb65299bb0b6
1a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes/* 2a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * Copyright (C) 2015 The Android Open Source Project 3a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * 4a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * Licensed under the Apache License, Version 2.0 (the "License"); 5a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * you may not use this file except in compliance with the License. 6a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * You may obtain a copy of the License at 7a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * 8a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * http://www.apache.org/licenses/LICENSE-2.0 9a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * 10a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * Unless required by applicable law or agreed to in writing, software 11a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * distributed under the License is distributed on an "AS IS" BASIS, 12a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * See the License for the specific language governing permissions and 14a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * limitations under the License. 15a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes */ 16a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 17a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banespackage android.support.design.widget; 18a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 19a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banesimport android.content.Context; 20a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banesimport android.content.res.TypedArray; 2150dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banesimport android.support.annotation.IntDef; 22a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banesimport android.support.design.R; 23a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banesimport android.support.v4.view.ViewCompat; 246ba61c5c79fe025036593c9daf79cb65299bb0b6Chris Banesimport android.support.v4.view.WindowInsetsCompat; 25631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banesimport android.support.v4.widget.ScrollerCompat; 26a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banesimport android.util.AttributeSet; 27a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banesimport android.view.View; 28a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banesimport android.view.ViewGroup; 29a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banesimport android.view.animation.Interpolator; 30a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banesimport android.widget.LinearLayout; 31a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 3250dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banesimport java.lang.annotation.Retention; 3350dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banesimport java.lang.annotation.RetentionPolicy; 34631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banesimport java.lang.ref.WeakReference; 35631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banesimport java.util.ArrayList; 36631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banesimport java.util.Iterator; 37a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banesimport java.util.List; 38a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 39a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes/** 40a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * AppBarLayout is a vertical {@link LinearLayout} which implements many of the features of 41631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes * material design's app bar concept, namely scrolling gestures. 42a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * <p> 43a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * Children should provide their desired scrolling behavior through 44a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * {@link LayoutParams#setScrollFlags(int)} and the associated layout xml attribute: 45a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * {@code app:layout_scrollFlags}. 46a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * 47a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * <p> 48a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * This view depends heavily on being used as a direct child within a {@link CoordinatorLayout}. 49a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * If you use AppBarLayout within a different {@link ViewGroup}, most of it's functionality will 50a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * not work. 51a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * <p> 52a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * AppBarLayout also requires a separate scrolling sibling in order to. The binding is done through 53a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * the {@link ScrollingViewBehavior} beahior class, meaning that you should set your scrolling 54a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * view's behavior to be an instance of {@link ScrollingViewBehavior}. A string resource containing 55a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * the full class name is available. 56a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * 57a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * <pre> 58a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * <android.support.design.widget.CoordinatorLayout 59a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * xmlns:android="http://schemas.android.com/apk/res/android" 60a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * xmlns:app="http://schemas.android.com/apk/res-auto" 61a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * android:layout_width="match_parent" 62a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * android:layout_height="match_parent"> 63a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * 64a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * <android.support.v4.widget.NestedScrollView 65a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * android:layout_width="match_parent" 66a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * android:layout_height="match_parent" 67a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * app:layout_behavior="@string/appbar_scrolling_view_behavior"> 68a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * 69a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * <!-- Your scrolling content --> 70a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * 71a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * </android.support.v4.widget.NestedScrollView> 72a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * 73a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * <android.support.design.widget.AppBarLayout 74a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * android:layout_height="wrap_content" 75a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * android:layout_width="match_parent"> 76a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * 77a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * <android.support.v7.widget.Toolbar 78a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * ... 79a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * app:layout_scrollFlags="scroll|enterAlways"/> 80a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * 81a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * <android.support.design.widget.TabLayout 82a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * ... 83a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * app:layout_scrollFlags="scroll|enterAlways"/> 84a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * 85a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * </android.support.design.widget.AppBarLayout> 86a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * 87a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * </android.support.design.widget.CoordinatorLayout> 88a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * </pre> 89a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * 90a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * @see <a href="http://www.google.com/design/spec/layout/structure.html#structure-app-bar"> 91a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * http://www.google.com/design/spec/layout/structure.html#structure-app-bar</a> 92a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes */ 93a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes@CoordinatorLayout.DefaultBehavior(AppBarLayout.Behavior.class) 94a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banespublic class AppBarLayout extends LinearLayout { 95a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 96a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes /** 97631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes * Interface definition for a callback to be invoked when an {@link AppBarLayout}'s vertical 98631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes * offset changes. 99a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes */ 100631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes public interface OnOffsetChangedListener { 101a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes /** 102a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * Called when the {@link AppBarLayout}'s layout offset has been changed. This allows 103a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * child views to implement custom behavior based on the offset (for instance pinning a 104a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * view at a certain y value). 105a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * 106631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes * @param appBarLayout the {@link AppBarLayout} which offset has changed 10750dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes * @param verticalOffset the vertical offset for the parent {@link AppBarLayout}, in px 108a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes */ 109631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset); 110a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 111a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 112a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes private static final int INVALID_SCROLL_RANGE = -1; 113a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 114a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes private int mTotalScrollRange = INVALID_SCROLL_RANGE; 115a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes private int mDownPreScrollRange = INVALID_SCROLL_RANGE; 116a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes private int mDownScrollRange = INVALID_SCROLL_RANGE; 117a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 118a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes boolean mHaveChildWithInterpolator; 119a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 12050dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes private float mTargetElevation; 12150dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes 1226ba61c5c79fe025036593c9daf79cb65299bb0b6Chris Banes private WindowInsetsCompat mLastInsets; 1236ba61c5c79fe025036593c9daf79cb65299bb0b6Chris Banes 124631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes private final List<WeakReference<OnOffsetChangedListener>> mListeners; 125631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes 126a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes public AppBarLayout(Context context) { 127a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes this(context, null); 128a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 129a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 130a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes public AppBarLayout(Context context, AttributeSet attrs) { 131a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes super(context, attrs); 132a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes setOrientation(VERTICAL); 13350dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes 13450dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.AppBarLayout, 13550dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes 0, R.style.Widget_Design_AppBarLayout); 13650dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes mTargetElevation = a.getDimensionPixelSize(R.styleable.AppBarLayout_elevation, 0); 13781520564f3dd783136e025174021ba4eabd6ff3cChris Banes setBackgroundDrawable(a.getDrawable(R.styleable.AppBarLayout_android_background)); 13850dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes a.recycle(); 13950dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes 14050dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes // Use the bounds view outline provider so that we cast a shadow, even without a background 14150dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes ViewUtils.setBoundsViewOutlineProvider(this); 142631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes 143631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes mListeners = new ArrayList<>(); 144631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes 145631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes ViewCompat.setElevation(this, mTargetElevation); 1466ba61c5c79fe025036593c9daf79cb65299bb0b6Chris Banes 1476ba61c5c79fe025036593c9daf79cb65299bb0b6Chris Banes ViewCompat.setOnApplyWindowInsetsListener(this, 1486ba61c5c79fe025036593c9daf79cb65299bb0b6Chris Banes new android.support.v4.view.OnApplyWindowInsetsListener() { 1496ba61c5c79fe025036593c9daf79cb65299bb0b6Chris Banes @Override 1506ba61c5c79fe025036593c9daf79cb65299bb0b6Chris Banes public WindowInsetsCompat onApplyWindowInsets(View v, 1516ba61c5c79fe025036593c9daf79cb65299bb0b6Chris Banes WindowInsetsCompat insets) { 1526ba61c5c79fe025036593c9daf79cb65299bb0b6Chris Banes setWindowInsets(insets); 1536ba61c5c79fe025036593c9daf79cb65299bb0b6Chris Banes return insets.consumeSystemWindowInsets(); 1546ba61c5c79fe025036593c9daf79cb65299bb0b6Chris Banes } 1556ba61c5c79fe025036593c9daf79cb65299bb0b6Chris Banes }); 156631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes } 157631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes 158631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes /** 159631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes * Add a listener that will be called when the offset of this {@link AppBarLayout} changes. 160631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes * 161631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes * @param listener The listener that will be called when the offset changes.] 162631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes * 163631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes * @see #removeOnOffsetChangedListener(OnOffsetChangedListener) 164631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes */ 165631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes public void addOnOffsetChangedListener(OnOffsetChangedListener listener) { 166631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes for (int i = 0, z = mListeners.size(); i < z; i++) { 167631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes final WeakReference<OnOffsetChangedListener> ref = mListeners.get(i); 168631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes if (ref != null && ref.get() == listener) { 169631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes // Listener already added 170631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes return; 171631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes } 172631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes } 173631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes mListeners.add(new WeakReference<>(listener)); 174631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes } 175631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes 176631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes /** 177631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes * Remove the previously added {@link OnOffsetChangedListener}. 178631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes * 179631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes * @param listener the listener to remove. 180631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes */ 181631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes public void removeOnOffsetChangedListener(OnOffsetChangedListener listener) { 182631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes final Iterator<WeakReference<OnOffsetChangedListener>> i = mListeners.iterator(); 183631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes while (i.hasNext()) { 184631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes final WeakReference<OnOffsetChangedListener> ref = i.next(); 185631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes final OnOffsetChangedListener item = ref.get(); 186631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes if (item == listener || item == null) { 187631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes // If the item is null, or is our given listener, remove 188631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes i.remove(); 189631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes } 190631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes } 191a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 192a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 193a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes @Override 194a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes protected void onLayout(boolean changed, int l, int t, int r, int b) { 195a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes super.onLayout(changed, l, t, r, b); 196a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 197a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes // Invalidate the scroll ranges 198a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes mTotalScrollRange = INVALID_SCROLL_RANGE; 199a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes mDownPreScrollRange = INVALID_SCROLL_RANGE; 200a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes mDownPreScrollRange = INVALID_SCROLL_RANGE; 201a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 202a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes mHaveChildWithInterpolator = false; 203a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes for (int i = 0, z = getChildCount(); i < z; i++) { 204a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes final View child = getChildAt(i); 205a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes final LayoutParams childLp = (LayoutParams) child.getLayoutParams(); 206a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes final Interpolator interpolator = childLp.getScrollInterpolator(); 207a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 208a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes if (interpolator != null) { 209a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes mHaveChildWithInterpolator = true; 210a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes break; 211a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 212a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 213a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 214a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 215a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes @Override 216a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes public void setOrientation(int orientation) { 217a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes if (orientation != VERTICAL) { 218a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes throw new IllegalArgumentException("AppBarLayout is always vertical and does" 219a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes + " not support horizontal orientation"); 220a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 221a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes super.setOrientation(orientation); 222a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 223a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 224a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes @Override 225a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { 226a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes return p instanceof LayoutParams; 227a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 228a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 229a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes @Override 230a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes protected LayoutParams generateDefaultLayoutParams() { 231a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); 232a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 233a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 234a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes @Override 235a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes public LayoutParams generateLayoutParams(AttributeSet attrs) { 236a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes return new LayoutParams(getContext(), attrs); 237a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 238a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 239a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes @Override 240a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { 241a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes if (p instanceof LinearLayout.LayoutParams) { 242a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes return new LayoutParams((LinearLayout.LayoutParams) p); 243a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } else if (p instanceof MarginLayoutParams) { 244a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes return new LayoutParams((MarginLayoutParams) p); 245a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 246a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes return new LayoutParams(p); 247a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 248a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 249a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes final boolean hasChildWithInterpolator() { 250a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes return mHaveChildWithInterpolator; 251a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 252a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 253a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes /** 2546ba61c5c79fe025036593c9daf79cb65299bb0b6Chris Banes * Returns the scroll range of all children. 255a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * 256a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * @return the scroll range in px 257a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes */ 2586ba61c5c79fe025036593c9daf79cb65299bb0b6Chris Banes public final int getTotalScrollRange() { 259a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes if (mTotalScrollRange != INVALID_SCROLL_RANGE) { 260a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes return mTotalScrollRange; 261a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 262a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 263a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes int range = 0; 264a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes for (int i = 0, z = getChildCount(); i < z; i++) { 265a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes final View child = getChildAt(i); 266a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 267a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes final int childHeight = ViewCompat.isLaidOut(child) 268a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes ? child.getHeight() 269a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes : child.getMeasuredHeight(); 270a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes final int flags = lp.mScrollFlags; 271a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 272a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes if ((flags & LayoutParams.SCROLL_FLAG_SCROLL) != 0) { 273a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes // We're set to scroll so add the child's height 274a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes range += childHeight; 275a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 276a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes if ((flags & LayoutParams.SCROLL_FLAG_EXIT_UNTIL_COLLAPSED) != 0) { 277a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes // For a collapsing scroll, we to take the collapsed height into account. 2786ba61c5c79fe025036593c9daf79cb65299bb0b6Chris Banes // We also break straight away since later views can't scroll beneath 279a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes // us 2806ba61c5c79fe025036593c9daf79cb65299bb0b6Chris Banes range -= ViewCompat.getMinimumHeight(child); 2816ba61c5c79fe025036593c9daf79cb65299bb0b6Chris Banes break; 282a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 283a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } else { 284a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes // As soon as a view doesn't have the scroll flag, we end the range calculation. 285a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes // This is because views below can not scroll under a fixed view. 286a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes break; 287a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 288a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 2896ba61c5c79fe025036593c9daf79cb65299bb0b6Chris Banes final int top = mLastInsets != null ? mLastInsets.getSystemWindowInsetTop() : 0; 2906ba61c5c79fe025036593c9daf79cb65299bb0b6Chris Banes return mTotalScrollRange = (range - top); 291a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 292a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 293a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes final boolean hasScrollableChildren() { 294a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes return getTotalScrollRange() != 0; 295a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 296a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 297a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes /** 298a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * Return the scroll range when scrolling up from a nested pre-scroll. 299a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes */ 300a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes final int getUpNestedPreScrollRange() { 30150dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes return getTotalScrollRange(); 302a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 303a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 304a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes /** 305a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * Return the scroll range when scrolling down from a nested pre-scroll. 306a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes */ 307a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes final int getDownNestedPreScrollRange() { 308a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes if (mDownPreScrollRange != INVALID_SCROLL_RANGE) { 309a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes // If we already have a valid value, return it 310a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes return mDownPreScrollRange; 311a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 312a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 313a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes int range = 0; 314a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes for (int i = getChildCount() - 1; i >= 0; i--) { 315a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes final View child = getChildAt(i); 316a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 317a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes final int childHeight = ViewCompat.isLaidOut(child) 318a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes ? child.getHeight() 319a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes : child.getMeasuredHeight(); 320a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes final int flags = lp.mScrollFlags; 321a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 322a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes if ((flags & LayoutParams.FLAG_QUICK_RETURN) == LayoutParams.FLAG_QUICK_RETURN) { 323a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes // The view has the quick return flag combination... 324a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes if ((flags & LayoutParams.SCROLL_FLAG_ENTER_ALWAYS_COLLAPSED) != 0) { 325a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes // If they're set to enter collapsed, use the minimum height 326a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes range += ViewCompat.getMinimumHeight(child); 327a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } else { 328a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes // Else use the full height 329a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes range += childHeight; 330a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 331a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } else if (range > 0) { 332a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes // If we've hit an non-quick return scrollable view, and we've already hit a 333a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes // quick return view, return now 334a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes break; 335a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 336a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 337a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes return mDownPreScrollRange = range; 338a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 339a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 340a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes /** 341a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * Return the scroll range when scrolling down from a nested scroll. 342a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes */ 343a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes final int getDownNestedScrollRange() { 344a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes if (mDownScrollRange != INVALID_SCROLL_RANGE) { 345a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes // If we already have a valid value, return it 346a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes return mDownScrollRange; 347a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 348a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 349a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes int range = 0; 350a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes for (int i = getChildCount() - 1; i >= 0; i--) { 351a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes final View child = getChildAt(i); 352a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 353a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes final int childHeight = ViewCompat.isLaidOut(child) 354a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes ? child.getHeight() 355a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes : child.getMeasuredHeight(); 356a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 357a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes final int flags = lp.mScrollFlags; 358a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 359a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes if ((flags & LayoutParams.SCROLL_FLAG_SCROLL) != 0) { 360a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes // We're set to scroll so add the child's height 361a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes range += childHeight; 362a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 363a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes if ((flags & LayoutParams.SCROLL_FLAG_EXIT_UNTIL_COLLAPSED) != 0) { 364a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes // For a collapsing exit scroll, we to take the collapsed height into account. 365a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes // We also return the range straight away since later views can't scroll 366a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes // beneath us 367a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes return range - ViewCompat.getMinimumHeight(child); 368a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 369a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } else { 370a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes // As soon as a view doesn't have the scroll flag, we end the range calculation. 371a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes // This is because views below can not scroll under a fixed view. 372a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes break; 373a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 374a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 375a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes return mDownScrollRange = range; 376a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 377a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 37850dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes final int getMinimumHeightForVisibleOverlappingContent() { 3796ba61c5c79fe025036593c9daf79cb65299bb0b6Chris Banes final int topInset = mLastInsets != null ? mLastInsets.getSystemWindowInsetTop() : 0; 38050dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes final int minHeight = ViewCompat.getMinimumHeight(this); 38150dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes if (minHeight != 0) { 38250dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes // If this layout has a min height, use it (doubled) 3836ba61c5c79fe025036593c9daf79cb65299bb0b6Chris Banes return (minHeight * 2) + topInset; 38450dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes } 38550dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes 38650dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes // Otherwise, we'll use twice the min height of our last child 38750dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes final int childCount = getChildCount(); 38850dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes return childCount >= 1 3896ba61c5c79fe025036593c9daf79cb65299bb0b6Chris Banes ? (ViewCompat.getMinimumHeight(getChildAt(childCount - 1)) * 2) + topInset 39050dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes : 0; 39150dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes } 39250dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes 39350dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes /** 394631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes * Set the elevation value to use when this {@link AppBarLayout} should be elevated 395631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes * above content. 396631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes * <p> 397631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes * This method does not do anything itself. A typical use for this method is called from within 398631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes * an {@link OnOffsetChangedListener} when the offset has changed in such a way to require an 399631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes * elevation change. 400631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes * 401631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes * @param elevation the elevation value to use. 402631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes * 403631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes * @see ViewCompat#setElevation(View, float) 40450dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes */ 405631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes public void setTargetElevation(float elevation) { 406631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes mTargetElevation = elevation; 407631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes } 408631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes 409631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes /** 410631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes * Returns the elevation value to use when this {@link AppBarLayout} should be elevated 411631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes * above content. 412631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes */ 413631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes public float getTargetElevation() { 41450dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes return mTargetElevation; 41550dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes } 41650dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes 4176ba61c5c79fe025036593c9daf79cb65299bb0b6Chris Banes private void setWindowInsets(WindowInsetsCompat insets) { 4186ba61c5c79fe025036593c9daf79cb65299bb0b6Chris Banes // Invalidate the total scroll range... 4196ba61c5c79fe025036593c9daf79cb65299bb0b6Chris Banes mTotalScrollRange = INVALID_SCROLL_RANGE; 4206ba61c5c79fe025036593c9daf79cb65299bb0b6Chris Banes mLastInsets = insets; 4216ba61c5c79fe025036593c9daf79cb65299bb0b6Chris Banes 4226ba61c5c79fe025036593c9daf79cb65299bb0b6Chris Banes // Now dispatch them to our children 4236ba61c5c79fe025036593c9daf79cb65299bb0b6Chris Banes for (int i = 0, z = getChildCount(); i < z; i++) { 4246ba61c5c79fe025036593c9daf79cb65299bb0b6Chris Banes final View child = getChildAt(i); 4256ba61c5c79fe025036593c9daf79cb65299bb0b6Chris Banes insets = ViewCompat.dispatchApplyWindowInsets(child, insets); 4266ba61c5c79fe025036593c9daf79cb65299bb0b6Chris Banes if (insets.isConsumed()) { 4276ba61c5c79fe025036593c9daf79cb65299bb0b6Chris Banes break; 4286ba61c5c79fe025036593c9daf79cb65299bb0b6Chris Banes } 4296ba61c5c79fe025036593c9daf79cb65299bb0b6Chris Banes } 4306ba61c5c79fe025036593c9daf79cb65299bb0b6Chris Banes } 4316ba61c5c79fe025036593c9daf79cb65299bb0b6Chris Banes 432a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes public static class LayoutParams extends LinearLayout.LayoutParams { 43350dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes 43450dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes /** @hide */ 43550dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes @IntDef(flag=true, value={ 43650dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes SCROLL_FLAG_SCROLL, 43750dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes SCROLL_FLAG_EXIT_UNTIL_COLLAPSED, 43850dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes SCROLL_FLAG_ENTER_ALWAYS, 43950dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes SCROLL_FLAG_ENTER_ALWAYS_COLLAPSED 44050dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes }) 44150dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes @Retention(RetentionPolicy.SOURCE) 44250dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes public @interface ScrollFlags {} 44350dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes 444a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes /** 445a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * The view will be scroll in direct relation to scroll events. This flag needs to be 446a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * set for any of the other flags to take effect. If any sibling views 447a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * before this one do not have this flag, then this value has no effect. 448a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes */ 449a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes public static final int SCROLL_FLAG_SCROLL = 0x1; 450a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 451a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes /** 452a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * When exiting (scrolling off screen) the view will be scrolled until it is 453a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * 'collapsed'. The collapsed height is defined by the view's minimum height. 454a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * 455a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * @see ViewCompat#getMinimumHeight(View) 456a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * @see View#setMinimumHeight(int) 457a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes */ 458a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes public static final int SCROLL_FLAG_EXIT_UNTIL_COLLAPSED = 0x2; 459a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 460a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes /** 461a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * When entering (scrolling on screen) the view will scroll on any downwards 462a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * scroll event, regardless of whether the scrolling view is also scrolling. This 463a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * is commonly referred to as the 'quick return' pattern. 464a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes */ 465a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes public static final int SCROLL_FLAG_ENTER_ALWAYS = 0x4; 466a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 467a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes /** 468a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * An additional flag for 'enterAlways' which modifies the returning view to 469a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * only initially scroll back to it's collapsed height. Once the scrolling view has 470a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * reached the end of it's scroll range, the remainder of this view will be scrolled 471a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * into view. The collapsed height is defined by the view's minimum height. 472a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * 473a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * @see ViewCompat#getMinimumHeight(View) 474a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * @see View#setMinimumHeight(int) 475a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes */ 476a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes public static final int SCROLL_FLAG_ENTER_ALWAYS_COLLAPSED = 0x8; 477a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 478a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes /** 479a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * Internal flag which allows quick checking of 'quick return' 480a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes */ 481a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes static final int FLAG_QUICK_RETURN = SCROLL_FLAG_SCROLL | SCROLL_FLAG_ENTER_ALWAYS; 482a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 483a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes int mScrollFlags = SCROLL_FLAG_SCROLL; 484a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes Interpolator mScrollInterpolator; 485a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 486a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes public LayoutParams(Context c, AttributeSet attrs) { 487a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes super(c, attrs); 488a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.AppBarLayout_LayoutParams); 489a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes mScrollFlags = a.getInt(R.styleable.AppBarLayout_LayoutParams_layout_scrollFlags, 0); 490a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes if (a.hasValue(R.styleable.AppBarLayout_LayoutParams_layout_scrollInterpolator)) { 491a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes int resId = a.getResourceId( 492a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes R.styleable.AppBarLayout_LayoutParams_layout_scrollInterpolator, 0); 493a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes mScrollInterpolator = android.view.animation.AnimationUtils.loadInterpolator( 494a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes c, resId); 495a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 496a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes a.recycle(); 497a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 498a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 499a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes public LayoutParams(int width, int height) { 500a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes super(width, height); 501a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 502a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 503a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes public LayoutParams(int width, int height, float weight) { 504a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes super(width, height, weight); 505a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 506a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 507a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes public LayoutParams(ViewGroup.LayoutParams p) { 508a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes super(p); 509a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 510a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 511a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes public LayoutParams(MarginLayoutParams source) { 512a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes super(source); 513a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 514a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 515a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes public LayoutParams(LinearLayout.LayoutParams source) { 516a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes super(source); 517a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 518a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 519a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes public LayoutParams(LayoutParams source) { 520a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes super(source); 521a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes mScrollFlags = source.mScrollFlags; 522a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes mScrollInterpolator = source.mScrollInterpolator; 523a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 524a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 525a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes /** 526a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * Set the scrolling flags. 527a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * 528a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * @param flags bitwise int of {@link #SCROLL_FLAG_SCROLL}, 529a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * {@link #SCROLL_FLAG_EXIT_UNTIL_COLLAPSED}, {@link #SCROLL_FLAG_ENTER_ALWAYS} 530a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * and {@link #SCROLL_FLAG_ENTER_ALWAYS_COLLAPSED}. 531a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * 532a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * @see #getScrollFlags() 533a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * 534149689d3a0b4165577470d7152112674d1d7f87cChris Banes * @attr ref android.support.design.R.styleable#AppBarLayout_LayoutParams_layout_scrollFlags 535a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes */ 53650dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes public void setScrollFlags(@ScrollFlags int flags) { 537a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes mScrollFlags = flags; 538a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 539a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 540a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes /** 541a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * Returns the scrolling flags. 542a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * 543a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * @see #setScrollFlags(int) 544a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * 545149689d3a0b4165577470d7152112674d1d7f87cChris Banes * @attr ref android.support.design.R.styleable#AppBarLayout_LayoutParams_layout_scrollFlags 546a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes */ 54750dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes @ScrollFlags 548a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes public int getScrollFlags() { 549a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes return mScrollFlags; 550a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 551a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 552a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes /** 553a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * Set the interpolator to when scrolling the view associated with this 554a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * {@link LayoutParams}. 555a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * 556a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * @param interpolator the interpolator to use, or null to use normal 1-to-1 scrolling. 557a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * 558149689d3a0b4165577470d7152112674d1d7f87cChris Banes * @attr ref android.support.design.R.styleable#AppBarLayout_LayoutParams_layout_scrollInterpolator 559a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * @see #getScrollInterpolator() 560a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes */ 561a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes public void setScrollInterpolator(Interpolator interpolator) { 562a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes mScrollInterpolator = interpolator; 563a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 564a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 565a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes /** 566a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * Returns the {@link Interpolator} being used for scrolling the view associated with this 567a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * {@link LayoutParams}. Null indicates 'normal' 1-to-1 scrolling. 568a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * 569149689d3a0b4165577470d7152112674d1d7f87cChris Banes * @attr ref android.support.design.R.styleable#AppBarLayout_LayoutParams_layout_scrollInterpolator 570a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * @see #setScrollInterpolator(Interpolator) 571a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes */ 572a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes public Interpolator getScrollInterpolator() { 573a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes return mScrollInterpolator; 574a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 575a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 576a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 577a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes /** 578a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * The default {@link Behavior} for {@link AppBarLayout}. Implements the necessary nested 579a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * scroll handling with offsetting. 580a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes */ 581a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes public static class Behavior extends ViewOffsetBehavior<AppBarLayout> { 582631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes private int mLogicalOffsetTop; 583a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 58450dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes private boolean mSkipNestedPreScroll; 585631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes private Runnable mFlingRunnable; 586631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes private ScrollerCompat mScroller; 587631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes 588631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes private ValueAnimatorCompat mAnimator; 58950dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes 590a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes public Behavior() {} 591a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 592a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes public Behavior(Context context, AttributeSet attrs) { 593a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes super(context, attrs); 594a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 595a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 596a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes @Override 597a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child, 598a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes View directTargetChild, View target, int nestedScrollAxes) { 599a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes // Return true if we're nested scrolling vertically and we have scrollable children 600631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes final boolean started = (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0 601a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes && child.hasScrollableChildren(); 602631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes 603631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes if (started && mAnimator != null) { 604631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes // Cancel any offset animation 605631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes mAnimator.cancel(); 606631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes } 607631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes 608631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes return started; 609a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 610a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 611a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes @Override 612a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child, 613a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes View target, int dx, int dy, int[] consumed) { 61450dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes if (dy != 0 && !mSkipNestedPreScroll) { 61550dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes int min, max; 61650dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes if (dy < 0) { 61750dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes // We're scrolling down 61850dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes min = -child.getTotalScrollRange(); 61950dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes max = min + child.getDownNestedPreScrollRange(); 62050dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes } else { 62150dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes // We're scrolling up 62250dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes min = -child.getUpNestedPreScrollRange(); 62350dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes max = 0; 62450dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes } 62550dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes consumed[1] = scroll(coordinatorLayout, child, dy, min, max); 626a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 627a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 628a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 629a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes @Override 630a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes public void onNestedScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child, 631a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes View target, int dxConsumed, int dyConsumed, 632a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes int dxUnconsumed, int dyUnconsumed) { 63350dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes if (dyUnconsumed < 0) { 63450dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes // If the scrolling view is scrolling down but not consuming, it's probably be at 63550dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes // the top of it's content 63650dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes scroll(coordinatorLayout, child, dyUnconsumed, 63750dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes -child.getDownNestedScrollRange(), 0); 63850dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes // Set the expanding flag so that onNestedPreScroll doesn't handle any events 63950dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes mSkipNestedPreScroll = true; 64050dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes } else { 64150dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes // As we're no longer handling nested scrolls, reset the skip flag 64250dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes mSkipNestedPreScroll = false; 643a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 64450dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes } 645a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 64650dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes @Override 64750dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child, 64850dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes View target) { 64950dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes // Reset the skip flag 65050dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes mSkipNestedPreScroll = false; 65150dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes } 65250dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes 65350dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes @Override 654631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes public boolean onNestedFling(final CoordinatorLayout coordinatorLayout, 655631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes final AppBarLayout child, View target, float velocityX, float velocityY, 656631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes boolean consumed) { 657631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes if (!consumed) { 658631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes // It has been consumed so let's fling ourselves 659631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes return fling(coordinatorLayout, child, -child.getTotalScrollRange(), 0, -velocityY); 660631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes } else { 661631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes // If we're scrolling up and the child also consumed the fling. We'll fake scroll 662631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes // upto our 'collapsed' offset 663631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes int targetScroll; 664631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes if (velocityY < 0) { 665631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes // We're scrolling down 666631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes targetScroll = -child.getTotalScrollRange() 667631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes + child.getDownNestedPreScrollRange(); 668631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes 669631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes if (getTopBottomOffsetForScrollingSibling() > targetScroll) { 670631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes // If we're currently expanded more than the target scroll, we'll return false 671631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes // now. This is so that we don't 'scroll' the wrong way. 672631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes return false; 673631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes } 674631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes } else { 675631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes // We're scrolling up 676631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes targetScroll = -child.getUpNestedPreScrollRange(); 677631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes 678631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes if (getTopBottomOffsetForScrollingSibling() < targetScroll) { 679631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes // If we're currently expanded less than the target scroll, we'll return 680631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes // false now. This is so that we don't 'scroll' the wrong way. 681631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes return false; 682631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes } 683631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes } 684631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes 685631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes if (mLogicalOffsetTop != targetScroll) { 686631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes animateOffsetTo(coordinatorLayout, child, targetScroll); 687631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes return true; 688631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes } 689631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes } 690631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes 691631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes return false; 692631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes } 693631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes 694631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes private void animateOffsetTo(final CoordinatorLayout coordinatorLayout, 695631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes final AppBarLayout child, int offset) { 696631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes if (mAnimator == null) { 697631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes mAnimator = ViewUtils.createAnimator(); 698631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes mAnimator.setInterpolator(AnimationUtils.DECELERATE_INTERPOLATOR); 699631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes mAnimator.setUpdateListener(new ValueAnimatorCompat.AnimatorUpdateListener() { 700631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes @Override 701631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes public void onAnimationUpdate(ValueAnimatorCompat animator) { 702631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes setAppBarTopBottomOffset(coordinatorLayout, child, 703631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes animator.getAnimatedIntValue()); 704631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes } 705631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes }); 706631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes } else { 707631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes mAnimator.cancel(); 708631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes } 709631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes 710631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes mAnimator.setIntValues(getTopBottomOffsetForScrollingSibling(), offset); 711631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes mAnimator.start(); 712631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes } 713631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes 714631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes private boolean fling(CoordinatorLayout coordinatorLayout, AppBarLayout layout, int minOffset, 715631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes int maxOffset, float velocityY) { 716631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes if (mFlingRunnable != null) { 717631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes layout.removeCallbacks(mFlingRunnable); 718631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes } 719631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes 720631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes if (mScroller == null) { 721631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes mScroller = ScrollerCompat.create(layout.getContext()); 722631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes } 723631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes 724631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes mScroller.fling( 725631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes 0, mLogicalOffsetTop, // curr 726631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes 0, Math.round(velocityY), // velocity. 727631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes 0, 0, // x 728631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes minOffset, maxOffset); // y 729631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes 730631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes if (mScroller.computeScrollOffset()) { 731631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes mFlingRunnable = new FlingRunnable(coordinatorLayout, layout); 732631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes ViewCompat.postOnAnimation(layout, mFlingRunnable); 733631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes return true; 734631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes } else { 735631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes mFlingRunnable = null; 736631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes return false; 737631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes } 738631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes } 739631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes 740631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes private class FlingRunnable implements Runnable { 741631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes private final CoordinatorLayout mParent; 742631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes private final AppBarLayout mLayout; 743631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes 744631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes FlingRunnable(CoordinatorLayout parent, AppBarLayout layout) { 745631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes mParent = parent; 746631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes mLayout = layout; 747631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes } 748631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes 749631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes @Override 750631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes public void run() { 751631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes if (mLayout != null && mScroller != null && mScroller.computeScrollOffset()) { 752631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes setAppBarTopBottomOffset(mParent, mLayout, mScroller.getCurrY()); 753631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes 754631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes // Post ourselves so that we run on the next animation 755631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes ViewCompat.postOnAnimation(mLayout, this); 756631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes } 757631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes } 758631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes } 759631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes 760631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes @Override 76150dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes public boolean onLayoutChild(CoordinatorLayout parent, AppBarLayout child, 76250dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes int layoutDirection) { 76350dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes boolean handled = super.onLayoutChild(parent, child, layoutDirection); 76450dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes 76550dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes // Make sure we update the elevation 766631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes dispatchOffsetUpdates(child); 76750dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes 76850dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes return handled; 769a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 770a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 771a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes private int scroll(CoordinatorLayout coordinatorLayout, AppBarLayout appBarLayout, 77250dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes int dy, int minOffset, int maxOffset) { 773a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes return setAppBarTopBottomOffset(coordinatorLayout, appBarLayout, 774631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes mLogicalOffsetTop - dy, minOffset, maxOffset); 775a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 776a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 777631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes final int setAppBarTopBottomOffset(CoordinatorLayout coordinatorLayout, 778631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes AppBarLayout appBarLayout, int newOffset) { 779631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes return setAppBarTopBottomOffset(coordinatorLayout, appBarLayout, newOffset, 780631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes Integer.MIN_VALUE, Integer.MAX_VALUE); 781631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes } 782631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes 783631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes final int setAppBarTopBottomOffset(CoordinatorLayout coordinatorLayout, 78450dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes AppBarLayout appBarLayout, int newOffset, int minOffset, int maxOffset) { 785631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes final int curOffset = mLogicalOffsetTop; 786a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes int consumed = 0; 787a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 788631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes if (minOffset != 0 && curOffset >= minOffset && curOffset <= maxOffset) { 789631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes // If we have some scrolling range, and we're currently within the min and max 790631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes // offsets, calculate a new offset 79150dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes newOffset = MathUtils.constrain(newOffset, minOffset, maxOffset); 792a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 793a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes if (curOffset != newOffset) { 794a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes boolean offsetChanged = setTopAndBottomOffset( 795a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes appBarLayout.hasChildWithInterpolator() 796a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes ? interpolateOffset(appBarLayout, newOffset) 797a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes : newOffset); 798631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes 799a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes // Update how much dy we have consumed 800a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes consumed = curOffset - newOffset; 801a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes // Update the stored sibling offset 802631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes mLogicalOffsetTop = newOffset; 803a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 804a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes if (!offsetChanged && appBarLayout.hasChildWithInterpolator()) { 805a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes // If the offset hasn't changed and we're using an interpolated scroll 806a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes // then we need to keep any dependent views updated. CoL will do this for 807a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes // us when we move, but we need to do it manually when we don't (as an 808a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes // interpolated scroll may finish early). 809a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes coordinatorLayout.dispatchDependentViewsChanged(appBarLayout); 810a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 811a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 812631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes // Dispatch the updates to any listeners 813631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes dispatchOffsetUpdates(appBarLayout); 814a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 815a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 816a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 817a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes return consumed; 818a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 819a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 820631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes private void dispatchOffsetUpdates(AppBarLayout layout) { 821631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes final List<WeakReference<OnOffsetChangedListener>> listeners = layout.mListeners; 82250dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes 823631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes // Iterate backwards through the list so that most recently added listeners 824631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes // get the first chance to decide 825631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes for (int i = 0, z = listeners.size(); i < z; i++) { 826631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes final WeakReference<OnOffsetChangedListener> ref = listeners.get(i); 827631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes final OnOffsetChangedListener listener = ref != null ? ref.get() : null; 82850dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes 829631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes if (listener != null) { 830631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes listener.onOffsetChanged(layout, getTopAndBottomOffset()); 831a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 832a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 833a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 834a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 835a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes private int interpolateOffset(AppBarLayout layout, final int offset) { 836a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes final int absOffset = Math.abs(offset); 837a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 838a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes for (int i = 0, z = layout.getChildCount(); i < z; i++) { 839a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes final View child = layout.getChildAt(i); 840a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes final AppBarLayout.LayoutParams childLp = (LayoutParams) child.getLayoutParams(); 84150dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes final Interpolator interpolator = childLp.getScrollInterpolator(); 842a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 84350dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes if (absOffset >= child.getTop() && absOffset <= child.getBottom()) { 844a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes if (interpolator != null) { 84550dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes int childScrollableHeight = 0; 84650dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes final int flags = childLp.getScrollFlags(); 84750dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes if ((flags & LayoutParams.SCROLL_FLAG_SCROLL) != 0) { 84850dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes // We're set to scroll so add the child's height 84950dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes childScrollableHeight += child.getHeight(); 85050dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes if ((flags & LayoutParams.SCROLL_FLAG_EXIT_UNTIL_COLLAPSED) != 0) { 85150dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes // For a collapsing scroll, we to take the collapsed height into account. 85250dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes childScrollableHeight -= ViewCompat.getMinimumHeight(child); 85350dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes } 85450dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes } 85550dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes 85650dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes if (childScrollableHeight > 0) { 85750dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes final int offsetForView = absOffset - child.getTop(); 85850dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes final int interpolatedDiff = Math.round(childScrollableHeight * 85950dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes interpolator.getInterpolation( 86050dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes offsetForView / (float) childScrollableHeight)); 86150dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes 86250dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes return Integer.signum(offset) * (child.getTop() + interpolatedDiff); 86350dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes } 864a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 86550dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes 86650dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes // If we get to here then the view on the offset isn't suitable for interpolated 86750dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes // scrolling. So break out of the loop 86850dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes break; 869a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 870a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 871a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 872a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes return offset; 873a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 874a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 875a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes final int getTopBottomOffsetForScrollingSibling() { 876631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes return mLogicalOffsetTop; 877a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 878a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 879a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 880a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes /** 881a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * Behavior which should be used by {@link View}s which can scroll vertically and support 882a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * nested scrolling to automatically scroll any {@link AppBarLayout} siblings. 883a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes */ 884a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes public static class ScrollingViewBehavior extends ViewOffsetBehavior<View> { 885a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes private int mOverlayTop; 886a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 887a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes public ScrollingViewBehavior() {} 888a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 889a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes public ScrollingViewBehavior(Context context, AttributeSet attrs) { 890a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes super(context, attrs); 891a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 892a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes TypedArray a = context.obtainStyledAttributes(attrs, 893a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes R.styleable.ScrollingViewBehavior_Params); 894a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes mOverlayTop = a.getDimensionPixelSize( 895a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes R.styleable.ScrollingViewBehavior_Params_behavior_overlapTop, 0); 896a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes a.recycle(); 897a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 898a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 899a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes @Override 900a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) { 901a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes // We depend on any AppBarLayouts 902a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes return dependency instanceof AppBarLayout; 903a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 904a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 905a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes @Override 906a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes public boolean onMeasureChild(CoordinatorLayout parent, View child, 907a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes int parentWidthMeasureSpec, int widthUsed, 908a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes int parentHeightMeasureSpec, int heightUsed) { 909a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes if (child.getLayoutParams().height == LayoutParams.MATCH_PARENT) { 910a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes // If the child's height is set to match_parent then it with it's maximum visible 911a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes // visible height 912a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 913a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes final List<View> dependencies = parent.getDependencies(child); 914a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes if (dependencies.isEmpty()) { 915a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes // If we don't have any dependencies, return false 916a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes return false; 917a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 918a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 919a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes final AppBarLayout appBar = findFirstAppBarLayout(dependencies); 9206ba61c5c79fe025036593c9daf79cb65299bb0b6Chris Banes if (appBar != null && ViewCompat.isLaidOut(appBar)) { 9216ba61c5c79fe025036593c9daf79cb65299bb0b6Chris Banes if (ViewCompat.getFitsSystemWindows(appBar)) { 9226ba61c5c79fe025036593c9daf79cb65299bb0b6Chris Banes // If the AppBarLayout is fitting system windows then we need to also, 9236ba61c5c79fe025036593c9daf79cb65299bb0b6Chris Banes // otherwise we'll get CoL's compatible layout functionality 9246ba61c5c79fe025036593c9daf79cb65299bb0b6Chris Banes ViewCompat.setFitsSystemWindows(child, true); 925a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 926a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 927a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes final int scrollRange = appBar.getTotalScrollRange(); 9286ba61c5c79fe025036593c9daf79cb65299bb0b6Chris Banes final int height = parent.getHeight() 929a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes - appBar.getMeasuredHeight() + scrollRange; 930a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes final int heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, 931a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes MeasureSpec.AT_MOST); 932a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 933a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes // Now measure the scrolling child with the correct height 934a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes parent.onMeasureChild(child, parentWidthMeasureSpec, 935a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes widthUsed, heightMeasureSpec, heightUsed); 936a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 937a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes return true; 938a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 939a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 940a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes return false; 941a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 942a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 943a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes @Override 944a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes public boolean onDependentViewChanged(CoordinatorLayout parent, View child, 945a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes View dependency) { 946a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes final CoordinatorLayout.Behavior behavior = 947a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes ((CoordinatorLayout.LayoutParams) dependency.getLayoutParams()).getBehavior(); 948a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes if (behavior instanceof Behavior) { 949a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes // Offset the child so that it is below the app-bar (with any overlap) 950a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 951a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes final int appBarOffset = ((Behavior) behavior) 952a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes .getTopBottomOffsetForScrollingSibling(); 953a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes final int expandedMax = dependency.getHeight() - mOverlayTop; 954a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes final int collapsedMin = parent.getHeight() - child.getHeight(); 955a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 956a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes if (mOverlayTop != 0 && dependency instanceof AppBarLayout) { 957a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes // If we have an overlap top, and the dependency is an AppBarLayout, we control 958a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes // the offset ourselves based on the appbar's scroll progress. This is so that 959a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes // the scroll happens sequentially rather than linearly 960a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes final int scrollRange = ((AppBarLayout) dependency).getTotalScrollRange(); 961a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes setTopAndBottomOffset(AnimationUtils.lerp(expandedMax, collapsedMin, 962a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes Math.abs(appBarOffset) / (float) scrollRange)); 963a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } else { 964a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes setTopAndBottomOffset(MathUtils.constrain( 965a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes dependency.getHeight() - mOverlayTop + appBarOffset, 966a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes collapsedMin, expandedMax)); 967a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 968a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 969a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes return false; 970a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 971a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 972a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes /** 973a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * Set the distance that this view should overlap any {@link AppBarLayout}. 974a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * 975a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * @param overlayTop the distance in px 976a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes */ 977a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes public void setOverlayTop(int overlayTop) { 978a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes mOverlayTop = overlayTop; 979a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 980a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 981a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes /** 982a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * Returns the distance that this view should overlap any {@link AppBarLayout}. 983a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes */ 984a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes public int getOverlayTop() { 985a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes return mOverlayTop; 986a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 987a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 988a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes private static AppBarLayout findFirstAppBarLayout(List<View> views) { 989a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes for (int i = 0, z = views.size(); i < z; i++) { 990a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes View view = views.get(i); 991a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes if (view instanceof AppBarLayout) { 992a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes return (AppBarLayout) view; 993a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 994a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 995a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes return null; 996a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 997a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 998a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes} 999