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