[go: nahoru, domu]

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