AppBarLayout.java revision 8818e8e12682e5df1535cd9b2d792494bd3e059d
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; 21e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banesimport android.os.Parcel; 22e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banesimport android.os.Parcelable; 2350dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banesimport android.support.annotation.IntDef; 2472a0913607198c5ce3fa351242ccbdfb3b93f178Chris Banesimport android.support.annotation.NonNull; 2572a0913607198c5ce3fa351242ccbdfb3b93f178Chris Banesimport android.support.annotation.Nullable; 26a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banesimport android.support.design.R; 2762ab25d71ccfa775a8bd2add7b41277bc4e14bbcChris Banesimport android.support.v4.os.ParcelableCompat; 2862ab25d71ccfa775a8bd2add7b41277bc4e14bbcChris Banesimport android.support.v4.os.ParcelableCompatCreatorCallbacks; 29a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banesimport android.support.v4.view.ViewCompat; 306ba61c5c79fe025036593c9daf79cb65299bb0b6Chris Banesimport android.support.v4.view.WindowInsetsCompat; 31a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banesimport android.util.AttributeSet; 32a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banesimport android.view.View; 33a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banesimport android.view.ViewGroup; 34a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banesimport android.view.animation.Interpolator; 35a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banesimport android.widget.LinearLayout; 36a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 3750dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banesimport java.lang.annotation.Retention; 3850dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banesimport java.lang.annotation.RetentionPolicy; 39d4c72f08279df548fbc4d556fe1a8b3500fafdbcChris Banesimport java.lang.ref.WeakReference; 40631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banesimport java.util.ArrayList; 41a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banesimport java.util.List; 42a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 43a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes/** 44a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * AppBarLayout is a vertical {@link LinearLayout} which implements many of the features of 459fb154338a62edc2c57dc036895199d6f1769400Chris Banes * material designs app bar concept, namely scrolling gestures. 46a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * <p> 47a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * Children should provide their desired scrolling behavior through 48a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * {@link LayoutParams#setScrollFlags(int)} and the associated layout xml attribute: 49a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * {@code app:layout_scrollFlags}. 50a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * 51a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * <p> 52a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * This view depends heavily on being used as a direct child within a {@link CoordinatorLayout}. 53a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * If you use AppBarLayout within a different {@link ViewGroup}, most of it's functionality will 54a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * not work. 55a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * <p> 569fb154338a62edc2c57dc036895199d6f1769400Chris Banes * AppBarLayout also requires a separate scrolling sibling in order to know when to scroll. 579fb154338a62edc2c57dc036895199d6f1769400Chris Banes * The binding is done through the {@link ScrollingViewBehavior} behavior class, meaning that you 589fb154338a62edc2c57dc036895199d6f1769400Chris Banes * should set your scrolling view's behavior to be an instance of {@link ScrollingViewBehavior}. 599fb154338a62edc2c57dc036895199d6f1769400Chris Banes * A string resource containing the full class name is available. 60a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * 61a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * <pre> 62a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * <android.support.design.widget.CoordinatorLayout 63a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * xmlns:android="http://schemas.android.com/apk/res/android" 64a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * xmlns:app="http://schemas.android.com/apk/res-auto" 65a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * android:layout_width="match_parent" 66a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * android:layout_height="match_parent"> 67a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * 68a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * <android.support.v4.widget.NestedScrollView 69a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * android:layout_width="match_parent" 70a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * android:layout_height="match_parent" 71a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * app:layout_behavior="@string/appbar_scrolling_view_behavior"> 72a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * 73a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * <!-- Your scrolling content --> 74a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * 75a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * </android.support.v4.widget.NestedScrollView> 76a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * 77a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * <android.support.design.widget.AppBarLayout 78a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * android:layout_height="wrap_content" 79a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * android:layout_width="match_parent"> 80a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * 81a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * <android.support.v7.widget.Toolbar 82a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * ... 83a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * app:layout_scrollFlags="scroll|enterAlways"/> 84a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * 85a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * <android.support.design.widget.TabLayout 86a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * ... 87a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * app:layout_scrollFlags="scroll|enterAlways"/> 88a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * 89a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * </android.support.design.widget.AppBarLayout> 90a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * 91a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * </android.support.design.widget.CoordinatorLayout> 92a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * </pre> 93a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * 94a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * @see <a href="http://www.google.com/design/spec/layout/structure.html#structure-app-bar"> 95a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * http://www.google.com/design/spec/layout/structure.html#structure-app-bar</a> 96a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes */ 97a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes@CoordinatorLayout.DefaultBehavior(AppBarLayout.Behavior.class) 98a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banespublic class AppBarLayout extends LinearLayout { 99a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 1009995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes private static final int PENDING_ACTION_NONE = 0x0; 1019995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes private static final int PENDING_ACTION_EXPANDED = 0x1; 1029995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes private static final int PENDING_ACTION_COLLAPSED = 0x2; 1039995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes private static final int PENDING_ACTION_ANIMATE_ENABLED = 0x4; 1049995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes 105a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes /** 106631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes * Interface definition for a callback to be invoked when an {@link AppBarLayout}'s vertical 107631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes * offset changes. 108a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes */ 109631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes public interface OnOffsetChangedListener { 110a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes /** 111a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * Called when the {@link AppBarLayout}'s layout offset has been changed. This allows 112a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * child views to implement custom behavior based on the offset (for instance pinning a 113a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * view at a certain y value). 114a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * 115631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes * @param appBarLayout the {@link AppBarLayout} which offset has changed 11650dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes * @param verticalOffset the vertical offset for the parent {@link AppBarLayout}, in px 117a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes */ 118631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset); 119a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 120a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 121a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes private static final int INVALID_SCROLL_RANGE = -1; 122a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 123a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes private int mTotalScrollRange = INVALID_SCROLL_RANGE; 124a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes private int mDownPreScrollRange = INVALID_SCROLL_RANGE; 125a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes private int mDownScrollRange = INVALID_SCROLL_RANGE; 126a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 127a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes boolean mHaveChildWithInterpolator; 128a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 12950dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes private float mTargetElevation; 13050dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes 1319995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes private int mPendingAction = PENDING_ACTION_NONE; 1329995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes 1336ba61c5c79fe025036593c9daf79cb65299bb0b6Chris Banes private WindowInsetsCompat mLastInsets; 1346ba61c5c79fe025036593c9daf79cb65299bb0b6Chris Banes 1356f730c0acfb10a929172ea2981a1aded0e39f5c7Chris Banes private final List<OnOffsetChangedListener> mListeners; 136631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes 137a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes public AppBarLayout(Context context) { 138a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes this(context, null); 139a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 140a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 141a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes public AppBarLayout(Context context, AttributeSet attrs) { 142a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes super(context, attrs); 143a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes setOrientation(VERTICAL); 14450dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes 145809bb62055ad42b88f3a69308be222801b89fbd9Chris Banes ThemeUtils.checkAppCompatTheme(context); 146809bb62055ad42b88f3a69308be222801b89fbd9Chris Banes 14750dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.AppBarLayout, 14850dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes 0, R.style.Widget_Design_AppBarLayout); 14950dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes mTargetElevation = a.getDimensionPixelSize(R.styleable.AppBarLayout_elevation, 0); 15081520564f3dd783136e025174021ba4eabd6ff3cChris Banes setBackgroundDrawable(a.getDrawable(R.styleable.AppBarLayout_android_background)); 1519995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes if (a.hasValue(R.styleable.AppBarLayout_expanded)) { 1529995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes setExpanded(a.getBoolean(R.styleable.AppBarLayout_expanded, false)); 1539995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes } 15450dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes a.recycle(); 15550dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes 15650dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes // Use the bounds view outline provider so that we cast a shadow, even without a background 15750dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes ViewUtils.setBoundsViewOutlineProvider(this); 158631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes 159631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes mListeners = new ArrayList<>(); 160631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes 161631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes ViewCompat.setElevation(this, mTargetElevation); 1626ba61c5c79fe025036593c9daf79cb65299bb0b6Chris Banes 1636ba61c5c79fe025036593c9daf79cb65299bb0b6Chris Banes ViewCompat.setOnApplyWindowInsetsListener(this, 1646ba61c5c79fe025036593c9daf79cb65299bb0b6Chris Banes new android.support.v4.view.OnApplyWindowInsetsListener() { 1656ba61c5c79fe025036593c9daf79cb65299bb0b6Chris Banes @Override 1666ba61c5c79fe025036593c9daf79cb65299bb0b6Chris Banes public WindowInsetsCompat onApplyWindowInsets(View v, 1676ba61c5c79fe025036593c9daf79cb65299bb0b6Chris Banes WindowInsetsCompat insets) { 1688818e8e12682e5df1535cd9b2d792494bd3e059dChris Banes return onWindowInsetChanged(insets); 1696ba61c5c79fe025036593c9daf79cb65299bb0b6Chris Banes } 1706ba61c5c79fe025036593c9daf79cb65299bb0b6Chris Banes }); 171631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes } 172631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes 173631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes /** 174631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes * Add a listener that will be called when the offset of this {@link AppBarLayout} changes. 175631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes * 176631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes * @param listener The listener that will be called when the offset changes.] 177631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes * 178631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes * @see #removeOnOffsetChangedListener(OnOffsetChangedListener) 179631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes */ 180631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes public void addOnOffsetChangedListener(OnOffsetChangedListener listener) { 1816f730c0acfb10a929172ea2981a1aded0e39f5c7Chris Banes if (listener != null && !mListeners.contains(listener)) { 1826f730c0acfb10a929172ea2981a1aded0e39f5c7Chris Banes mListeners.add(listener); 183631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes } 184631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes } 185631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes 186631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes /** 187631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes * Remove the previously added {@link OnOffsetChangedListener}. 188631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes * 189631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes * @param listener the listener to remove. 190631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes */ 191631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes public void removeOnOffsetChangedListener(OnOffsetChangedListener listener) { 1926f730c0acfb10a929172ea2981a1aded0e39f5c7Chris Banes if (listener != null) { 1936f730c0acfb10a929172ea2981a1aded0e39f5c7Chris Banes mListeners.remove(listener); 194631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes } 195a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 196a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 197a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes @Override 198ea004a01f44aaf36212120c936952c1742d03d30Chris Banes protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 199ea004a01f44aaf36212120c936952c1742d03d30Chris Banes super.onMeasure(widthMeasureSpec, heightMeasureSpec); 200ea004a01f44aaf36212120c936952c1742d03d30Chris Banes invalidateScrollRanges(); 201ea004a01f44aaf36212120c936952c1742d03d30Chris Banes } 202ea004a01f44aaf36212120c936952c1742d03d30Chris Banes 203ea004a01f44aaf36212120c936952c1742d03d30Chris Banes @Override 204a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes protected void onLayout(boolean changed, int l, int t, int r, int b) { 205a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes super.onLayout(changed, l, t, r, b); 20679aa418487cdefb85d3c1fac32d6403a1ac6057dChris Banes invalidateScrollRanges(); 207a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 208a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes mHaveChildWithInterpolator = false; 209a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes for (int i = 0, z = getChildCount(); i < z; i++) { 210a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes final View child = getChildAt(i); 211a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes final LayoutParams childLp = (LayoutParams) child.getLayoutParams(); 212a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes final Interpolator interpolator = childLp.getScrollInterpolator(); 213a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 214a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes if (interpolator != null) { 215a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes mHaveChildWithInterpolator = true; 216a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes break; 217a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 218a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 219a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 220a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 221ea004a01f44aaf36212120c936952c1742d03d30Chris Banes private void invalidateScrollRanges() { 222ea004a01f44aaf36212120c936952c1742d03d30Chris Banes // Invalidate the scroll ranges 223ea004a01f44aaf36212120c936952c1742d03d30Chris Banes mTotalScrollRange = INVALID_SCROLL_RANGE; 224ea004a01f44aaf36212120c936952c1742d03d30Chris Banes mDownPreScrollRange = INVALID_SCROLL_RANGE; 225ea004a01f44aaf36212120c936952c1742d03d30Chris Banes mDownScrollRange = INVALID_SCROLL_RANGE; 226ea004a01f44aaf36212120c936952c1742d03d30Chris Banes } 227ea004a01f44aaf36212120c936952c1742d03d30Chris Banes 228a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes @Override 229a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes public void setOrientation(int orientation) { 230a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes if (orientation != VERTICAL) { 231a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes throw new IllegalArgumentException("AppBarLayout is always vertical and does" 232a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes + " not support horizontal orientation"); 233a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 234a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes super.setOrientation(orientation); 235a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 236a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 2379995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes /** 2389995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes * Sets whether this {@link AppBarLayout} is expanded or not, animating if it has already 2399995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes * been laid out. 2409995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes * 2419995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes * <p>As with {@link AppBarLayout}'s scrolling, this method relies on this layout being a 2429995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes * direct child of a {@link CoordinatorLayout}.</p> 2439995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes * 2449995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes * @param expanded true if the layout should be fully expanded, false if it should 2459995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes * be fully collapsed 2469995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes * 2479995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes * @attr ref android.support.design.R.styleable#AppBarLayout_expanded 2489995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes */ 2499995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes public void setExpanded(boolean expanded) { 2509995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes setExpanded(expanded, ViewCompat.isLaidOut(this)); 2519995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes } 2529995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes 2539995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes /** 2549995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes * Sets whether this {@link AppBarLayout} is expanded or not. 2559995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes * 2569995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes * <p>As with {@link AppBarLayout}'s scrolling, this method relies on this layout being a 2579995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes * direct child of a {@link CoordinatorLayout}.</p> 2589995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes * 2599995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes * @param expanded true if the layout should be fully expanded, false if it should 2609995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes * be fully collapsed 2619995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes * @param animate Whether to animate to the new state 2629995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes * 2639995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes * @attr ref android.support.design.R.styleable#AppBarLayout_expanded 2649995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes */ 2659995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes public void setExpanded(boolean expanded, boolean animate) { 2669995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes mPendingAction = (expanded ? PENDING_ACTION_EXPANDED : PENDING_ACTION_COLLAPSED) 2679995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes | (animate ? PENDING_ACTION_ANIMATE_ENABLED : 0); 2689995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes requestLayout(); 2699995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes } 2709995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes 271a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes @Override 272a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { 273a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes return p instanceof LayoutParams; 274a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 275a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 276a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes @Override 277a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes protected LayoutParams generateDefaultLayoutParams() { 278a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); 279a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 280a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 281a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes @Override 282a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes public LayoutParams generateLayoutParams(AttributeSet attrs) { 283a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes return new LayoutParams(getContext(), attrs); 284a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 285a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 286a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes @Override 287a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { 288a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes if (p instanceof LinearLayout.LayoutParams) { 289a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes return new LayoutParams((LinearLayout.LayoutParams) p); 290a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } else if (p instanceof MarginLayoutParams) { 291a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes return new LayoutParams((MarginLayoutParams) p); 292a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 293a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes return new LayoutParams(p); 294a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 295a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 296ea004a01f44aaf36212120c936952c1742d03d30Chris Banes private boolean hasChildWithInterpolator() { 297a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes return mHaveChildWithInterpolator; 298a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 299a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 300a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes /** 3016ba61c5c79fe025036593c9daf79cb65299bb0b6Chris Banes * Returns the scroll range of all children. 302a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * 303a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * @return the scroll range in px 304a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes */ 3056ba61c5c79fe025036593c9daf79cb65299bb0b6Chris Banes public final int getTotalScrollRange() { 306a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes if (mTotalScrollRange != INVALID_SCROLL_RANGE) { 307a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes return mTotalScrollRange; 308a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 309a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 310a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes int range = 0; 311a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes for (int i = 0, z = getChildCount(); i < z; i++) { 312a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes final View child = getChildAt(i); 313a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 314ea004a01f44aaf36212120c936952c1742d03d30Chris Banes final int childHeight = child.getMeasuredHeight(); 315a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes final int flags = lp.mScrollFlags; 316a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 317a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes if ((flags & LayoutParams.SCROLL_FLAG_SCROLL) != 0) { 318a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes // We're set to scroll so add the child's height 319b263b91ba75dc1a7855186e8b0e9cf75d46a6b8dChris Banes range += childHeight + lp.topMargin + lp.bottomMargin; 320a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 321a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes if ((flags & LayoutParams.SCROLL_FLAG_EXIT_UNTIL_COLLAPSED) != 0) { 322a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes // For a collapsing scroll, we to take the collapsed height into account. 3236ba61c5c79fe025036593c9daf79cb65299bb0b6Chris Banes // We also break straight away since later views can't scroll beneath 324a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes // us 3256ba61c5c79fe025036593c9daf79cb65299bb0b6Chris Banes range -= ViewCompat.getMinimumHeight(child); 3266ba61c5c79fe025036593c9daf79cb65299bb0b6Chris Banes break; 327a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 328a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } else { 329a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes // As soon as a view doesn't have the scroll flag, we end the range calculation. 330a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes // This is because views below can not scroll under a fixed view. 331a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes break; 332a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 333a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 3344832063daa6620f0ad51c4a16f31502dab1adc89Chris Banes return mTotalScrollRange = Math.max(0, range - getTopInset()); 335a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 336a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 337ea004a01f44aaf36212120c936952c1742d03d30Chris Banes private boolean hasScrollableChildren() { 338a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes return getTotalScrollRange() != 0; 339a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 340a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 341a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes /** 342a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * Return the scroll range when scrolling up from a nested pre-scroll. 343a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes */ 344ea004a01f44aaf36212120c936952c1742d03d30Chris Banes private int getUpNestedPreScrollRange() { 34550dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes return getTotalScrollRange(); 346a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 347a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 348a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes /** 349a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * Return the scroll range when scrolling down from a nested pre-scroll. 350a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes */ 351ea004a01f44aaf36212120c936952c1742d03d30Chris Banes private int getDownNestedPreScrollRange() { 352a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes if (mDownPreScrollRange != INVALID_SCROLL_RANGE) { 353a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes // If we already have a valid value, return it 354a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes return mDownPreScrollRange; 355a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 356a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 357a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes int range = 0; 358a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes for (int i = getChildCount() - 1; i >= 0; i--) { 359a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes final View child = getChildAt(i); 360a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 361ea004a01f44aaf36212120c936952c1742d03d30Chris Banes final int childHeight = child.getMeasuredHeight(); 362a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes final int flags = lp.mScrollFlags; 363a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 364a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes if ((flags & LayoutParams.FLAG_QUICK_RETURN) == LayoutParams.FLAG_QUICK_RETURN) { 365b263b91ba75dc1a7855186e8b0e9cf75d46a6b8dChris Banes // First take the margin into account 366b263b91ba75dc1a7855186e8b0e9cf75d46a6b8dChris Banes range += lp.topMargin + lp.bottomMargin; 367a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes // The view has the quick return flag combination... 368a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes if ((flags & LayoutParams.SCROLL_FLAG_ENTER_ALWAYS_COLLAPSED) != 0) { 369a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes // If they're set to enter collapsed, use the minimum height 370a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes range += ViewCompat.getMinimumHeight(child); 37147543aa43aacb8defbbf90682fcb2b63ce1b00b5Chris Banes } else if ((flags & LayoutParams.SCROLL_FLAG_EXIT_UNTIL_COLLAPSED) != 0) { 37247543aa43aacb8defbbf90682fcb2b63ce1b00b5Chris Banes // Only enter by the amount of the collapsed height 37347543aa43aacb8defbbf90682fcb2b63ce1b00b5Chris Banes range += childHeight - ViewCompat.getMinimumHeight(child); 374a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } else { 375a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes // Else use the full height 376a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes range += childHeight; 377a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 378a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } else if (range > 0) { 379a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes // If we've hit an non-quick return scrollable view, and we've already hit a 380a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes // quick return view, return now 381a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes break; 382a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 383a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 38447543aa43aacb8defbbf90682fcb2b63ce1b00b5Chris Banes return mDownPreScrollRange = Math.max(0, range - getTopInset()); 385a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 386a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 387a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes /** 388a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * Return the scroll range when scrolling down from a nested scroll. 389a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes */ 390ea004a01f44aaf36212120c936952c1742d03d30Chris Banes private int getDownNestedScrollRange() { 391a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes if (mDownScrollRange != INVALID_SCROLL_RANGE) { 392a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes // If we already have a valid value, return it 393a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes return mDownScrollRange; 394a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 395a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 396a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes int range = 0; 397c1ce4f6cf4d99d6856c4259bf1bb1cb56f604737Chris Banes for (int i = 0, z = getChildCount(); i < z; i++) { 398a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes final View child = getChildAt(i); 399a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 400ea004a01f44aaf36212120c936952c1742d03d30Chris Banes int childHeight = child.getMeasuredHeight(); 401b263b91ba75dc1a7855186e8b0e9cf75d46a6b8dChris Banes childHeight += lp.topMargin + lp.bottomMargin; 402a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 403a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes final int flags = lp.mScrollFlags; 404a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 405a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes if ((flags & LayoutParams.SCROLL_FLAG_SCROLL) != 0) { 406a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes // We're set to scroll so add the child's height 407a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes range += childHeight; 408a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 409a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes if ((flags & LayoutParams.SCROLL_FLAG_EXIT_UNTIL_COLLAPSED) != 0) { 410a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes // For a collapsing exit scroll, we to take the collapsed height into account. 4114832063daa6620f0ad51c4a16f31502dab1adc89Chris Banes // We also break the range straight away since later views can't scroll 412a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes // beneath us 4134832063daa6620f0ad51c4a16f31502dab1adc89Chris Banes range -= ViewCompat.getMinimumHeight(child) + getTopInset(); 4144832063daa6620f0ad51c4a16f31502dab1adc89Chris Banes break; 415a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 416a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } else { 417a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes // As soon as a view doesn't have the scroll flag, we end the range calculation. 418a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes // This is because views below can not scroll under a fixed view. 419a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes break; 420a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 421a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 4224c33be829067714342b629d29329206bc2116afeChris Banes return mDownScrollRange = Math.max(0, range); 423a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 424a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 42550dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes final int getMinimumHeightForVisibleOverlappingContent() { 42617ed3263761329f6aa6796941358c41001fff325Chris Banes final int topInset = getTopInset(); 42750dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes final int minHeight = ViewCompat.getMinimumHeight(this); 42850dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes if (minHeight != 0) { 42950dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes // If this layout has a min height, use it (doubled) 4306ba61c5c79fe025036593c9daf79cb65299bb0b6Chris Banes return (minHeight * 2) + topInset; 43150dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes } 43250dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes 43350dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes // Otherwise, we'll use twice the min height of our last child 43450dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes final int childCount = getChildCount(); 43550dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes return childCount >= 1 4366ba61c5c79fe025036593c9daf79cb65299bb0b6Chris Banes ? (ViewCompat.getMinimumHeight(getChildAt(childCount - 1)) * 2) + topInset 43750dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes : 0; 43850dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes } 43950dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes 44050dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes /** 441631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes * Set the elevation value to use when this {@link AppBarLayout} should be elevated 442631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes * above content. 443631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes * <p> 444631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes * This method does not do anything itself. A typical use for this method is called from within 445631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes * an {@link OnOffsetChangedListener} when the offset has changed in such a way to require an 446631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes * elevation change. 447631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes * 448631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes * @param elevation the elevation value to use. 449631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes * 450631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes * @see ViewCompat#setElevation(View, float) 45150dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes */ 452631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes public void setTargetElevation(float elevation) { 453631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes mTargetElevation = elevation; 454631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes } 455631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes 456631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes /** 457631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes * Returns the elevation value to use when this {@link AppBarLayout} should be elevated 458631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes * above content. 459631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes */ 460631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes public float getTargetElevation() { 46150dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes return mTargetElevation; 46250dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes } 46350dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes 464ea004a01f44aaf36212120c936952c1742d03d30Chris Banes private int getPendingAction() { 4659995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes return mPendingAction; 4669995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes } 4679995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes 468ea004a01f44aaf36212120c936952c1742d03d30Chris Banes private void resetPendingAction() { 4699995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes mPendingAction = PENDING_ACTION_NONE; 4709995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes } 4719995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes 472ea004a01f44aaf36212120c936952c1742d03d30Chris Banes private int getTopInset() { 473ea004a01f44aaf36212120c936952c1742d03d30Chris Banes return mLastInsets != null ? mLastInsets.getSystemWindowInsetTop() : 0; 474ea004a01f44aaf36212120c936952c1742d03d30Chris Banes } 475ea004a01f44aaf36212120c936952c1742d03d30Chris Banes 4768818e8e12682e5df1535cd9b2d792494bd3e059dChris Banes private WindowInsetsCompat onWindowInsetChanged(final WindowInsetsCompat insets) { 4778818e8e12682e5df1535cd9b2d792494bd3e059dChris Banes WindowInsetsCompat newInsets = null; 4788818e8e12682e5df1535cd9b2d792494bd3e059dChris Banes 4798818e8e12682e5df1535cd9b2d792494bd3e059dChris Banes if (ViewCompat.getFitsSystemWindows(this)) { 4808818e8e12682e5df1535cd9b2d792494bd3e059dChris Banes // If we're set to fit system windows, keep the insets 4818818e8e12682e5df1535cd9b2d792494bd3e059dChris Banes newInsets = insets; 4828818e8e12682e5df1535cd9b2d792494bd3e059dChris Banes } 4838818e8e12682e5df1535cd9b2d792494bd3e059dChris Banes 4848818e8e12682e5df1535cd9b2d792494bd3e059dChris Banes // If our insets have changed, keep them and invalidate the scroll ranges... 4858818e8e12682e5df1535cd9b2d792494bd3e059dChris Banes if (newInsets != mLastInsets) { 4868818e8e12682e5df1535cd9b2d792494bd3e059dChris Banes mLastInsets = newInsets; 4878818e8e12682e5df1535cd9b2d792494bd3e059dChris Banes invalidateScrollRanges(); 4888818e8e12682e5df1535cd9b2d792494bd3e059dChris Banes } 4898818e8e12682e5df1535cd9b2d792494bd3e059dChris Banes 49017ed3263761329f6aa6796941358c41001fff325Chris Banes return insets; 4916ba61c5c79fe025036593c9daf79cb65299bb0b6Chris Banes } 4926ba61c5c79fe025036593c9daf79cb65299bb0b6Chris Banes 493a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes public static class LayoutParams extends LinearLayout.LayoutParams { 49450dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes 49550dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes /** @hide */ 49650dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes @IntDef(flag=true, value={ 49750dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes SCROLL_FLAG_SCROLL, 49850dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes SCROLL_FLAG_EXIT_UNTIL_COLLAPSED, 49950dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes SCROLL_FLAG_ENTER_ALWAYS, 500bc22c4fd37e99643b3e7b87e2849a9e63fb8c5fcChris Banes SCROLL_FLAG_ENTER_ALWAYS_COLLAPSED, 501bc22c4fd37e99643b3e7b87e2849a9e63fb8c5fcChris Banes SCROLL_FLAG_SNAP 50250dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes }) 50350dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes @Retention(RetentionPolicy.SOURCE) 50450dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes public @interface ScrollFlags {} 50550dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes 506a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes /** 507a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * The view will be scroll in direct relation to scroll events. This flag needs to be 508a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * set for any of the other flags to take effect. If any sibling views 509a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * before this one do not have this flag, then this value has no effect. 510a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes */ 511a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes public static final int SCROLL_FLAG_SCROLL = 0x1; 512a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 513a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes /** 514a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * When exiting (scrolling off screen) the view will be scrolled until it is 515a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * 'collapsed'. The collapsed height is defined by the view's minimum height. 516a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * 517a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * @see ViewCompat#getMinimumHeight(View) 518a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * @see View#setMinimumHeight(int) 519a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes */ 520a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes public static final int SCROLL_FLAG_EXIT_UNTIL_COLLAPSED = 0x2; 521a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 522a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes /** 523a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * When entering (scrolling on screen) the view will scroll on any downwards 524a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * scroll event, regardless of whether the scrolling view is also scrolling. This 525a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * is commonly referred to as the 'quick return' pattern. 526a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes */ 527a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes public static final int SCROLL_FLAG_ENTER_ALWAYS = 0x4; 528a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 529a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes /** 530a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * An additional flag for 'enterAlways' which modifies the returning view to 531a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * only initially scroll back to it's collapsed height. Once the scrolling view has 532a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * reached the end of it's scroll range, the remainder of this view will be scrolled 533a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * into view. The collapsed height is defined by the view's minimum height. 534a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * 535a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * @see ViewCompat#getMinimumHeight(View) 536a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * @see View#setMinimumHeight(int) 537a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes */ 538a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes public static final int SCROLL_FLAG_ENTER_ALWAYS_COLLAPSED = 0x8; 539a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 540a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes /** 541bc22c4fd37e99643b3e7b87e2849a9e63fb8c5fcChris Banes * Upon a scroll ending, if the view is only partially visible then it will be snapped 542bc22c4fd37e99643b3e7b87e2849a9e63fb8c5fcChris Banes * and scrolled to it's closest edge. For example, if the view only has it's bottom 25% 543bc22c4fd37e99643b3e7b87e2849a9e63fb8c5fcChris Banes * displayed, it will be scrolled off screen completely. Conversely, if it's bottom 75% 544bc22c4fd37e99643b3e7b87e2849a9e63fb8c5fcChris Banes * is visible then it will be scrolled fully into view. 545bc22c4fd37e99643b3e7b87e2849a9e63fb8c5fcChris Banes */ 546bc22c4fd37e99643b3e7b87e2849a9e63fb8c5fcChris Banes public static final int SCROLL_FLAG_SNAP = 0x10; 547bc22c4fd37e99643b3e7b87e2849a9e63fb8c5fcChris Banes 548bc22c4fd37e99643b3e7b87e2849a9e63fb8c5fcChris Banes /** 549bc22c4fd37e99643b3e7b87e2849a9e63fb8c5fcChris Banes * Internal flags which allows quick checking features 550a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes */ 551a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes static final int FLAG_QUICK_RETURN = SCROLL_FLAG_SCROLL | SCROLL_FLAG_ENTER_ALWAYS; 552bc22c4fd37e99643b3e7b87e2849a9e63fb8c5fcChris Banes static final int FLAG_SNAP = SCROLL_FLAG_SCROLL | SCROLL_FLAG_SNAP; 553a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 554a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes int mScrollFlags = SCROLL_FLAG_SCROLL; 555a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes Interpolator mScrollInterpolator; 556a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 557a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes public LayoutParams(Context c, AttributeSet attrs) { 558a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes super(c, attrs); 559a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.AppBarLayout_LayoutParams); 560a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes mScrollFlags = a.getInt(R.styleable.AppBarLayout_LayoutParams_layout_scrollFlags, 0); 561a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes if (a.hasValue(R.styleable.AppBarLayout_LayoutParams_layout_scrollInterpolator)) { 562a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes int resId = a.getResourceId( 563a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes R.styleable.AppBarLayout_LayoutParams_layout_scrollInterpolator, 0); 564a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes mScrollInterpolator = android.view.animation.AnimationUtils.loadInterpolator( 565a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes c, resId); 566a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 567a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes a.recycle(); 568a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 569a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 570a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes public LayoutParams(int width, int height) { 571a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes super(width, height); 572a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 573a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 574a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes public LayoutParams(int width, int height, float weight) { 575a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes super(width, height, weight); 576a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 577a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 578a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes public LayoutParams(ViewGroup.LayoutParams p) { 579a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes super(p); 580a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 581a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 582a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes public LayoutParams(MarginLayoutParams source) { 583a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes super(source); 584a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 585a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 586a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes public LayoutParams(LinearLayout.LayoutParams source) { 587a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes super(source); 588a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 589a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 590a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes public LayoutParams(LayoutParams source) { 591a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes super(source); 592a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes mScrollFlags = source.mScrollFlags; 593a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes mScrollInterpolator = source.mScrollInterpolator; 594a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 595a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 596a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes /** 597a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * Set the scrolling flags. 598a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * 599a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * @param flags bitwise int of {@link #SCROLL_FLAG_SCROLL}, 600bc22c4fd37e99643b3e7b87e2849a9e63fb8c5fcChris Banes * {@link #SCROLL_FLAG_EXIT_UNTIL_COLLAPSED}, {@link #SCROLL_FLAG_ENTER_ALWAYS}, 601bc22c4fd37e99643b3e7b87e2849a9e63fb8c5fcChris Banes * {@link #SCROLL_FLAG_ENTER_ALWAYS_COLLAPSED} and {@link #SCROLL_FLAG_SNAP }. 602a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * 603a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * @see #getScrollFlags() 604a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * 605149689d3a0b4165577470d7152112674d1d7f87cChris Banes * @attr ref android.support.design.R.styleable#AppBarLayout_LayoutParams_layout_scrollFlags 606a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes */ 60750dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes public void setScrollFlags(@ScrollFlags int flags) { 608a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes mScrollFlags = flags; 609a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 610a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 611a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes /** 612a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * Returns the scrolling flags. 613a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * 614a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * @see #setScrollFlags(int) 615a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * 616149689d3a0b4165577470d7152112674d1d7f87cChris Banes * @attr ref android.support.design.R.styleable#AppBarLayout_LayoutParams_layout_scrollFlags 617a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes */ 61850dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes @ScrollFlags 619a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes public int getScrollFlags() { 620a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes return mScrollFlags; 621a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 622a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 623a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes /** 624a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * Set the interpolator to when scrolling the view associated with this 625a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * {@link LayoutParams}. 626a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * 627a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * @param interpolator the interpolator to use, or null to use normal 1-to-1 scrolling. 628a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * 629149689d3a0b4165577470d7152112674d1d7f87cChris Banes * @attr ref android.support.design.R.styleable#AppBarLayout_LayoutParams_layout_scrollInterpolator 630a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * @see #getScrollInterpolator() 631a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes */ 632a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes public void setScrollInterpolator(Interpolator interpolator) { 633a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes mScrollInterpolator = interpolator; 634a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 635a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 636a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes /** 637a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * Returns the {@link Interpolator} being used for scrolling the view associated with this 638a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * {@link LayoutParams}. Null indicates 'normal' 1-to-1 scrolling. 639a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * 640149689d3a0b4165577470d7152112674d1d7f87cChris Banes * @attr ref android.support.design.R.styleable#AppBarLayout_LayoutParams_layout_scrollInterpolator 641a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * @see #setScrollInterpolator(Interpolator) 642a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes */ 643a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes public Interpolator getScrollInterpolator() { 644a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes return mScrollInterpolator; 645a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 646a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 647a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 648a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes /** 649a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * The default {@link Behavior} for {@link AppBarLayout}. Implements the necessary nested 650a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * scroll handling with offsetting. 651a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes */ 652ec161ac1da9c8ca0e942b01e037ceb1cc51a2f3cMady Mellor public static class Behavior extends HeaderBehavior<AppBarLayout> { 653dcde7bf0c79a12f30d3853a82f4fbf54ce5161c8Chris Banes private static final int ANIMATE_OFFSET_DIPS_PER_SECOND = 300; 654e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banes private static final int INVALID_POSITION = -1; 655e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banes 65672a0913607198c5ce3fa351242ccbdfb3b93f178Chris Banes /** 65772a0913607198c5ce3fa351242ccbdfb3b93f178Chris Banes * Callback to allow control over any {@link AppBarLayout} dragging. 65872a0913607198c5ce3fa351242ccbdfb3b93f178Chris Banes */ 65972a0913607198c5ce3fa351242ccbdfb3b93f178Chris Banes public static abstract class DragCallback { 66072a0913607198c5ce3fa351242ccbdfb3b93f178Chris Banes /** 66172a0913607198c5ce3fa351242ccbdfb3b93f178Chris Banes * Allows control over whether the given {@link AppBarLayout} can be dragged or not. 66272a0913607198c5ce3fa351242ccbdfb3b93f178Chris Banes * 66372a0913607198c5ce3fa351242ccbdfb3b93f178Chris Banes * <p>Dragging is defined as a direct touch on the AppBarLayout with movement. This 66472a0913607198c5ce3fa351242ccbdfb3b93f178Chris Banes * call does not affect any nested scrolling.</p> 66572a0913607198c5ce3fa351242ccbdfb3b93f178Chris Banes * 66672a0913607198c5ce3fa351242ccbdfb3b93f178Chris Banes * @return true if we are in a position to scroll the AppBarLayout via a drag, false 66772a0913607198c5ce3fa351242ccbdfb3b93f178Chris Banes * if not. 66872a0913607198c5ce3fa351242ccbdfb3b93f178Chris Banes */ 66972a0913607198c5ce3fa351242ccbdfb3b93f178Chris Banes public abstract boolean canDrag(@NonNull AppBarLayout appBarLayout); 67072a0913607198c5ce3fa351242ccbdfb3b93f178Chris Banes } 67172a0913607198c5ce3fa351242ccbdfb3b93f178Chris Banes 672e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banes private int mOffsetDelta; 673a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 67450dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes private boolean mSkipNestedPreScroll; 67547543aa43aacb8defbbf90682fcb2b63ce1b00b5Chris Banes private boolean mWasNestedFlung; 676631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes 677631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes private ValueAnimatorCompat mAnimator; 67850dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes 679e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banes private int mOffsetToChildIndexOnLayout = INVALID_POSITION; 680e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banes private boolean mOffsetToChildIndexOnLayoutIsMinHeight; 681e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banes private float mOffsetToChildIndexOnLayoutPerc; 682e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banes 683d4c72f08279df548fbc4d556fe1a8b3500fafdbcChris Banes private WeakReference<View> mLastNestedScrollingChildRef; 68472a0913607198c5ce3fa351242ccbdfb3b93f178Chris Banes private DragCallback mOnDragCallback; 685d4c72f08279df548fbc4d556fe1a8b3500fafdbcChris Banes 686a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes public Behavior() {} 687a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 688a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes public Behavior(Context context, AttributeSet attrs) { 689a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes super(context, attrs); 690a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 691a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 692a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes @Override 693ab809dd75ef2f73b312038f2c10473cfa5885a58Chris Banes public boolean onStartNestedScroll(CoordinatorLayout parent, AppBarLayout child, 694a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes View directTargetChild, View target, int nestedScrollAxes) { 695f57cd2fd5059c43d77bdfec57edfd5a217533103Chris Banes // Return true if we're nested scrolling vertically, and we have scrollable children 696f57cd2fd5059c43d77bdfec57edfd5a217533103Chris Banes // and the scrolling view is big enough to scroll 697631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes final boolean started = (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0 698f57cd2fd5059c43d77bdfec57edfd5a217533103Chris Banes && child.hasScrollableChildren() 699ab809dd75ef2f73b312038f2c10473cfa5885a58Chris Banes && parent.getHeight() - directTargetChild.getHeight() <= child.getHeight(); 700631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes 701631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes if (started && mAnimator != null) { 702631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes // Cancel any offset animation 703631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes mAnimator.cancel(); 704631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes } 705631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes 706d4c72f08279df548fbc4d556fe1a8b3500fafdbcChris Banes // A new nested scroll has started so clear out the previous ref 707d4c72f08279df548fbc4d556fe1a8b3500fafdbcChris Banes mLastNestedScrollingChildRef = null; 708d4c72f08279df548fbc4d556fe1a8b3500fafdbcChris Banes 709631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes return started; 710a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 711a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 712a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes @Override 713a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child, 714a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes View target, int dx, int dy, int[] consumed) { 71550dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes if (dy != 0 && !mSkipNestedPreScroll) { 71650dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes int min, max; 71750dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes if (dy < 0) { 71850dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes // We're scrolling down 71950dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes min = -child.getTotalScrollRange(); 72050dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes max = min + child.getDownNestedPreScrollRange(); 72150dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes } else { 72250dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes // We're scrolling up 72350dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes min = -child.getUpNestedPreScrollRange(); 72450dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes max = 0; 72550dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes } 72650dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes consumed[1] = scroll(coordinatorLayout, child, dy, min, max); 727a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 728a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 729a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 730a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes @Override 731a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes public void onNestedScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child, 732a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes View target, int dxConsumed, int dyConsumed, 733a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes int dxUnconsumed, int dyUnconsumed) { 73450dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes if (dyUnconsumed < 0) { 73550dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes // If the scrolling view is scrolling down but not consuming, it's probably be at 73650dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes // the top of it's content 73750dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes scroll(coordinatorLayout, child, dyUnconsumed, 73850dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes -child.getDownNestedScrollRange(), 0); 73950dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes // Set the expanding flag so that onNestedPreScroll doesn't handle any events 74050dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes mSkipNestedPreScroll = true; 74150dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes } else { 74250dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes // As we're no longer handling nested scrolls, reset the skip flag 74350dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes mSkipNestedPreScroll = false; 744a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 74550dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes } 746a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 74750dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes @Override 748bc22c4fd37e99643b3e7b87e2849a9e63fb8c5fcChris Banes public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, AppBarLayout abl, 74950dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes View target) { 75047543aa43aacb8defbbf90682fcb2b63ce1b00b5Chris Banes if (!mWasNestedFlung) { 751bc22c4fd37e99643b3e7b87e2849a9e63fb8c5fcChris Banes // If we haven't been flung then let's see if the current view has been set to snap 752bc22c4fd37e99643b3e7b87e2849a9e63fb8c5fcChris Banes snapToChildIfNeeded(coordinatorLayout, abl); 753bc22c4fd37e99643b3e7b87e2849a9e63fb8c5fcChris Banes } 754bc22c4fd37e99643b3e7b87e2849a9e63fb8c5fcChris Banes 755bc22c4fd37e99643b3e7b87e2849a9e63fb8c5fcChris Banes // Reset the flags 75650dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes mSkipNestedPreScroll = false; 75747543aa43aacb8defbbf90682fcb2b63ce1b00b5Chris Banes mWasNestedFlung = false; 758d4c72f08279df548fbc4d556fe1a8b3500fafdbcChris Banes // Keep a reference to the previous nested scrolling child 759d4c72f08279df548fbc4d556fe1a8b3500fafdbcChris Banes mLastNestedScrollingChildRef = new WeakReference<>(target); 760d4c72f08279df548fbc4d556fe1a8b3500fafdbcChris Banes } 761d4c72f08279df548fbc4d556fe1a8b3500fafdbcChris Banes 762d4c72f08279df548fbc4d556fe1a8b3500fafdbcChris Banes @Override 763631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes public boolean onNestedFling(final CoordinatorLayout coordinatorLayout, 764631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes final AppBarLayout child, View target, float velocityX, float velocityY, 765631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes boolean consumed) { 766bc22c4fd37e99643b3e7b87e2849a9e63fb8c5fcChris Banes boolean flung = false; 767bc22c4fd37e99643b3e7b87e2849a9e63fb8c5fcChris Banes 768631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes if (!consumed) { 769631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes // It has been consumed so let's fling ourselves 770bc22c4fd37e99643b3e7b87e2849a9e63fb8c5fcChris Banes flung = fling(coordinatorLayout, child, -child.getTotalScrollRange(), 771bc22c4fd37e99643b3e7b87e2849a9e63fb8c5fcChris Banes 0, -velocityY); 772631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes } else { 773631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes // If we're scrolling up and the child also consumed the fling. We'll fake scroll 774631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes // upto our 'collapsed' offset 775631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes if (velocityY < 0) { 776631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes // We're scrolling down 777bc22c4fd37e99643b3e7b87e2849a9e63fb8c5fcChris Banes final int targetScroll = -child.getTotalScrollRange() 778631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes + child.getDownNestedPreScrollRange(); 779bc22c4fd37e99643b3e7b87e2849a9e63fb8c5fcChris Banes if (getTopBottomOffsetForScrollingSibling() < targetScroll) { 780bc22c4fd37e99643b3e7b87e2849a9e63fb8c5fcChris Banes // If we're currently not expanded more than the target scroll, we'll 781bc22c4fd37e99643b3e7b87e2849a9e63fb8c5fcChris Banes // animate a fling 782bc22c4fd37e99643b3e7b87e2849a9e63fb8c5fcChris Banes animateOffsetTo(coordinatorLayout, child, targetScroll); 783bc22c4fd37e99643b3e7b87e2849a9e63fb8c5fcChris Banes flung = true; 784631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes } 785631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes } else { 786631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes // We're scrolling up 787bc22c4fd37e99643b3e7b87e2849a9e63fb8c5fcChris Banes final int targetScroll = -child.getUpNestedPreScrollRange(); 788bc22c4fd37e99643b3e7b87e2849a9e63fb8c5fcChris Banes if (getTopBottomOffsetForScrollingSibling() > targetScroll) { 789bc22c4fd37e99643b3e7b87e2849a9e63fb8c5fcChris Banes // If we're currently not expanded less than the target scroll, we'll 790bc22c4fd37e99643b3e7b87e2849a9e63fb8c5fcChris Banes // animate a fling 791bc22c4fd37e99643b3e7b87e2849a9e63fb8c5fcChris Banes animateOffsetTo(coordinatorLayout, child, targetScroll); 792bc22c4fd37e99643b3e7b87e2849a9e63fb8c5fcChris Banes flung = true; 793631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes } 794631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes } 795631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes } 796631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes 79747543aa43aacb8defbbf90682fcb2b63ce1b00b5Chris Banes mWasNestedFlung = flung; 798bc22c4fd37e99643b3e7b87e2849a9e63fb8c5fcChris Banes return flung; 799631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes } 800631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes 80172a0913607198c5ce3fa351242ccbdfb3b93f178Chris Banes /** 80272a0913607198c5ce3fa351242ccbdfb3b93f178Chris Banes * Set a callback to control any {@link AppBarLayout} dragging. 80372a0913607198c5ce3fa351242ccbdfb3b93f178Chris Banes * 80472a0913607198c5ce3fa351242ccbdfb3b93f178Chris Banes * @param callback the callback to use, or {@code null} to use the default behavior. 80572a0913607198c5ce3fa351242ccbdfb3b93f178Chris Banes */ 80672a0913607198c5ce3fa351242ccbdfb3b93f178Chris Banes public void setDragCallback(@Nullable DragCallback callback) { 80772a0913607198c5ce3fa351242ccbdfb3b93f178Chris Banes mOnDragCallback = callback; 80872a0913607198c5ce3fa351242ccbdfb3b93f178Chris Banes } 80972a0913607198c5ce3fa351242ccbdfb3b93f178Chris Banes 810631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes private void animateOffsetTo(final CoordinatorLayout coordinatorLayout, 811dcde7bf0c79a12f30d3853a82f4fbf54ce5161c8Chris Banes final AppBarLayout child, final int offset) { 812dcde7bf0c79a12f30d3853a82f4fbf54ce5161c8Chris Banes final int currentOffset = getTopBottomOffsetForScrollingSibling(); 813dcde7bf0c79a12f30d3853a82f4fbf54ce5161c8Chris Banes if (currentOffset == offset) { 814dcde7bf0c79a12f30d3853a82f4fbf54ce5161c8Chris Banes if (mAnimator != null && mAnimator.isRunning()) { 815dcde7bf0c79a12f30d3853a82f4fbf54ce5161c8Chris Banes mAnimator.cancel(); 816dcde7bf0c79a12f30d3853a82f4fbf54ce5161c8Chris Banes } 817dcde7bf0c79a12f30d3853a82f4fbf54ce5161c8Chris Banes return; 818dcde7bf0c79a12f30d3853a82f4fbf54ce5161c8Chris Banes } 819dcde7bf0c79a12f30d3853a82f4fbf54ce5161c8Chris Banes 820631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes if (mAnimator == null) { 821631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes mAnimator = ViewUtils.createAnimator(); 822631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes mAnimator.setInterpolator(AnimationUtils.DECELERATE_INTERPOLATOR); 823631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes mAnimator.setUpdateListener(new ValueAnimatorCompat.AnimatorUpdateListener() { 824631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes @Override 825631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes public void onAnimationUpdate(ValueAnimatorCompat animator) { 826ec161ac1da9c8ca0e942b01e037ceb1cc51a2f3cMady Mellor setHeaderTopBottomOffset(coordinatorLayout, child, 827631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes animator.getAnimatedIntValue()); 828631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes } 829631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes }); 830631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes } else { 831631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes mAnimator.cancel(); 832631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes } 833631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes 834dcde7bf0c79a12f30d3853a82f4fbf54ce5161c8Chris Banes // Set the duration based on the amount of dips we're travelling in 835dcde7bf0c79a12f30d3853a82f4fbf54ce5161c8Chris Banes final float distanceDp = Math.abs(currentOffset - offset) / 836dcde7bf0c79a12f30d3853a82f4fbf54ce5161c8Chris Banes coordinatorLayout.getResources().getDisplayMetrics().density; 837dcde7bf0c79a12f30d3853a82f4fbf54ce5161c8Chris Banes mAnimator.setDuration(Math.round(distanceDp * 1000 / ANIMATE_OFFSET_DIPS_PER_SECOND)); 838dcde7bf0c79a12f30d3853a82f4fbf54ce5161c8Chris Banes 839dcde7bf0c79a12f30d3853a82f4fbf54ce5161c8Chris Banes mAnimator.setIntValues(currentOffset, offset); 840631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes mAnimator.start(); 841631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes } 842631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes 843bc22c4fd37e99643b3e7b87e2849a9e63fb8c5fcChris Banes private View getChildOnOffset(AppBarLayout abl, final int offset) { 844bc22c4fd37e99643b3e7b87e2849a9e63fb8c5fcChris Banes for (int i = 0, count = abl.getChildCount(); i < count; i++) { 845bc22c4fd37e99643b3e7b87e2849a9e63fb8c5fcChris Banes View child = abl.getChildAt(i); 846bc22c4fd37e99643b3e7b87e2849a9e63fb8c5fcChris Banes if (child.getTop() <= -offset && child.getBottom() >= -offset) { 847bc22c4fd37e99643b3e7b87e2849a9e63fb8c5fcChris Banes return child; 848bc22c4fd37e99643b3e7b87e2849a9e63fb8c5fcChris Banes } 849bc22c4fd37e99643b3e7b87e2849a9e63fb8c5fcChris Banes } 850bc22c4fd37e99643b3e7b87e2849a9e63fb8c5fcChris Banes return null; 851bc22c4fd37e99643b3e7b87e2849a9e63fb8c5fcChris Banes } 852bc22c4fd37e99643b3e7b87e2849a9e63fb8c5fcChris Banes 853bc22c4fd37e99643b3e7b87e2849a9e63fb8c5fcChris Banes private void snapToChildIfNeeded(CoordinatorLayout coordinatorLayout, AppBarLayout abl) { 854bc22c4fd37e99643b3e7b87e2849a9e63fb8c5fcChris Banes final int offset = getTopBottomOffsetForScrollingSibling(); 855bc22c4fd37e99643b3e7b87e2849a9e63fb8c5fcChris Banes final View offsetChild = getChildOnOffset(abl, offset); 856bc22c4fd37e99643b3e7b87e2849a9e63fb8c5fcChris Banes if (offsetChild != null) { 857bc22c4fd37e99643b3e7b87e2849a9e63fb8c5fcChris Banes final LayoutParams lp = (LayoutParams) offsetChild.getLayoutParams(); 858bc22c4fd37e99643b3e7b87e2849a9e63fb8c5fcChris Banes if ((lp.getScrollFlags() & LayoutParams.FLAG_SNAP) == LayoutParams.FLAG_SNAP) { 859bc22c4fd37e99643b3e7b87e2849a9e63fb8c5fcChris Banes // We're set the snap, so animate the offset to the nearest edge 860dcde7bf0c79a12f30d3853a82f4fbf54ce5161c8Chris Banes int childTop = -offsetChild.getTop(); 861dcde7bf0c79a12f30d3853a82f4fbf54ce5161c8Chris Banes int childBottom = -offsetChild.getBottom(); 862dcde7bf0c79a12f30d3853a82f4fbf54ce5161c8Chris Banes 863dcde7bf0c79a12f30d3853a82f4fbf54ce5161c8Chris Banes // If the view is set only exit until it is collapsed, we'll abide by that 864dcde7bf0c79a12f30d3853a82f4fbf54ce5161c8Chris Banes if ((lp.getScrollFlags() & LayoutParams.SCROLL_FLAG_EXIT_UNTIL_COLLAPSED) 865dcde7bf0c79a12f30d3853a82f4fbf54ce5161c8Chris Banes == LayoutParams.SCROLL_FLAG_EXIT_UNTIL_COLLAPSED) { 866dcde7bf0c79a12f30d3853a82f4fbf54ce5161c8Chris Banes childBottom += ViewCompat.getMinimumHeight(offsetChild); 867dcde7bf0c79a12f30d3853a82f4fbf54ce5161c8Chris Banes } 868dcde7bf0c79a12f30d3853a82f4fbf54ce5161c8Chris Banes 869dcde7bf0c79a12f30d3853a82f4fbf54ce5161c8Chris Banes final int newOffset = offset < (childBottom + childTop) / 2 870dcde7bf0c79a12f30d3853a82f4fbf54ce5161c8Chris Banes ? childBottom : childTop; 871bc22c4fd37e99643b3e7b87e2849a9e63fb8c5fcChris Banes animateOffsetTo(coordinatorLayout, abl, 872dcde7bf0c79a12f30d3853a82f4fbf54ce5161c8Chris Banes MathUtils.constrain(newOffset, -abl.getTotalScrollRange(), 0)); 873bc22c4fd37e99643b3e7b87e2849a9e63fb8c5fcChris Banes } 874bc22c4fd37e99643b3e7b87e2849a9e63fb8c5fcChris Banes } 875bc22c4fd37e99643b3e7b87e2849a9e63fb8c5fcChris Banes } 876bc22c4fd37e99643b3e7b87e2849a9e63fb8c5fcChris Banes 877631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes @Override 8789995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes public boolean onLayoutChild(CoordinatorLayout parent, AppBarLayout abl, 87950dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes int layoutDirection) { 8809995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes boolean handled = super.onLayoutChild(parent, abl, layoutDirection); 8819995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes 8829995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes final int pendingAction = abl.getPendingAction(); 8839995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes if (pendingAction != PENDING_ACTION_NONE) { 8849995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes final boolean animate = (pendingAction & PENDING_ACTION_ANIMATE_ENABLED) != 0; 8859995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes if ((pendingAction & PENDING_ACTION_COLLAPSED) != 0) { 8869995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes final int offset = -abl.getUpNestedPreScrollRange(); 8879995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes if (animate) { 8889995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes animateOffsetTo(parent, abl, offset); 8899995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes } else { 890ec161ac1da9c8ca0e942b01e037ceb1cc51a2f3cMady Mellor setHeaderTopBottomOffset(parent, abl, offset); 8919995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes } 8929995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes } else if ((pendingAction & PENDING_ACTION_EXPANDED) != 0) { 8939995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes if (animate) { 8949995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes animateOffsetTo(parent, abl, 0); 8959995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes } else { 896ec161ac1da9c8ca0e942b01e037ceb1cc51a2f3cMady Mellor setHeaderTopBottomOffset(parent, abl, 0); 8979995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes } 8989995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes } 8999995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes } else if (mOffsetToChildIndexOnLayout >= 0) { 9009995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes View child = abl.getChildAt(mOffsetToChildIndexOnLayout); 901e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banes int offset = -child.getBottom(); 902e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banes if (mOffsetToChildIndexOnLayoutIsMinHeight) { 903e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banes offset += ViewCompat.getMinimumHeight(child); 904e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banes } else { 905e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banes offset += Math.round(child.getHeight() * mOffsetToChildIndexOnLayoutPerc); 906e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banes } 907e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banes setTopAndBottomOffset(offset); 908e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banes } 90950dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes 91079aa418487cdefb85d3c1fac32d6403a1ac6057dChris Banes // Finally reset any pending states 91179aa418487cdefb85d3c1fac32d6403a1ac6057dChris Banes abl.resetPendingAction(); 91279aa418487cdefb85d3c1fac32d6403a1ac6057dChris Banes mOffsetToChildIndexOnLayout = INVALID_POSITION; 91379aa418487cdefb85d3c1fac32d6403a1ac6057dChris Banes 91447543aa43aacb8defbbf90682fcb2b63ce1b00b5Chris Banes // We may have changed size, so let's constrain the top and bottom offset correctly, 91547543aa43aacb8defbbf90682fcb2b63ce1b00b5Chris Banes // just in case we're out of the bounds 91647543aa43aacb8defbbf90682fcb2b63ce1b00b5Chris Banes setTopAndBottomOffset( 91747543aa43aacb8defbbf90682fcb2b63ce1b00b5Chris Banes MathUtils.constrain(getTopAndBottomOffset(), -abl.getTotalScrollRange(), 0)); 91847543aa43aacb8defbbf90682fcb2b63ce1b00b5Chris Banes 91950dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes // Make sure we update the elevation 9209995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes dispatchOffsetUpdates(abl); 92150dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes 92250dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes return handled; 923a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 924a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 92572a0913607198c5ce3fa351242ccbdfb3b93f178Chris Banes @Override 92672a0913607198c5ce3fa351242ccbdfb3b93f178Chris Banes boolean canDragView(AppBarLayout view) { 92772a0913607198c5ce3fa351242ccbdfb3b93f178Chris Banes if (mOnDragCallback != null) { 92872a0913607198c5ce3fa351242ccbdfb3b93f178Chris Banes // If there is a drag callback set, it's in control 92972a0913607198c5ce3fa351242ccbdfb3b93f178Chris Banes return mOnDragCallback.canDrag(view); 93072a0913607198c5ce3fa351242ccbdfb3b93f178Chris Banes } 93172a0913607198c5ce3fa351242ccbdfb3b93f178Chris Banes 93272a0913607198c5ce3fa351242ccbdfb3b93f178Chris Banes // Else we'll use the default behaviour of seeing if it can scroll down 933d4c72f08279df548fbc4d556fe1a8b3500fafdbcChris Banes if (mLastNestedScrollingChildRef != null) { 93472a0913607198c5ce3fa351242ccbdfb3b93f178Chris Banes // If we have a reference to a scrolling view, check it 93572a0913607198c5ce3fa351242ccbdfb3b93f178Chris Banes final View scrollingView = mLastNestedScrollingChildRef.get(); 93672a0913607198c5ce3fa351242ccbdfb3b93f178Chris Banes return scrollingView != null && scrollingView.isShown() 93772a0913607198c5ce3fa351242ccbdfb3b93f178Chris Banes && !ViewCompat.canScrollVertically(scrollingView, -1); 93872a0913607198c5ce3fa351242ccbdfb3b93f178Chris Banes } else { 93972a0913607198c5ce3fa351242ccbdfb3b93f178Chris Banes // Otherwise we assume that the scrolling view hasn't been scrolled and can drag. 94072a0913607198c5ce3fa351242ccbdfb3b93f178Chris Banes return true; 941d4c72f08279df548fbc4d556fe1a8b3500fafdbcChris Banes } 942d4c72f08279df548fbc4d556fe1a8b3500fafdbcChris Banes } 943d4c72f08279df548fbc4d556fe1a8b3500fafdbcChris Banes 944ec161ac1da9c8ca0e942b01e037ceb1cc51a2f3cMady Mellor @Override 94547543aa43aacb8defbbf90682fcb2b63ce1b00b5Chris Banes void onFlingFinished(CoordinatorLayout parent, AppBarLayout layout) { 94647543aa43aacb8defbbf90682fcb2b63ce1b00b5Chris Banes // At the end of a manual fling, check to see if we need to snap to the edge-child 94747543aa43aacb8defbbf90682fcb2b63ce1b00b5Chris Banes snapToChildIfNeeded(parent, layout); 94847543aa43aacb8defbbf90682fcb2b63ce1b00b5Chris Banes } 94947543aa43aacb8defbbf90682fcb2b63ce1b00b5Chris Banes 95047543aa43aacb8defbbf90682fcb2b63ce1b00b5Chris Banes @Override 95172a0913607198c5ce3fa351242ccbdfb3b93f178Chris Banes int getMaxDragOffset(AppBarLayout view) { 95272a0913607198c5ce3fa351242ccbdfb3b93f178Chris Banes return -view.getDownNestedScrollRange(); 95372a0913607198c5ce3fa351242ccbdfb3b93f178Chris Banes } 95472a0913607198c5ce3fa351242ccbdfb3b93f178Chris Banes 95572a0913607198c5ce3fa351242ccbdfb3b93f178Chris Banes @Override 95672a0913607198c5ce3fa351242ccbdfb3b93f178Chris Banes int getScrollRangeForDragFling(AppBarLayout view) { 95772a0913607198c5ce3fa351242ccbdfb3b93f178Chris Banes return view.getTotalScrollRange(); 95872a0913607198c5ce3fa351242ccbdfb3b93f178Chris Banes } 95972a0913607198c5ce3fa351242ccbdfb3b93f178Chris Banes 96072a0913607198c5ce3fa351242ccbdfb3b93f178Chris Banes @Override 96172a0913607198c5ce3fa351242ccbdfb3b93f178Chris Banes int setHeaderTopBottomOffset(CoordinatorLayout coordinatorLayout, 96272a0913607198c5ce3fa351242ccbdfb3b93f178Chris Banes AppBarLayout header, int newOffset, int minOffset, int maxOffset) { 963e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banes final int curOffset = getTopBottomOffsetForScrollingSibling(); 964a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes int consumed = 0; 965a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 9665e7673e0dbd89512b525d1bde5c912eb07885550Chris Banes if (minOffset != 0 && curOffset >= minOffset && curOffset <= maxOffset) { 967631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes // If we have some scrolling range, and we're currently within the min and max 968631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes // offsets, calculate a new offset 96950dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes newOffset = MathUtils.constrain(newOffset, minOffset, maxOffset); 970ec161ac1da9c8ca0e942b01e037ceb1cc51a2f3cMady Mellor AppBarLayout appBarLayout = (AppBarLayout) header; 971a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes if (curOffset != newOffset) { 972e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banes final int interpolatedOffset = appBarLayout.hasChildWithInterpolator() 973e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banes ? interpolateOffset(appBarLayout, newOffset) 974e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banes : newOffset; 975e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banes 976e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banes boolean offsetChanged = setTopAndBottomOffset(interpolatedOffset); 977631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes 978a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes // Update how much dy we have consumed 979a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes consumed = curOffset - newOffset; 980a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes // Update the stored sibling offset 981e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banes mOffsetDelta = newOffset - interpolatedOffset; 982a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 983a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes if (!offsetChanged && appBarLayout.hasChildWithInterpolator()) { 984a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes // If the offset hasn't changed and we're using an interpolated scroll 985a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes // then we need to keep any dependent views updated. CoL will do this for 986a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes // us when we move, but we need to do it manually when we don't (as an 987a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes // interpolated scroll may finish early). 988a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes coordinatorLayout.dispatchDependentViewsChanged(appBarLayout); 989a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 990a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 991631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes // Dispatch the updates to any listeners 992631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes dispatchOffsetUpdates(appBarLayout); 993a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 9945e7673e0dbd89512b525d1bde5c912eb07885550Chris Banes } else { 9955e7673e0dbd89512b525d1bde5c912eb07885550Chris Banes // Reset the offset delta 9965e7673e0dbd89512b525d1bde5c912eb07885550Chris Banes mOffsetDelta = 0; 997a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 998a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 999a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes return consumed; 1000a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 1001a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 1002631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes private void dispatchOffsetUpdates(AppBarLayout layout) { 10036f730c0acfb10a929172ea2981a1aded0e39f5c7Chris Banes final List<OnOffsetChangedListener> listeners = layout.mListeners; 100450dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes 1005631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes // Iterate backwards through the list so that most recently added listeners 1006631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes // get the first chance to decide 1007631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes for (int i = 0, z = listeners.size(); i < z; i++) { 10086f730c0acfb10a929172ea2981a1aded0e39f5c7Chris Banes final OnOffsetChangedListener listener = listeners.get(i); 1009631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes if (listener != null) { 1010631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes listener.onOffsetChanged(layout, getTopAndBottomOffset()); 1011a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 1012a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 1013a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 1014a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 1015a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes private int interpolateOffset(AppBarLayout layout, final int offset) { 1016a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes final int absOffset = Math.abs(offset); 1017a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 1018a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes for (int i = 0, z = layout.getChildCount(); i < z; i++) { 1019a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes final View child = layout.getChildAt(i); 1020a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes final AppBarLayout.LayoutParams childLp = (LayoutParams) child.getLayoutParams(); 102150dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes final Interpolator interpolator = childLp.getScrollInterpolator(); 1022a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 102350dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes if (absOffset >= child.getTop() && absOffset <= child.getBottom()) { 1024a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes if (interpolator != null) { 102550dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes int childScrollableHeight = 0; 102650dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes final int flags = childLp.getScrollFlags(); 102750dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes if ((flags & LayoutParams.SCROLL_FLAG_SCROLL) != 0) { 1028b263b91ba75dc1a7855186e8b0e9cf75d46a6b8dChris Banes // We're set to scroll so add the child's height plus margin 1029b263b91ba75dc1a7855186e8b0e9cf75d46a6b8dChris Banes childScrollableHeight += child.getHeight() + childLp.topMargin 1030b263b91ba75dc1a7855186e8b0e9cf75d46a6b8dChris Banes + childLp.bottomMargin; 1031b263b91ba75dc1a7855186e8b0e9cf75d46a6b8dChris Banes 103250dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes if ((flags & LayoutParams.SCROLL_FLAG_EXIT_UNTIL_COLLAPSED) != 0) { 1033b263b91ba75dc1a7855186e8b0e9cf75d46a6b8dChris Banes // For a collapsing scroll, we to take the collapsed height 1034b263b91ba75dc1a7855186e8b0e9cf75d46a6b8dChris Banes // into account. 103550dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes childScrollableHeight -= ViewCompat.getMinimumHeight(child); 103650dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes } 103750dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes } 103850dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes 1039ea004a01f44aaf36212120c936952c1742d03d30Chris Banes if (ViewCompat.getFitsSystemWindows(child)) { 1040ea004a01f44aaf36212120c936952c1742d03d30Chris Banes childScrollableHeight -= layout.getTopInset(); 1041ea004a01f44aaf36212120c936952c1742d03d30Chris Banes } 1042ea004a01f44aaf36212120c936952c1742d03d30Chris Banes 104350dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes if (childScrollableHeight > 0) { 104450dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes final int offsetForView = absOffset - child.getTop(); 104550dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes final int interpolatedDiff = Math.round(childScrollableHeight * 104650dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes interpolator.getInterpolation( 104750dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes offsetForView / (float) childScrollableHeight)); 104850dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes 104950dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes return Integer.signum(offset) * (child.getTop() + interpolatedDiff); 105050dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes } 1051a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 105250dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes 105350dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes // If we get to here then the view on the offset isn't suitable for interpolated 105450dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes // scrolling. So break out of the loop 105550dfc33a565c4aefe6d5e844c93aa24a74cb80b3Chris Banes break; 1056a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 1057a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 1058a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 1059a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes return offset; 1060a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 1061a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 1062ec161ac1da9c8ca0e942b01e037ceb1cc51a2f3cMady Mellor @Override 106372a0913607198c5ce3fa351242ccbdfb3b93f178Chris Banes int getTopBottomOffsetForScrollingSibling() { 1064e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banes return getTopAndBottomOffset() + mOffsetDelta; 1065e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banes } 1066e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banes 1067e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banes @Override 1068e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banes public Parcelable onSaveInstanceState(CoordinatorLayout parent, AppBarLayout appBarLayout) { 1069e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banes final Parcelable superState = super.onSaveInstanceState(parent, appBarLayout); 1070e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banes final int offset = getTopAndBottomOffset(); 1071e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banes 1072e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banes // Try and find the first visible child... 1073e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banes for (int i = 0, count = appBarLayout.getChildCount(); i < count; i++) { 1074e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banes View child = appBarLayout.getChildAt(i); 1075e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banes final int visBottom = child.getBottom() + offset; 1076e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banes 1077e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banes if (child.getTop() + offset <= 0 && visBottom >= 0) { 1078e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banes final SavedState ss = new SavedState(superState); 1079e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banes ss.firstVisibleChildIndex = i; 1080e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banes ss.firstVisibileChildAtMinimumHeight = 1081e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banes visBottom == ViewCompat.getMinimumHeight(child); 1082e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banes ss.firstVisibileChildPercentageShown = visBottom / (float) child.getHeight(); 1083e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banes return ss; 1084e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banes } 1085e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banes } 1086e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banes 1087e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banes // Else we'll just return the super state 1088e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banes return superState; 1089e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banes } 1090e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banes 1091e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banes @Override 1092e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banes public void onRestoreInstanceState(CoordinatorLayout parent, AppBarLayout appBarLayout, 1093e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banes Parcelable state) { 1094e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banes if (state instanceof SavedState) { 1095e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banes final SavedState ss = (SavedState) state; 1096e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banes super.onRestoreInstanceState(parent, appBarLayout, ss.getSuperState()); 1097e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banes mOffsetToChildIndexOnLayout = ss.firstVisibleChildIndex; 1098e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banes mOffsetToChildIndexOnLayoutPerc = ss.firstVisibileChildPercentageShown; 1099e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banes mOffsetToChildIndexOnLayoutIsMinHeight = ss.firstVisibileChildAtMinimumHeight; 1100e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banes } else { 1101e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banes super.onRestoreInstanceState(parent, appBarLayout, state); 1102e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banes mOffsetToChildIndexOnLayout = INVALID_POSITION; 1103e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banes } 1104e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banes } 1105e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banes 110662ab25d71ccfa775a8bd2add7b41277bc4e14bbcChris Banes protected static class SavedState extends BaseSavedState { 1107e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banes int firstVisibleChildIndex; 1108e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banes float firstVisibileChildPercentageShown; 1109e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banes boolean firstVisibileChildAtMinimumHeight; 1110e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banes 111162ab25d71ccfa775a8bd2add7b41277bc4e14bbcChris Banes public SavedState(Parcel source, ClassLoader loader) { 1112e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banes super(source); 1113e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banes firstVisibleChildIndex = source.readInt(); 1114e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banes firstVisibileChildPercentageShown = source.readFloat(); 1115e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banes firstVisibileChildAtMinimumHeight = source.readByte() != 0; 1116e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banes } 1117e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banes 1118e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banes public SavedState(Parcelable superState) { 1119e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banes super(superState); 1120e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banes } 1121e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banes 1122e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banes @Override 1123e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banes public void writeToParcel(Parcel dest, int flags) { 1124e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banes super.writeToParcel(dest, flags); 1125e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banes dest.writeInt(firstVisibleChildIndex); 1126e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banes dest.writeFloat(firstVisibileChildPercentageShown); 1127e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banes dest.writeByte((byte) (firstVisibileChildAtMinimumHeight ? 1 : 0)); 1128e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banes } 1129e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banes 1130e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banes public static final Parcelable.Creator<SavedState> CREATOR = 113162ab25d71ccfa775a8bd2add7b41277bc4e14bbcChris Banes ParcelableCompat.newCreator(new ParcelableCompatCreatorCallbacks<SavedState>() { 1132e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banes @Override 113362ab25d71ccfa775a8bd2add7b41277bc4e14bbcChris Banes public SavedState createFromParcel(Parcel source, ClassLoader loader) { 113462ab25d71ccfa775a8bd2add7b41277bc4e14bbcChris Banes return new SavedState(source, loader); 1135e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banes } 1136e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banes 1137e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banes @Override 1138e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banes public SavedState[] newArray(int size) { 1139e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banes return new SavedState[size]; 1140e52e16988e2500e20052ddd70920f950f4eb4f79Chris Banes } 114162ab25d71ccfa775a8bd2add7b41277bc4e14bbcChris Banes }); 1142a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 1143a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 1144a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 1145a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes /** 1146a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * Behavior which should be used by {@link View}s which can scroll vertically and support 1147a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes * nested scrolling to automatically scroll any {@link AppBarLayout} siblings. 1148a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes */ 1149ec161ac1da9c8ca0e942b01e037ceb1cc51a2f3cMady Mellor public static class ScrollingViewBehavior extends HeaderScrollingViewBehavior { 1150a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 1151a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes public ScrollingViewBehavior() {} 1152a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 1153a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes public ScrollingViewBehavior(Context context, AttributeSet attrs) { 1154a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes super(context, attrs); 1155a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 1156b2f568c9c71763ed823b1f9c274077825b5d0c9eChris Banes final TypedArray a = context.obtainStyledAttributes(attrs, 1157a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes R.styleable.ScrollingViewBehavior_Params); 1158b2f568c9c71763ed823b1f9c274077825b5d0c9eChris Banes setOverlayTop(a.getDimensionPixelSize( 1159b2f568c9c71763ed823b1f9c274077825b5d0c9eChris Banes R.styleable.ScrollingViewBehavior_Params_behavior_overlapTop, 0)); 1160a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes a.recycle(); 1161a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 1162a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 1163a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes @Override 1164a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) { 1165a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes // We depend on any AppBarLayouts 1166a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes return dependency instanceof AppBarLayout; 1167a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 1168a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 1169a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes @Override 1170a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes public boolean onDependentViewChanged(CoordinatorLayout parent, View child, 1171a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes View dependency) { 11725e7673e0dbd89512b525d1bde5c912eb07885550Chris Banes offsetChildAsNeeded(parent, child, dependency); 11730d2f46331a9fc3b21597e926ba2e061af7c00708Chris Banes return false; 11740d2f46331a9fc3b21597e926ba2e061af7c00708Chris Banes } 11750d2f46331a9fc3b21597e926ba2e061af7c00708Chris Banes 11765e7673e0dbd89512b525d1bde5c912eb07885550Chris Banes private void offsetChildAsNeeded(CoordinatorLayout parent, View child, View dependency) { 1177a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes final CoordinatorLayout.Behavior behavior = 1178a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes ((CoordinatorLayout.LayoutParams) dependency.getLayoutParams()).getBehavior(); 1179a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes if (behavior instanceof Behavior) { 11805e7673e0dbd89512b525d1bde5c912eb07885550Chris Banes // Offset the child, pinning it to the bottom the header-dependency, maintaining 11815e7673e0dbd89512b525d1bde5c912eb07885550Chris Banes // any vertical gap, and overlap 11825e7673e0dbd89512b525d1bde5c912eb07885550Chris Banes final Behavior ablBehavior = (Behavior) behavior; 11835e7673e0dbd89512b525d1bde5c912eb07885550Chris Banes final int offset = ablBehavior.getTopBottomOffsetForScrollingSibling(); 11845e7673e0dbd89512b525d1bde5c912eb07885550Chris Banes child.offsetTopAndBottom((dependency.getBottom() - child.getTop()) 11855e7673e0dbd89512b525d1bde5c912eb07885550Chris Banes + ablBehavior.mOffsetDelta 11865e7673e0dbd89512b525d1bde5c912eb07885550Chris Banes + getVerticalLayoutGap() 1187b2f568c9c71763ed823b1f9c274077825b5d0c9eChris Banes - getOverlapPixelsForOffset(dependency)); 1188ea004a01f44aaf36212120c936952c1742d03d30Chris Banes } 1189ea004a01f44aaf36212120c936952c1742d03d30Chris Banes } 1190ea004a01f44aaf36212120c936952c1742d03d30Chris Banes 1191b2f568c9c71763ed823b1f9c274077825b5d0c9eChris Banes 1192b2f568c9c71763ed823b1f9c274077825b5d0c9eChris Banes @Override 1193b2f568c9c71763ed823b1f9c274077825b5d0c9eChris Banes float getOverlapRatioForOffset(final View header) { 1194b2f568c9c71763ed823b1f9c274077825b5d0c9eChris Banes if (header instanceof AppBarLayout) { 1195b2f568c9c71763ed823b1f9c274077825b5d0c9eChris Banes final AppBarLayout abl = (AppBarLayout) header; 1196ea004a01f44aaf36212120c936952c1742d03d30Chris Banes final int totalScrollRange = abl.getTotalScrollRange(); 1197ea004a01f44aaf36212120c936952c1742d03d30Chris Banes final int preScrollDown = abl.getDownNestedPreScrollRange(); 1198b2f568c9c71763ed823b1f9c274077825b5d0c9eChris Banes final int offset = getAppBarLayoutOffset(abl); 1199a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 1200ea004a01f44aaf36212120c936952c1742d03d30Chris Banes if (preScrollDown != 0 && (totalScrollRange + offset) <= preScrollDown) { 1201ea004a01f44aaf36212120c936952c1742d03d30Chris Banes // If we're in a pre-scroll down. Don't use the offset at all. 1202ea004a01f44aaf36212120c936952c1742d03d30Chris Banes return 0; 1203a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } else { 1204ea004a01f44aaf36212120c936952c1742d03d30Chris Banes final int availScrollRange = totalScrollRange - preScrollDown; 1205dfb857dd12b359c0bdae11a23e039f286d54dc0aChris Banes if (availScrollRange != 0) { 1206ea004a01f44aaf36212120c936952c1742d03d30Chris Banes // Else we'll use a interpolated ratio of the overlap, depending on offset 1207b2f568c9c71763ed823b1f9c274077825b5d0c9eChris Banes return 1f + (offset / (float) availScrollRange); 1208ea004a01f44aaf36212120c936952c1742d03d30Chris Banes } 1209a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 1210a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 1211b2f568c9c71763ed823b1f9c274077825b5d0c9eChris Banes return 0f; 1212a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 1213a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 1214b2f568c9c71763ed823b1f9c274077825b5d0c9eChris Banes private static int getAppBarLayoutOffset(AppBarLayout abl) { 1215b2f568c9c71763ed823b1f9c274077825b5d0c9eChris Banes final CoordinatorLayout.Behavior behavior = 1216b2f568c9c71763ed823b1f9c274077825b5d0c9eChris Banes ((CoordinatorLayout.LayoutParams) abl.getLayoutParams()).getBehavior(); 1217b2f568c9c71763ed823b1f9c274077825b5d0c9eChris Banes if (behavior instanceof Behavior) { 1218b2f568c9c71763ed823b1f9c274077825b5d0c9eChris Banes return ((Behavior) behavior).getTopBottomOffsetForScrollingSibling(); 1219b2f568c9c71763ed823b1f9c274077825b5d0c9eChris Banes } 1220b2f568c9c71763ed823b1f9c274077825b5d0c9eChris Banes return 0; 1221a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 1222a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 1223ec161ac1da9c8ca0e942b01e037ceb1cc51a2f3cMady Mellor @Override 122472a0913607198c5ce3fa351242ccbdfb3b93f178Chris Banes View findFirstDependency(List<View> views) { 1225a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes for (int i = 0, z = views.size(); i < z; i++) { 1226a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes View view = views.get(i); 1227a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes if (view instanceof AppBarLayout) { 122872a0913607198c5ce3fa351242ccbdfb3b93f178Chris Banes return view; 1229a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 1230a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 1231a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes return null; 1232a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 1233ec161ac1da9c8ca0e942b01e037ceb1cc51a2f3cMady Mellor 1234ec161ac1da9c8ca0e942b01e037ceb1cc51a2f3cMady Mellor @Override 123572a0913607198c5ce3fa351242ccbdfb3b93f178Chris Banes int getScrollRange(View v) { 1236ec161ac1da9c8ca0e942b01e037ceb1cc51a2f3cMady Mellor if (v instanceof AppBarLayout) { 1237ec161ac1da9c8ca0e942b01e037ceb1cc51a2f3cMady Mellor return ((AppBarLayout) v).getTotalScrollRange(); 1238ec161ac1da9c8ca0e942b01e037ceb1cc51a2f3cMady Mellor } else { 1239ec161ac1da9c8ca0e942b01e037ceb1cc51a2f3cMady Mellor return super.getScrollRange(v); 1240ec161ac1da9c8ca0e942b01e037ceb1cc51a2f3cMady Mellor } 1241ec161ac1da9c8ca0e942b01e037ceb1cc51a2f3cMady Mellor } 1242a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 1243a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes} 1244