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