1 /*
2  * Copyright (C) 2006 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.widget;
18 
19 import java.util.ArrayList;
20 
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.content.Context;
24 import android.content.res.ColorStateList;
25 import android.content.res.TypedArray;
26 import android.graphics.Canvas;
27 import android.graphics.PorterDuff;
28 import android.graphics.Rect;
29 import android.graphics.Region;
30 import android.graphics.drawable.Drawable;
31 import android.util.AttributeSet;
32 import android.view.Gravity;
33 import android.view.View;
34 import android.view.ViewDebug;
35 import android.view.ViewGroup;
36 import android.view.ViewHierarchyEncoder;
37 import android.widget.RemoteViews.RemoteView;
38 
39 import com.android.internal.R;
40 
41 
42 /**
43  * FrameLayout is designed to block out an area on the screen to display
44  * a single item. Generally, FrameLayout should be used to hold a single child view, because it can
45  * be difficult to organize child views in a way that's scalable to different screen sizes without
46  * the children overlapping each other. You can, however, add multiple children to a FrameLayout
47  * and control their position within the FrameLayout by assigning gravity to each child, using the
48  * <a href="FrameLayout.LayoutParams.html#attr_android:layout_gravity">{@code
49  * android:layout_gravity}</a> attribute.
50  * <p>Child views are drawn in a stack, with the most recently added child on top.
51  * The size of the FrameLayout is the size of its largest child (plus padding), visible
52  * or not (if the FrameLayout's parent permits). Views that are {@link android.view.View#GONE} are
53  * used for sizing
54  * only if {@link #setMeasureAllChildren(boolean) setConsiderGoneChildrenWhenMeasuring()}
55  * is set to true.
56  *
57  * @attr ref android.R.styleable#FrameLayout_measureAllChildren
58  */
59 @RemoteView
60 public class FrameLayout extends ViewGroup {
61     private static final int DEFAULT_CHILD_GRAVITY = Gravity.TOP | Gravity.START;
62 
63     @ViewDebug.ExportedProperty(category = "measurement")
64     boolean mMeasureAllChildren = false;
65 
66     @ViewDebug.ExportedProperty(category = "padding")
67     private int mForegroundPaddingLeft = 0;
68 
69     @ViewDebug.ExportedProperty(category = "padding")
70     private int mForegroundPaddingTop = 0;
71 
72     @ViewDebug.ExportedProperty(category = "padding")
73     private int mForegroundPaddingRight = 0;
74 
75     @ViewDebug.ExportedProperty(category = "padding")
76     private int mForegroundPaddingBottom = 0;
77 
78     private final Rect mSelfBounds = new Rect();
79     private final Rect mOverlayBounds = new Rect();
80 
81     private final ArrayList<View> mMatchParentChildren = new ArrayList<View>(1);
82 
FrameLayout(Context context)83     public FrameLayout(Context context) {
84         super(context);
85     }
86 
FrameLayout(Context context, @Nullable AttributeSet attrs)87     public FrameLayout(Context context, @Nullable AttributeSet attrs) {
88         this(context, attrs, 0);
89     }
90 
FrameLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr)91     public FrameLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
92         this(context, attrs, defStyleAttr, 0);
93     }
94 
FrameLayout( Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes)95     public FrameLayout(
96             Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
97         super(context, attrs, defStyleAttr, defStyleRes);
98 
99         final TypedArray a = context.obtainStyledAttributes(
100                 attrs, com.android.internal.R.styleable.FrameLayout, defStyleAttr, defStyleRes);
101 
102         if (a.getBoolean(com.android.internal.R.styleable.FrameLayout_measureAllChildren, false)) {
103             setMeasureAllChildren(true);
104         }
105 
106         a.recycle();
107     }
108 
109     /**
110      * Describes how the foreground is positioned. Defaults to START and TOP.
111      *
112      * @param foregroundGravity See {@link android.view.Gravity}
113      *
114      * @see #getForegroundGravity()
115      *
116      * @attr ref android.R.styleable#View_foregroundGravity
117      */
118     @android.view.RemotableViewMethod
setForegroundGravity(int foregroundGravity)119     public void setForegroundGravity(int foregroundGravity) {
120         if (getForegroundGravity() != foregroundGravity) {
121             super.setForegroundGravity(foregroundGravity);
122 
123             // calling get* again here because the set above may apply default constraints
124             final Drawable foreground = getForeground();
125             if (getForegroundGravity() == Gravity.FILL && foreground != null) {
126                 Rect padding = new Rect();
127                 if (foreground.getPadding(padding)) {
128                     mForegroundPaddingLeft = padding.left;
129                     mForegroundPaddingTop = padding.top;
130                     mForegroundPaddingRight = padding.right;
131                     mForegroundPaddingBottom = padding.bottom;
132                 }
133             } else {
134                 mForegroundPaddingLeft = 0;
135                 mForegroundPaddingTop = 0;
136                 mForegroundPaddingRight = 0;
137                 mForegroundPaddingBottom = 0;
138             }
139 
140             requestLayout();
141         }
142     }
143 
144     /**
145      * Returns a set of layout parameters with a width of
146      * {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT},
147      * and a height of {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT}.
148      */
149     @Override
generateDefaultLayoutParams()150     protected LayoutParams generateDefaultLayoutParams() {
151         return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
152     }
153 
getPaddingLeftWithForeground()154     int getPaddingLeftWithForeground() {
155         return isForegroundInsidePadding() ? Math.max(mPaddingLeft, mForegroundPaddingLeft) :
156             mPaddingLeft + mForegroundPaddingLeft;
157     }
158 
getPaddingRightWithForeground()159     int getPaddingRightWithForeground() {
160         return isForegroundInsidePadding() ? Math.max(mPaddingRight, mForegroundPaddingRight) :
161             mPaddingRight + mForegroundPaddingRight;
162     }
163 
getPaddingTopWithForeground()164     private int getPaddingTopWithForeground() {
165         return isForegroundInsidePadding() ? Math.max(mPaddingTop, mForegroundPaddingTop) :
166             mPaddingTop + mForegroundPaddingTop;
167     }
168 
getPaddingBottomWithForeground()169     private int getPaddingBottomWithForeground() {
170         return isForegroundInsidePadding() ? Math.max(mPaddingBottom, mForegroundPaddingBottom) :
171             mPaddingBottom + mForegroundPaddingBottom;
172     }
173 
174 
175     /**
176      * {@inheritDoc}
177      */
178     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)179     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
180         int count = getChildCount();
181 
182         final boolean measureMatchParentChildren =
183                 MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
184                 MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
185         mMatchParentChildren.clear();
186 
187         int maxHeight = 0;
188         int maxWidth = 0;
189         int childState = 0;
190 
191         for (int i = 0; i < count; i++) {
192             final View child = getChildAt(i);
193             if (mMeasureAllChildren || child.getVisibility() != GONE) {
194                 measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
195                 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
196                 maxWidth = Math.max(maxWidth,
197                         child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
198                 maxHeight = Math.max(maxHeight,
199                         child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
200                 childState = combineMeasuredStates(childState, child.getMeasuredState());
201                 if (measureMatchParentChildren) {
202                     if (lp.width == LayoutParams.MATCH_PARENT ||
203                             lp.height == LayoutParams.MATCH_PARENT) {
204                         mMatchParentChildren.add(child);
205                     }
206                 }
207             }
208         }
209 
210         // Account for padding too
211         maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
212         maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();
213 
214         // Check against our minimum height and width
215         maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
216         maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
217 
218         // Check against our foreground's minimum height and width
219         final Drawable drawable = getForeground();
220         if (drawable != null) {
221             maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
222             maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
223         }
224 
225         setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
226                 resolveSizeAndState(maxHeight, heightMeasureSpec,
227                         childState << MEASURED_HEIGHT_STATE_SHIFT));
228 
229         count = mMatchParentChildren.size();
230         if (count > 1) {
231             for (int i = 0; i < count; i++) {
232                 final View child = mMatchParentChildren.get(i);
233                 final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
234 
235                 final int childWidthMeasureSpec;
236                 if (lp.width == LayoutParams.MATCH_PARENT) {
237                     final int width = Math.max(0, getMeasuredWidth()
238                             - getPaddingLeftWithForeground() - getPaddingRightWithForeground()
239                             - lp.leftMargin - lp.rightMargin);
240                     childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
241                             width, MeasureSpec.EXACTLY);
242                 } else {
243                     childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
244                             getPaddingLeftWithForeground() + getPaddingRightWithForeground() +
245                             lp.leftMargin + lp.rightMargin,
246                             lp.width);
247                 }
248 
249                 final int childHeightMeasureSpec;
250                 if (lp.height == LayoutParams.MATCH_PARENT) {
251                     final int height = Math.max(0, getMeasuredHeight()
252                             - getPaddingTopWithForeground() - getPaddingBottomWithForeground()
253                             - lp.topMargin - lp.bottomMargin);
254                     childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
255                             height, MeasureSpec.EXACTLY);
256                 } else {
257                     childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
258                             getPaddingTopWithForeground() + getPaddingBottomWithForeground() +
259                             lp.topMargin + lp.bottomMargin,
260                             lp.height);
261                 }
262 
263                 child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
264             }
265         }
266     }
267 
268     /**
269      * {@inheritDoc}
270      */
271     @Override
onLayout(boolean changed, int left, int top, int right, int bottom)272     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
273         layoutChildren(left, top, right, bottom, false /* no force left gravity */);
274     }
275 
layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity)276     void layoutChildren(int left, int top, int right, int bottom,
277                                   boolean forceLeftGravity) {
278         final int count = getChildCount();
279 
280         final int parentLeft = getPaddingLeftWithForeground();
281         final int parentRight = right - left - getPaddingRightWithForeground();
282 
283         final int parentTop = getPaddingTopWithForeground();
284         final int parentBottom = bottom - top - getPaddingBottomWithForeground();
285 
286         for (int i = 0; i < count; i++) {
287             final View child = getChildAt(i);
288             if (child.getVisibility() != GONE) {
289                 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
290 
291                 final int width = child.getMeasuredWidth();
292                 final int height = child.getMeasuredHeight();
293 
294                 int childLeft;
295                 int childTop;
296 
297                 int gravity = lp.gravity;
298                 if (gravity == -1) {
299                     gravity = DEFAULT_CHILD_GRAVITY;
300                 }
301 
302                 final int layoutDirection = getLayoutDirection();
303                 final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
304                 final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
305 
306                 switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
307                     case Gravity.CENTER_HORIZONTAL:
308                         childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
309                         lp.leftMargin - lp.rightMargin;
310                         break;
311                     case Gravity.RIGHT:
312                         if (!forceLeftGravity) {
313                             childLeft = parentRight - width - lp.rightMargin;
314                             break;
315                         }
316                     case Gravity.LEFT:
317                     default:
318                         childLeft = parentLeft + lp.leftMargin;
319                 }
320 
321                 switch (verticalGravity) {
322                     case Gravity.TOP:
323                         childTop = parentTop + lp.topMargin;
324                         break;
325                     case Gravity.CENTER_VERTICAL:
326                         childTop = parentTop + (parentBottom - parentTop - height) / 2 +
327                         lp.topMargin - lp.bottomMargin;
328                         break;
329                     case Gravity.BOTTOM:
330                         childTop = parentBottom - height - lp.bottomMargin;
331                         break;
332                     default:
333                         childTop = parentTop + lp.topMargin;
334                 }
335 
336                 child.layout(childLeft, childTop, childLeft + width, childTop + height);
337             }
338         }
339     }
340 
341     /**
342      * Sets whether to consider all children, or just those in
343      * the VISIBLE or INVISIBLE state, when measuring. Defaults to false.
344      *
345      * @param measureAll true to consider children marked GONE, false otherwise.
346      * Default value is false.
347      *
348      * @attr ref android.R.styleable#FrameLayout_measureAllChildren
349      */
350     @android.view.RemotableViewMethod
setMeasureAllChildren(boolean measureAll)351     public void setMeasureAllChildren(boolean measureAll) {
352         mMeasureAllChildren = measureAll;
353     }
354 
355     /**
356      * Determines whether all children, or just those in the VISIBLE or
357      * INVISIBLE state, are considered when measuring.
358      *
359      * @return Whether all children are considered when measuring.
360      *
361      * @deprecated This method is deprecated in favor of
362      * {@link #getMeasureAllChildren() getMeasureAllChildren()}, which was
363      * renamed for consistency with
364      * {@link #setMeasureAllChildren(boolean) setMeasureAllChildren()}.
365      */
366     @Deprecated
getConsiderGoneChildrenWhenMeasuring()367     public boolean getConsiderGoneChildrenWhenMeasuring() {
368         return getMeasureAllChildren();
369     }
370 
371     /**
372      * Determines whether all children, or just those in the VISIBLE or
373      * INVISIBLE state, are considered when measuring.
374      *
375      * @return Whether all children are considered when measuring.
376      */
getMeasureAllChildren()377     public boolean getMeasureAllChildren() {
378         return mMeasureAllChildren;
379     }
380 
381     /**
382      * {@inheritDoc}
383      */
384     @Override
generateLayoutParams(AttributeSet attrs)385     public LayoutParams generateLayoutParams(AttributeSet attrs) {
386         return new FrameLayout.LayoutParams(getContext(), attrs);
387     }
388 
389     @Override
shouldDelayChildPressedState()390     public boolean shouldDelayChildPressedState() {
391         return false;
392     }
393 
394     /**
395      * {@inheritDoc}
396      */
397     @Override
checkLayoutParams(ViewGroup.LayoutParams p)398     protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
399         return p instanceof LayoutParams;
400     }
401 
402     @Override
generateLayoutParams(ViewGroup.LayoutParams p)403     protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
404         return new LayoutParams(p);
405     }
406 
407     @Override
getAccessibilityClassName()408     public CharSequence getAccessibilityClassName() {
409         return FrameLayout.class.getName();
410     }
411 
412     /** @hide */
413     @Override
encodeProperties(@onNull ViewHierarchyEncoder encoder)414     protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) {
415         super.encodeProperties(encoder);
416 
417         encoder.addProperty("measurement:measureAllChildren", mMeasureAllChildren);
418         encoder.addProperty("padding:foregroundPaddingLeft", mForegroundPaddingLeft);
419         encoder.addProperty("padding:foregroundPaddingTop", mForegroundPaddingTop);
420         encoder.addProperty("padding:foregroundPaddingRight", mForegroundPaddingRight);
421         encoder.addProperty("padding:foregroundPaddingBottom", mForegroundPaddingBottom);
422     }
423 
424     /**
425      * Per-child layout information for layouts that support margins.
426      * See {@link android.R.styleable#FrameLayout_Layout FrameLayout Layout Attributes}
427      * for a list of all child view attributes that this class supports.
428      *
429      * @attr ref android.R.styleable#FrameLayout_Layout_layout_gravity
430      */
431     public static class LayoutParams extends MarginLayoutParams {
432         /**
433          * The gravity to apply with the View to which these layout parameters
434          * are associated.
435          *
436          * @see android.view.Gravity
437          *
438          * @attr ref android.R.styleable#FrameLayout_Layout_layout_gravity
439          */
440         public int gravity = -1;
441 
442         /**
443          * {@inheritDoc}
444          */
LayoutParams(Context c, AttributeSet attrs)445         public LayoutParams(Context c, AttributeSet attrs) {
446             super(c, attrs);
447 
448             TypedArray a = c.obtainStyledAttributes(attrs, com.android.internal.R.styleable.FrameLayout_Layout);
449             gravity = a.getInt(com.android.internal.R.styleable.FrameLayout_Layout_layout_gravity, -1);
450             a.recycle();
451         }
452 
453         /**
454          * {@inheritDoc}
455          */
LayoutParams(int width, int height)456         public LayoutParams(int width, int height) {
457             super(width, height);
458         }
459 
460         /**
461          * Creates a new set of layout parameters with the specified width, height
462          * and weight.
463          *
464          * @param width the width, either {@link #MATCH_PARENT},
465          *        {@link #WRAP_CONTENT} or a fixed size in pixels
466          * @param height the height, either {@link #MATCH_PARENT},
467          *        {@link #WRAP_CONTENT} or a fixed size in pixels
468          * @param gravity the gravity
469          *
470          * @see android.view.Gravity
471          */
LayoutParams(int width, int height, int gravity)472         public LayoutParams(int width, int height, int gravity) {
473             super(width, height);
474             this.gravity = gravity;
475         }
476 
477         /**
478          * {@inheritDoc}
479          */
LayoutParams(ViewGroup.LayoutParams source)480         public LayoutParams(ViewGroup.LayoutParams source) {
481             super(source);
482         }
483 
484         /**
485          * {@inheritDoc}
486          */
LayoutParams(ViewGroup.MarginLayoutParams source)487         public LayoutParams(ViewGroup.MarginLayoutParams source) {
488             super(source);
489         }
490 
491         /**
492          * Copy constructor. Clones the width, height, margin values, and
493          * gravity of the source.
494          *
495          * @param source The layout params to copy from.
496          */
LayoutParams(LayoutParams source)497         public LayoutParams(LayoutParams source) {
498             super(source);
499 
500             this.gravity = source.gravity;
501         }
502     }
503 }
504