1 /*
2  * Copyright 2018 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 androidx.media.widget;
18 
19 import android.content.Context;
20 import android.graphics.drawable.Drawable;
21 import android.os.Build;
22 import android.util.AttributeSet;
23 import android.view.View;
24 import android.view.ViewGroup;
25 
26 import androidx.annotation.AttrRes;
27 import androidx.annotation.NonNull;
28 import androidx.annotation.Nullable;
29 import androidx.annotation.RequiresApi;
30 import androidx.annotation.StyleRes;
31 
32 import java.util.ArrayList;
33 
34 class BaseLayout extends ViewGroup {
35     private final ArrayList<View> mMatchParentChildren = new ArrayList<>(1);
36 
BaseLayout(@onNull Context context)37     BaseLayout(@NonNull Context context) {
38         super(context);
39     }
40 
BaseLayout(@onNull Context context, @Nullable AttributeSet attrs)41     BaseLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
42         super(context, attrs);
43     }
44 
BaseLayout(@onNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr)45     BaseLayout(@NonNull Context context, @Nullable AttributeSet attrs,
46             @AttrRes int defStyleAttr) {
47         super(context, attrs, defStyleAttr);
48     }
49 
50     @RequiresApi(21)
BaseLayout(@onNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr, @StyleRes int defStyleRes)51     BaseLayout(@NonNull Context context, @Nullable AttributeSet attrs,
52             @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
53         super(context, attrs, defStyleAttr, defStyleRes);
54     }
55 
56     @Override
checkLayoutParams(LayoutParams p)57     public boolean checkLayoutParams(LayoutParams p) {
58         return p instanceof MarginLayoutParams;
59     }
60 
61     @Override
generateDefaultLayoutParams()62     public LayoutParams generateDefaultLayoutParams() {
63         return new MarginLayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
64     }
65 
66     @Override
generateLayoutParams(AttributeSet attrs)67     public LayoutParams generateLayoutParams(AttributeSet attrs) {
68         return new MarginLayoutParams(getContext(), attrs);
69     }
70 
71     @Override
generateLayoutParams(LayoutParams lp)72     public LayoutParams generateLayoutParams(LayoutParams lp) {
73         if (lp instanceof MarginLayoutParams) {
74             return lp;
75         }
76         return new MarginLayoutParams(lp);
77     }
78 
79     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)80     public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
81         int count = getChildCount();
82 
83         final boolean measureMatchParentChildren =
84                 MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY
85                         || MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
86         mMatchParentChildren.clear();
87 
88         int maxHeight = 0;
89         int maxWidth = 0;
90         int childState = 0;
91 
92         for (int i = 0; i < count; i++) {
93             final View child = getChildAt(i);
94             if (child.getVisibility() != View.GONE) {
95                 measureChildWithMargins(
96                         child, widthMeasureSpec, 0, heightMeasureSpec, 0);
97                 final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
98                 maxWidth = Math.max(maxWidth,
99                         child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
100                 maxHeight = Math.max(maxHeight,
101                         child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
102                 childState = childState | child.getMeasuredState();
103                 if (measureMatchParentChildren) {
104                     if (lp.width == LayoutParams.MATCH_PARENT
105                             || lp.height == LayoutParams.MATCH_PARENT) {
106                         mMatchParentChildren.add(child);
107                     }
108                 }
109             }
110         }
111 
112         // Account for padding too
113         maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
114         maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();
115 
116         // Check against our minimum height and width
117         maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
118         maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
119 
120         if (Build.VERSION.SDK_INT >= 23) {
121             // Check against our foreground's minimum height and width
122             final Drawable drawable = getForeground();
123             if (drawable != null) {
124                 maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
125                 maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
126             }
127         }
128 
129         setMeasuredDimension(
130                 resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
131                 resolveSizeAndState(maxHeight, heightMeasureSpec,
132                         childState << View.MEASURED_HEIGHT_STATE_SHIFT));
133 
134         count = mMatchParentChildren.size();
135         if (count > 1) {
136             for (int i = 0; i < count; i++) {
137                 final View child = mMatchParentChildren.get(i);
138                 final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
139 
140                 final int childWidthMeasureSpec;
141                 if (lp.width == LayoutParams.MATCH_PARENT) {
142                     final int width = Math.max(0, getMeasuredWidth()
143                             - getPaddingLeftWithForeground() - getPaddingRightWithForeground()
144                             - lp.leftMargin - lp.rightMargin);
145                     childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
146                             width, MeasureSpec.EXACTLY);
147                 } else {
148                     childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
149                             getPaddingLeftWithForeground() + getPaddingRightWithForeground()
150                                     + lp.leftMargin + lp.rightMargin, lp.width);
151                 }
152 
153                 final int childHeightMeasureSpec;
154                 if (lp.height == LayoutParams.MATCH_PARENT) {
155                     final int height = Math.max(0, getMeasuredHeight()
156                             - getPaddingTopWithForeground() - getPaddingBottomWithForeground()
157                             - lp.topMargin - lp.bottomMargin);
158                     childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
159                             height, MeasureSpec.EXACTLY);
160                 } else {
161                     childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
162                             getPaddingTopWithForeground() + getPaddingBottomWithForeground()
163                                     + lp.topMargin + lp.bottomMargin, lp.height);
164                 }
165 
166                 child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
167             }
168         }
169     }
170 
171     @Override
onLayout(boolean changed, int left, int top, int right, int bottom)172     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
173         final int count = getChildCount();
174 
175         final int parentLeft = getPaddingLeftWithForeground();
176         final int parentRight = right - left - getPaddingRightWithForeground();
177 
178         final int parentTop = getPaddingTopWithForeground();
179         final int parentBottom = bottom - top - getPaddingBottomWithForeground();
180 
181         for (int i = 0; i < count; i++) {
182             final View child = getChildAt(i);
183             if (child.getVisibility() != View.GONE) {
184                 final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
185 
186                 final int width = child.getMeasuredWidth();
187                 final int height = child.getMeasuredHeight();
188 
189                 int childLeft;
190                 int childTop;
191 
192                 childLeft = parentLeft + (parentRight - parentLeft - width) / 2
193                         + lp.leftMargin - lp.rightMargin;
194 
195                 childTop = parentTop + (parentBottom - parentTop - height) / 2
196                         + lp.topMargin - lp.bottomMargin;
197 
198                 child.layout(childLeft, childTop, childLeft + width, childTop + height);
199             }
200         }
201     }
202 
203     @Override
shouldDelayChildPressedState()204     public boolean shouldDelayChildPressedState() {
205         return false;
206     }
207 
getPaddingLeftWithForeground()208     private int getPaddingLeftWithForeground() {
209         return isForegroundInsidePadding() ? Math.max(getPaddingLeft(), 0) :
210                 getPaddingLeft() + 0;
211     }
212 
getPaddingRightWithForeground()213     private int getPaddingRightWithForeground() {
214         return isForegroundInsidePadding() ? Math.max(getPaddingRight(), 0) :
215                 getPaddingRight() + 0;
216     }
217 
getPaddingTopWithForeground()218     private int getPaddingTopWithForeground() {
219         return isForegroundInsidePadding() ? Math.max(getPaddingTop(), 0) :
220                 getPaddingTop() + 0;
221     }
222 
getPaddingBottomWithForeground()223     private int getPaddingBottomWithForeground() {
224         return isForegroundInsidePadding() ? Math.max(getPaddingBottom(), 0) :
225                 getPaddingBottom() + 0;
226     }
227 
228     // A stub method for View's isForegroundInsidePadding() which is hidden.
229     // Always returns true for now, since the default value is true.
230     // See View's isForegroundInsidePadding method.
isForegroundInsidePadding()231     private boolean isForegroundInsidePadding() {
232         return true;
233     }
234 }
235