/*
 * Copyright (C) 2006 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.widget;

import android.annotation.AttrRes;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.StyleRes;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
import android.view.ViewDebug;
import android.view.ViewGroup;
import android.view.ViewHierarchyEncoder;
import android.widget.RemoteViews.RemoteView;

import com.android.internal.R;

import java.util.ArrayList;

FrameLayout is designed to block out an area on the screen to display a single item. Generally, FrameLayout should be used to hold a single child view, because it can be difficult to organize child views in a way that's scalable to different screen sizes without the children overlapping each other. You can, however, add multiple children to a FrameLayout and control their position within the FrameLayout by assigning gravity to each child, using the android:layout_gravity attribute.

Child views are drawn in a stack, with the most recently added child on top. The size of the FrameLayout is the size of its largest child (plus padding), visible or not (if the FrameLayout's parent permits). Views that are View.GONE are used for sizing only if setConsiderGoneChildrenWhenMeasuring() is set to true.

@attrref android.R.styleable#FrameLayout_measureAllChildren
/** * FrameLayout is designed to block out an area on the screen to display * a single item. Generally, FrameLayout should be used to hold a single child view, because it can * be difficult to organize child views in a way that's scalable to different screen sizes without * the children overlapping each other. You can, however, add multiple children to a FrameLayout * and control their position within the FrameLayout by assigning gravity to each child, using the * <a href="FrameLayout.LayoutParams.html#attr_android:layout_gravity">{@code * android:layout_gravity}</a> attribute. * <p>Child views are drawn in a stack, with the most recently added child on top. * The size of the FrameLayout is the size of its largest child (plus padding), visible * or not (if the FrameLayout's parent permits). Views that are {@link android.view.View#GONE} are * used for sizing * only if {@link #setMeasureAllChildren(boolean) setConsiderGoneChildrenWhenMeasuring()} * is set to true. * * @attr ref android.R.styleable#FrameLayout_measureAllChildren */
@RemoteView public class FrameLayout extends ViewGroup { private static final int DEFAULT_CHILD_GRAVITY = Gravity.TOP | Gravity.START; @ViewDebug.ExportedProperty(category = "measurement") boolean mMeasureAllChildren = false; @ViewDebug.ExportedProperty(category = "padding") private int mForegroundPaddingLeft = 0; @ViewDebug.ExportedProperty(category = "padding") private int mForegroundPaddingTop = 0; @ViewDebug.ExportedProperty(category = "padding") private int mForegroundPaddingRight = 0; @ViewDebug.ExportedProperty(category = "padding") private int mForegroundPaddingBottom = 0; private final ArrayList<View> mMatchParentChildren = new ArrayList<>(1); public FrameLayout(@NonNull Context context) { super(context); } public FrameLayout(@NonNull Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } public FrameLayout(@NonNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) { this(context, attrs, defStyleAttr, 0); } public FrameLayout(@NonNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr, @StyleRes int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); final TypedArray a = context.obtainStyledAttributes( attrs, R.styleable.FrameLayout, defStyleAttr, defStyleRes); if (a.getBoolean(R.styleable.FrameLayout_measureAllChildren, false)) { setMeasureAllChildren(true); } a.recycle(); }
Describes how the foreground is positioned. Defaults to START and TOP.
Params:
See Also:
@attrref android.R.styleable#View_foregroundGravity
/** * Describes how the foreground is positioned. Defaults to START and TOP. * * @param foregroundGravity See {@link android.view.Gravity} * * @see #getForegroundGravity() * * @attr ref android.R.styleable#View_foregroundGravity */
@android.view.RemotableViewMethod public void setForegroundGravity(int foregroundGravity) { if (getForegroundGravity() != foregroundGravity) { super.setForegroundGravity(foregroundGravity); // calling get* again here because the set above may apply default constraints final Drawable foreground = getForeground(); if (getForegroundGravity() == Gravity.FILL && foreground != null) { Rect padding = new Rect(); if (foreground.getPadding(padding)) { mForegroundPaddingLeft = padding.left; mForegroundPaddingTop = padding.top; mForegroundPaddingRight = padding.right; mForegroundPaddingBottom = padding.bottom; } } else { mForegroundPaddingLeft = 0; mForegroundPaddingTop = 0; mForegroundPaddingRight = 0; mForegroundPaddingBottom = 0; } requestLayout(); } }
Returns a set of layout parameters with a width of LayoutParams.MATCH_PARENT, and a height of LayoutParams.MATCH_PARENT.
/** * Returns a set of layout parameters with a width of * {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT}, * and a height of {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT}. */
@Override protected LayoutParams generateDefaultLayoutParams() { return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); } int getPaddingLeftWithForeground() { return isForegroundInsidePadding() ? Math.max(mPaddingLeft, mForegroundPaddingLeft) : mPaddingLeft + mForegroundPaddingLeft; } int getPaddingRightWithForeground() { return isForegroundInsidePadding() ? Math.max(mPaddingRight, mForegroundPaddingRight) : mPaddingRight + mForegroundPaddingRight; } private int getPaddingTopWithForeground() { return isForegroundInsidePadding() ? Math.max(mPaddingTop, mForegroundPaddingTop) : mPaddingTop + mForegroundPaddingTop; } private int getPaddingBottomWithForeground() { return isForegroundInsidePadding() ? Math.max(mPaddingBottom, mForegroundPaddingBottom) : mPaddingBottom + mForegroundPaddingBottom; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int count = getChildCount(); final boolean measureMatchParentChildren = MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY || MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY; mMatchParentChildren.clear(); int maxHeight = 0; int maxWidth = 0; int childState = 0; for (int i = 0; i < count; i++) { final View child = getChildAt(i); if (mMeasureAllChildren || child.getVisibility() != GONE) { measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0); final LayoutParams lp = (LayoutParams) child.getLayoutParams(); maxWidth = Math.max(maxWidth, child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin); maxHeight = Math.max(maxHeight, child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin); childState = combineMeasuredStates(childState, child.getMeasuredState()); if (measureMatchParentChildren) { if (lp.width == LayoutParams.MATCH_PARENT || lp.height == LayoutParams.MATCH_PARENT) { mMatchParentChildren.add(child); } } } } // Account for padding too maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground(); maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground(); // Check against our minimum height and width maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight()); maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth()); // Check against our foreground's minimum height and width final Drawable drawable = getForeground(); if (drawable != null) { maxHeight = Math.max(maxHeight, drawable.getMinimumHeight()); maxWidth = Math.max(maxWidth, drawable.getMinimumWidth()); } setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState), resolveSizeAndState(maxHeight, heightMeasureSpec, childState << MEASURED_HEIGHT_STATE_SHIFT)); count = mMatchParentChildren.size(); if (count > 1) { for (int i = 0; i < count; i++) { final View child = mMatchParentChildren.get(i); final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); final int childWidthMeasureSpec; if (lp.width == LayoutParams.MATCH_PARENT) { final int width = Math.max(0, getMeasuredWidth() - getPaddingLeftWithForeground() - getPaddingRightWithForeground() - lp.leftMargin - lp.rightMargin); childWidthMeasureSpec = MeasureSpec.makeMeasureSpec( width, MeasureSpec.EXACTLY); } else { childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, getPaddingLeftWithForeground() + getPaddingRightWithForeground() + lp.leftMargin + lp.rightMargin, lp.width); } final int childHeightMeasureSpec; if (lp.height == LayoutParams.MATCH_PARENT) { final int height = Math.max(0, getMeasuredHeight() - getPaddingTopWithForeground() - getPaddingBottomWithForeground() - lp.topMargin - lp.bottomMargin); childHeightMeasureSpec = MeasureSpec.makeMeasureSpec( height, MeasureSpec.EXACTLY); } else { childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, getPaddingTopWithForeground() + getPaddingBottomWithForeground() + lp.topMargin + lp.bottomMargin, lp.height); } child.measure(childWidthMeasureSpec, childHeightMeasureSpec); } } } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { layoutChildren(left, top, right, bottom, false /* no force left gravity */); } void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) { final int count = getChildCount(); final int parentLeft = getPaddingLeftWithForeground(); final int parentRight = right - left - getPaddingRightWithForeground(); final int parentTop = getPaddingTopWithForeground(); final int parentBottom = bottom - top - getPaddingBottomWithForeground(); for (int i = 0; i < count; i++) { final View child = getChildAt(i); if (child.getVisibility() != GONE) { final LayoutParams lp = (LayoutParams) child.getLayoutParams(); final int width = child.getMeasuredWidth(); final int height = child.getMeasuredHeight(); int childLeft; int childTop; int gravity = lp.gravity; if (gravity == -1) { gravity = DEFAULT_CHILD_GRAVITY; } final int layoutDirection = getLayoutDirection(); final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection); final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK; switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { case Gravity.CENTER_HORIZONTAL: childLeft = parentLeft + (parentRight - parentLeft - width) / 2 + lp.leftMargin - lp.rightMargin; break; case Gravity.RIGHT: if (!forceLeftGravity) { childLeft = parentRight - width - lp.rightMargin; break; } case Gravity.LEFT: default: childLeft = parentLeft + lp.leftMargin; } switch (verticalGravity) { case Gravity.TOP: childTop = parentTop + lp.topMargin; break; case Gravity.CENTER_VERTICAL: childTop = parentTop + (parentBottom - parentTop - height) / 2 + lp.topMargin - lp.bottomMargin; break; case Gravity.BOTTOM: childTop = parentBottom - height - lp.bottomMargin; break; default: childTop = parentTop + lp.topMargin; } child.layout(childLeft, childTop, childLeft + width, childTop + height); } } }
Sets whether to consider all children, or just those in the VISIBLE or INVISIBLE state, when measuring. Defaults to false.
Params:
  • measureAll – true to consider children marked GONE, false otherwise. Default value is false.
@attrref android.R.styleable#FrameLayout_measureAllChildren
/** * Sets whether to consider all children, or just those in * the VISIBLE or INVISIBLE state, when measuring. Defaults to false. * * @param measureAll true to consider children marked GONE, false otherwise. * Default value is false. * * @attr ref android.R.styleable#FrameLayout_measureAllChildren */
@android.view.RemotableViewMethod public void setMeasureAllChildren(boolean measureAll) { mMeasureAllChildren = measureAll; }
Determines whether all children, or just those in the VISIBLE or INVISIBLE state, are considered when measuring.
Returns:Whether all children are considered when measuring.
Deprecated:This method is deprecated in favor of getMeasureAllChildren(), which was renamed for consistency with setMeasureAllChildren().
/** * Determines whether all children, or just those in the VISIBLE or * INVISIBLE state, are considered when measuring. * * @return Whether all children are considered when measuring. * * @deprecated This method is deprecated in favor of * {@link #getMeasureAllChildren() getMeasureAllChildren()}, which was * renamed for consistency with * {@link #setMeasureAllChildren(boolean) setMeasureAllChildren()}. */
@Deprecated public boolean getConsiderGoneChildrenWhenMeasuring() { return getMeasureAllChildren(); }
Determines whether all children, or just those in the VISIBLE or INVISIBLE state, are considered when measuring.
Returns:Whether all children are considered when measuring.
/** * Determines whether all children, or just those in the VISIBLE or * INVISIBLE state, are considered when measuring. * * @return Whether all children are considered when measuring. */
public boolean getMeasureAllChildren() { return mMeasureAllChildren; } @Override public LayoutParams generateLayoutParams(AttributeSet attrs) { return new FrameLayout.LayoutParams(getContext(), attrs); } @Override public boolean shouldDelayChildPressedState() { return false; } @Override protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { return p instanceof LayoutParams; } @Override protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) { if (sPreserveMarginParamsInLayoutParamConversion) { if (lp instanceof LayoutParams) { return new LayoutParams((LayoutParams) lp); } else if (lp instanceof MarginLayoutParams) { return new LayoutParams((MarginLayoutParams) lp); } } return new LayoutParams(lp); } @Override public CharSequence getAccessibilityClassName() { return FrameLayout.class.getName(); }
@hide
/** @hide */
@Override protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) { super.encodeProperties(encoder); encoder.addProperty("measurement:measureAllChildren", mMeasureAllChildren); encoder.addProperty("padding:foregroundPaddingLeft", mForegroundPaddingLeft); encoder.addProperty("padding:foregroundPaddingTop", mForegroundPaddingTop); encoder.addProperty("padding:foregroundPaddingRight", mForegroundPaddingRight); encoder.addProperty("padding:foregroundPaddingBottom", mForegroundPaddingBottom); }
Per-child layout information for layouts that support margins. See FrameLayout Layout Attributes for a list of all child view attributes that this class supports.
@attrref android.R.styleable#FrameLayout_Layout_layout_gravity
/** * Per-child layout information for layouts that support margins. * See {@link android.R.styleable#FrameLayout_Layout FrameLayout Layout Attributes} * for a list of all child view attributes that this class supports. * * @attr ref android.R.styleable#FrameLayout_Layout_layout_gravity */
public static class LayoutParams extends MarginLayoutParams {
Value for gravity indicating that a gravity has not been explicitly specified.
/** * Value for {@link #gravity} indicating that a gravity has not been * explicitly specified. */
public static final int UNSPECIFIED_GRAVITY = -1;
The gravity to apply with the View to which these layout parameters are associated.

The default value is UNSPECIFIED_GRAVITY, which is treated by FrameLayout as Gravity.TOP | Gravity.START.

See Also:
@attrref android.R.styleable#FrameLayout_Layout_layout_gravity
/** * The gravity to apply with the View to which these layout parameters * are associated. * <p> * The default value is {@link #UNSPECIFIED_GRAVITY}, which is treated * by FrameLayout as {@code Gravity.TOP | Gravity.START}. * * @see android.view.Gravity * @attr ref android.R.styleable#FrameLayout_Layout_layout_gravity */
public int gravity = UNSPECIFIED_GRAVITY; public LayoutParams(@NonNull Context c, @Nullable AttributeSet attrs) { super(c, attrs); final TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.FrameLayout_Layout); gravity = a.getInt(R.styleable.FrameLayout_Layout_layout_gravity, UNSPECIFIED_GRAVITY); a.recycle(); } public LayoutParams(int width, int height) { super(width, height); }
Creates a new set of layout parameters with the specified width, height and weight.
Params:
See Also:
/** * Creates a new set of layout parameters with the specified width, height * and weight. * * @param width the width, either {@link #MATCH_PARENT}, * {@link #WRAP_CONTENT} or a fixed size in pixels * @param height the height, either {@link #MATCH_PARENT}, * {@link #WRAP_CONTENT} or a fixed size in pixels * @param gravity the gravity * * @see android.view.Gravity */
public LayoutParams(int width, int height, int gravity) { super(width, height); this.gravity = gravity; } public LayoutParams(@NonNull ViewGroup.LayoutParams source) { super(source); } public LayoutParams(@NonNull ViewGroup.MarginLayoutParams source) { super(source); }
Copy constructor. Clones the width, height, margin values, and gravity of the source.
Params:
  • source – The layout params to copy from.
/** * Copy constructor. Clones the width, height, margin values, and * gravity of the source. * * @param source The layout params to copy from. */
public LayoutParams(@NonNull LayoutParams source) { super(source); this.gravity = source.gravity; } } }