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}