1ec161ac1da9c8ca0e942b01e037ceb1cc51a2f3cMady Mellor/* 2ec161ac1da9c8ca0e942b01e037ceb1cc51a2f3cMady Mellor * Copyright (C) 2015 The Android Open Source Project 3ec161ac1da9c8ca0e942b01e037ceb1cc51a2f3cMady Mellor * 4ec161ac1da9c8ca0e942b01e037ceb1cc51a2f3cMady Mellor * Licensed under the Apache License, Version 2.0 (the "License"); 5ec161ac1da9c8ca0e942b01e037ceb1cc51a2f3cMady Mellor * you may not use this file except in compliance with the License. 6ec161ac1da9c8ca0e942b01e037ceb1cc51a2f3cMady Mellor * You may obtain a copy of the License at 7ec161ac1da9c8ca0e942b01e037ceb1cc51a2f3cMady Mellor * 8ec161ac1da9c8ca0e942b01e037ceb1cc51a2f3cMady Mellor * http://www.apache.org/licenses/LICENSE-2.0 9ec161ac1da9c8ca0e942b01e037ceb1cc51a2f3cMady Mellor * 10ec161ac1da9c8ca0e942b01e037ceb1cc51a2f3cMady Mellor * Unless required by applicable law or agreed to in writing, software 11ec161ac1da9c8ca0e942b01e037ceb1cc51a2f3cMady Mellor * distributed under the License is distributed on an "AS IS" BASIS, 12ec161ac1da9c8ca0e942b01e037ceb1cc51a2f3cMady Mellor * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13ec161ac1da9c8ca0e942b01e037ceb1cc51a2f3cMady Mellor * See the License for the specific language governing permissions and 14ec161ac1da9c8ca0e942b01e037ceb1cc51a2f3cMady Mellor * limitations under the License. 15ec161ac1da9c8ca0e942b01e037ceb1cc51a2f3cMady Mellor */ 16ec161ac1da9c8ca0e942b01e037ceb1cc51a2f3cMady Mellor 17ec161ac1da9c8ca0e942b01e037ceb1cc51a2f3cMady Mellorpackage android.support.design.widget; 18ec161ac1da9c8ca0e942b01e037ceb1cc51a2f3cMady Mellor 19ec161ac1da9c8ca0e942b01e037ceb1cc51a2f3cMady Mellorimport android.content.Context; 20c682e2c8129f6c2b6596b0ce4a8c4803629b432dChris Banesimport android.graphics.Rect; 21ec161ac1da9c8ca0e942b01e037ceb1cc51a2f3cMady Mellorimport android.support.design.widget.CoordinatorLayout.Behavior; 22c682e2c8129f6c2b6596b0ce4a8c4803629b432dChris Banesimport android.support.v4.view.GravityCompat; 23ec161ac1da9c8ca0e942b01e037ceb1cc51a2f3cMady Mellorimport android.support.v4.view.ViewCompat; 242e522d2998c937948757ccfe0a5523047726fb4dChris Banesimport android.support.v4.view.WindowInsetsCompat; 25ec161ac1da9c8ca0e942b01e037ceb1cc51a2f3cMady Mellorimport android.util.AttributeSet; 26c682e2c8129f6c2b6596b0ce4a8c4803629b432dChris Banesimport android.view.Gravity; 27ec161ac1da9c8ca0e942b01e037ceb1cc51a2f3cMady Mellorimport android.view.View; 28ec161ac1da9c8ca0e942b01e037ceb1cc51a2f3cMady Mellorimport android.view.ViewGroup; 29ec161ac1da9c8ca0e942b01e037ceb1cc51a2f3cMady Mellor 30ec161ac1da9c8ca0e942b01e037ceb1cc51a2f3cMady Mellorimport java.util.List; 31ec161ac1da9c8ca0e942b01e037ceb1cc51a2f3cMady Mellor 32ec161ac1da9c8ca0e942b01e037ceb1cc51a2f3cMady Mellor/** 33ec161ac1da9c8ca0e942b01e037ceb1cc51a2f3cMady Mellor * The {@link Behavior} for a scrolling view that is positioned vertically below another view. 34ec161ac1da9c8ca0e942b01e037ceb1cc51a2f3cMady Mellor * See {@link HeaderBehavior}. 35ec161ac1da9c8ca0e942b01e037ceb1cc51a2f3cMady Mellor */ 36ec161ac1da9c8ca0e942b01e037ceb1cc51a2f3cMady Mellorabstract class HeaderScrollingViewBehavior extends ViewOffsetBehavior<View> { 37ec161ac1da9c8ca0e942b01e037ceb1cc51a2f3cMady Mellor 3800a00a7d3ba8279294f63994473afc32e05dcf10Chris Banes final Rect mTempRect1 = new Rect(); 3900a00a7d3ba8279294f63994473afc32e05dcf10Chris Banes final Rect mTempRect2 = new Rect(); 40c682e2c8129f6c2b6596b0ce4a8c4803629b432dChris Banes 415e7673e0dbd89512b525d1bde5c912eb07885550Chris Banes private int mVerticalLayoutGap = 0; 42b2f568c9c71763ed823b1f9c274077825b5d0c9eChris Banes private int mOverlayTop; 435e7673e0dbd89512b525d1bde5c912eb07885550Chris Banes 4472a0913607198c5ce3fa351242ccbdfb3b93f178Chris Banes public HeaderScrollingViewBehavior() {} 45ec161ac1da9c8ca0e942b01e037ceb1cc51a2f3cMady Mellor 46ec161ac1da9c8ca0e942b01e037ceb1cc51a2f3cMady Mellor public HeaderScrollingViewBehavior(Context context, AttributeSet attrs) { 47ec161ac1da9c8ca0e942b01e037ceb1cc51a2f3cMady Mellor super(context, attrs); 48ec161ac1da9c8ca0e942b01e037ceb1cc51a2f3cMady Mellor } 49ec161ac1da9c8ca0e942b01e037ceb1cc51a2f3cMady Mellor 50ec161ac1da9c8ca0e942b01e037ceb1cc51a2f3cMady Mellor @Override 51ec161ac1da9c8ca0e942b01e037ceb1cc51a2f3cMady Mellor public boolean onMeasureChild(CoordinatorLayout parent, View child, 52ec161ac1da9c8ca0e942b01e037ceb1cc51a2f3cMady Mellor int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, 53ec161ac1da9c8ca0e942b01e037ceb1cc51a2f3cMady Mellor int heightUsed) { 54ec161ac1da9c8ca0e942b01e037ceb1cc51a2f3cMady Mellor final int childLpHeight = child.getLayoutParams().height; 55ec161ac1da9c8ca0e942b01e037ceb1cc51a2f3cMady Mellor if (childLpHeight == ViewGroup.LayoutParams.MATCH_PARENT 56ec161ac1da9c8ca0e942b01e037ceb1cc51a2f3cMady Mellor || childLpHeight == ViewGroup.LayoutParams.WRAP_CONTENT) { 57ec161ac1da9c8ca0e942b01e037ceb1cc51a2f3cMady Mellor // If the menu's height is set to match_parent/wrap_content then measure it 58ec161ac1da9c8ca0e942b01e037ceb1cc51a2f3cMady Mellor // with the maximum visible height 59ec161ac1da9c8ca0e942b01e037ceb1cc51a2f3cMady Mellor 60ec161ac1da9c8ca0e942b01e037ceb1cc51a2f3cMady Mellor final List<View> dependencies = parent.getDependencies(child); 61ec161ac1da9c8ca0e942b01e037ceb1cc51a2f3cMady Mellor final View header = findFirstDependency(dependencies); 62c682e2c8129f6c2b6596b0ce4a8c4803629b432dChris Banes if (header != null) { 63c682e2c8129f6c2b6596b0ce4a8c4803629b432dChris Banes if (ViewCompat.getFitsSystemWindows(header) 64c682e2c8129f6c2b6596b0ce4a8c4803629b432dChris Banes && !ViewCompat.getFitsSystemWindows(child)) { 65ec161ac1da9c8ca0e942b01e037ceb1cc51a2f3cMady Mellor // If the header is fitting system windows then we need to also, 66c682e2c8129f6c2b6596b0ce4a8c4803629b432dChris Banes // otherwise we'll get CoL's compatible measuring 67ec161ac1da9c8ca0e942b01e037ceb1cc51a2f3cMady Mellor ViewCompat.setFitsSystemWindows(child, true); 68ec161ac1da9c8ca0e942b01e037ceb1cc51a2f3cMady Mellor 69c682e2c8129f6c2b6596b0ce4a8c4803629b432dChris Banes if (ViewCompat.getFitsSystemWindows(child)) { 70c682e2c8129f6c2b6596b0ce4a8c4803629b432dChris Banes // If the set succeeded, trigger a new layout and return true 71c682e2c8129f6c2b6596b0ce4a8c4803629b432dChris Banes child.requestLayout(); 72c682e2c8129f6c2b6596b0ce4a8c4803629b432dChris Banes return true; 73c682e2c8129f6c2b6596b0ce4a8c4803629b432dChris Banes } 74ec161ac1da9c8ca0e942b01e037ceb1cc51a2f3cMady Mellor } 75ec161ac1da9c8ca0e942b01e037ceb1cc51a2f3cMady Mellor 766d2682fe1a0671bb336c99e0f72a1742b130be99Chris Banes int availableHeight = View.MeasureSpec.getSize(parentHeightMeasureSpec); 776d2682fe1a0671bb336c99e0f72a1742b130be99Chris Banes if (availableHeight == 0) { 786d2682fe1a0671bb336c99e0f72a1742b130be99Chris Banes // If the measure spec doesn't specify a size, use the current height 796d2682fe1a0671bb336c99e0f72a1742b130be99Chris Banes availableHeight = parent.getHeight(); 806d2682fe1a0671bb336c99e0f72a1742b130be99Chris Banes } 81ec161ac1da9c8ca0e942b01e037ceb1cc51a2f3cMady Mellor 826d2682fe1a0671bb336c99e0f72a1742b130be99Chris Banes final int height = availableHeight - header.getMeasuredHeight() 836d2682fe1a0671bb336c99e0f72a1742b130be99Chris Banes + getScrollRange(header); 846d2682fe1a0671bb336c99e0f72a1742b130be99Chris Banes final int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(height, 856d2682fe1a0671bb336c99e0f72a1742b130be99Chris Banes childLpHeight == ViewGroup.LayoutParams.MATCH_PARENT 866d2682fe1a0671bb336c99e0f72a1742b130be99Chris Banes ? View.MeasureSpec.EXACTLY 876d2682fe1a0671bb336c99e0f72a1742b130be99Chris Banes : View.MeasureSpec.AT_MOST); 88ec161ac1da9c8ca0e942b01e037ceb1cc51a2f3cMady Mellor 896d2682fe1a0671bb336c99e0f72a1742b130be99Chris Banes // Now measure the scrolling view with the correct height 906d2682fe1a0671bb336c99e0f72a1742b130be99Chris Banes parent.onMeasureChild(child, parentWidthMeasureSpec, 916d2682fe1a0671bb336c99e0f72a1742b130be99Chris Banes widthUsed, heightMeasureSpec, heightUsed); 92c682e2c8129f6c2b6596b0ce4a8c4803629b432dChris Banes 936d2682fe1a0671bb336c99e0f72a1742b130be99Chris Banes return true; 94ec161ac1da9c8ca0e942b01e037ceb1cc51a2f3cMady Mellor } 95ec161ac1da9c8ca0e942b01e037ceb1cc51a2f3cMady Mellor } 96ec161ac1da9c8ca0e942b01e037ceb1cc51a2f3cMady Mellor return false; 97ec161ac1da9c8ca0e942b01e037ceb1cc51a2f3cMady Mellor } 98ec161ac1da9c8ca0e942b01e037ceb1cc51a2f3cMady Mellor 99c682e2c8129f6c2b6596b0ce4a8c4803629b432dChris Banes @Override 100e04ed82f4562103baed27d81ff172418ab3ca41aChris Banes protected void layoutChild(final CoordinatorLayout parent, final View child, 101e04ed82f4562103baed27d81ff172418ab3ca41aChris Banes final int layoutDirection) { 102c682e2c8129f6c2b6596b0ce4a8c4803629b432dChris Banes final List<View> dependencies = parent.getDependencies(child); 103c682e2c8129f6c2b6596b0ce4a8c4803629b432dChris Banes final View header = findFirstDependency(dependencies); 104c682e2c8129f6c2b6596b0ce4a8c4803629b432dChris Banes 105c682e2c8129f6c2b6596b0ce4a8c4803629b432dChris Banes if (header != null) { 106c682e2c8129f6c2b6596b0ce4a8c4803629b432dChris Banes final CoordinatorLayout.LayoutParams lp = 107c682e2c8129f6c2b6596b0ce4a8c4803629b432dChris Banes (CoordinatorLayout.LayoutParams) child.getLayoutParams(); 108e04ed82f4562103baed27d81ff172418ab3ca41aChris Banes final Rect available = mTempRect1; 109e04ed82f4562103baed27d81ff172418ab3ca41aChris Banes available.set(parent.getPaddingLeft() + lp.leftMargin, 110e04ed82f4562103baed27d81ff172418ab3ca41aChris Banes header.getBottom() + lp.topMargin, 111c682e2c8129f6c2b6596b0ce4a8c4803629b432dChris Banes parent.getWidth() - parent.getPaddingRight() - lp.rightMargin, 112e04ed82f4562103baed27d81ff172418ab3ca41aChris Banes parent.getHeight() + header.getBottom() 113e04ed82f4562103baed27d81ff172418ab3ca41aChris Banes - parent.getPaddingBottom() - lp.bottomMargin); 114c682e2c8129f6c2b6596b0ce4a8c4803629b432dChris Banes 1152e522d2998c937948757ccfe0a5523047726fb4dChris Banes final WindowInsetsCompat parentInsets = parent.getLastWindowInsets(); 1162e522d2998c937948757ccfe0a5523047726fb4dChris Banes if (parentInsets != null && ViewCompat.getFitsSystemWindows(parent) 1172e522d2998c937948757ccfe0a5523047726fb4dChris Banes && !ViewCompat.getFitsSystemWindows(child)) { 1182e522d2998c937948757ccfe0a5523047726fb4dChris Banes // If we're set to handle insets but this child isn't, then it has been measured as 1192e522d2998c937948757ccfe0a5523047726fb4dChris Banes // if there are no insets. We need to lay it out to match horizontally. 1202e522d2998c937948757ccfe0a5523047726fb4dChris Banes // Top and bottom and already handled in the logic above 1212e522d2998c937948757ccfe0a5523047726fb4dChris Banes available.left += parentInsets.getSystemWindowInsetLeft(); 1222e522d2998c937948757ccfe0a5523047726fb4dChris Banes available.right -= parentInsets.getSystemWindowInsetRight(); 1232e522d2998c937948757ccfe0a5523047726fb4dChris Banes } 1242e522d2998c937948757ccfe0a5523047726fb4dChris Banes 125c682e2c8129f6c2b6596b0ce4a8c4803629b432dChris Banes final Rect out = mTempRect2; 126c682e2c8129f6c2b6596b0ce4a8c4803629b432dChris Banes GravityCompat.apply(resolveGravity(lp.gravity), child.getMeasuredWidth(), 127e04ed82f4562103baed27d81ff172418ab3ca41aChris Banes child.getMeasuredHeight(), available, out, layoutDirection); 128c682e2c8129f6c2b6596b0ce4a8c4803629b432dChris Banes 129b2f568c9c71763ed823b1f9c274077825b5d0c9eChris Banes final int overlap = getOverlapPixelsForOffset(header); 130b2f568c9c71763ed823b1f9c274077825b5d0c9eChris Banes 131b2f568c9c71763ed823b1f9c274077825b5d0c9eChris Banes child.layout(out.left, out.top - overlap, out.right, out.bottom - overlap); 1325e7673e0dbd89512b525d1bde5c912eb07885550Chris Banes mVerticalLayoutGap = out.top - header.getBottom(); 133c682e2c8129f6c2b6596b0ce4a8c4803629b432dChris Banes } else { 134c682e2c8129f6c2b6596b0ce4a8c4803629b432dChris Banes // If we don't have a dependency, let super handle it 135c682e2c8129f6c2b6596b0ce4a8c4803629b432dChris Banes super.layoutChild(parent, child, layoutDirection); 1365e7673e0dbd89512b525d1bde5c912eb07885550Chris Banes mVerticalLayoutGap = 0; 137c682e2c8129f6c2b6596b0ce4a8c4803629b432dChris Banes } 138c682e2c8129f6c2b6596b0ce4a8c4803629b432dChris Banes } 139c682e2c8129f6c2b6596b0ce4a8c4803629b432dChris Banes 140b2f568c9c71763ed823b1f9c274077825b5d0c9eChris Banes float getOverlapRatioForOffset(final View header) { 141b2f568c9c71763ed823b1f9c274077825b5d0c9eChris Banes return 1f; 142b2f568c9c71763ed823b1f9c274077825b5d0c9eChris Banes } 143b2f568c9c71763ed823b1f9c274077825b5d0c9eChris Banes 144b2f568c9c71763ed823b1f9c274077825b5d0c9eChris Banes final int getOverlapPixelsForOffset(final View header) { 145bb3740d273d4b5108ab63f6d264466250a197e5bChris Banes return mOverlayTop == 0 ? 0 : MathUtils.constrain( 146bb3740d273d4b5108ab63f6d264466250a197e5bChris Banes (int) (getOverlapRatioForOffset(header) * mOverlayTop), 0, mOverlayTop); 147b2f568c9c71763ed823b1f9c274077825b5d0c9eChris Banes } 148b2f568c9c71763ed823b1f9c274077825b5d0c9eChris Banes 149c682e2c8129f6c2b6596b0ce4a8c4803629b432dChris Banes private static int resolveGravity(int gravity) { 150c682e2c8129f6c2b6596b0ce4a8c4803629b432dChris Banes return gravity == Gravity.NO_GRAVITY ? GravityCompat.START | Gravity.TOP : gravity; 151c682e2c8129f6c2b6596b0ce4a8c4803629b432dChris Banes } 152c682e2c8129f6c2b6596b0ce4a8c4803629b432dChris Banes 15372a0913607198c5ce3fa351242ccbdfb3b93f178Chris Banes abstract View findFirstDependency(List<View> views); 15472a0913607198c5ce3fa351242ccbdfb3b93f178Chris Banes 15572a0913607198c5ce3fa351242ccbdfb3b93f178Chris Banes int getScrollRange(View v) { 156ec161ac1da9c8ca0e942b01e037ceb1cc51a2f3cMady Mellor return v.getMeasuredHeight(); 157ec161ac1da9c8ca0e942b01e037ceb1cc51a2f3cMady Mellor } 1585e7673e0dbd89512b525d1bde5c912eb07885550Chris Banes 1595e7673e0dbd89512b525d1bde5c912eb07885550Chris Banes /** 1605e7673e0dbd89512b525d1bde5c912eb07885550Chris Banes * The gap between the top of the scrolling view and the bottom of the header layout in pixels. 1615e7673e0dbd89512b525d1bde5c912eb07885550Chris Banes */ 162b2f568c9c71763ed823b1f9c274077825b5d0c9eChris Banes final int getVerticalLayoutGap() { 1635e7673e0dbd89512b525d1bde5c912eb07885550Chris Banes return mVerticalLayoutGap; 1645e7673e0dbd89512b525d1bde5c912eb07885550Chris Banes } 165b2f568c9c71763ed823b1f9c274077825b5d0c9eChris Banes 166b2f568c9c71763ed823b1f9c274077825b5d0c9eChris Banes /** 167b2f568c9c71763ed823b1f9c274077825b5d0c9eChris Banes * Set the distance that this view should overlap any {@link AppBarLayout}. 168b2f568c9c71763ed823b1f9c274077825b5d0c9eChris Banes * 169b2f568c9c71763ed823b1f9c274077825b5d0c9eChris Banes * @param overlayTop the distance in px 170b2f568c9c71763ed823b1f9c274077825b5d0c9eChris Banes */ 171b2f568c9c71763ed823b1f9c274077825b5d0c9eChris Banes public final void setOverlayTop(int overlayTop) { 172b2f568c9c71763ed823b1f9c274077825b5d0c9eChris Banes mOverlayTop = overlayTop; 173b2f568c9c71763ed823b1f9c274077825b5d0c9eChris Banes } 174b2f568c9c71763ed823b1f9c274077825b5d0c9eChris Banes 175b2f568c9c71763ed823b1f9c274077825b5d0c9eChris Banes /** 176b2f568c9c71763ed823b1f9c274077825b5d0c9eChris Banes * Returns the distance that this view should overlap any {@link AppBarLayout}. 177b2f568c9c71763ed823b1f9c274077825b5d0c9eChris Banes */ 178b2f568c9c71763ed823b1f9c274077825b5d0c9eChris Banes public final int getOverlayTop() { 179b2f568c9c71763ed823b1f9c274077825b5d0c9eChris Banes return mOverlayTop; 180b2f568c9c71763ed823b1f9c274077825b5d0c9eChris Banes } 181ec161ac1da9c8ca0e942b01e037ceb1cc51a2f3cMady Mellor}