|  | /* | 
|  | * 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.compat.annotation.UnsupportedAppUsage; | 
|  | 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.view.inspector.InspectableProperty; | 
|  | 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 | 
|  | * <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") | 
|  | @UnsupportedAppUsage | 
|  | boolean mMeasureAllChildren = false; | 
|  |  | 
|  | @ViewDebug.ExportedProperty(category = "padding") | 
|  | @UnsupportedAppUsage | 
|  | private int mForegroundPaddingLeft = 0; | 
|  |  | 
|  | @ViewDebug.ExportedProperty(category = "padding") | 
|  | @UnsupportedAppUsage | 
|  | private int mForegroundPaddingTop = 0; | 
|  |  | 
|  | @ViewDebug.ExportedProperty(category = "padding") | 
|  | @UnsupportedAppUsage | 
|  | private int mForegroundPaddingRight = 0; | 
|  |  | 
|  | @ViewDebug.ExportedProperty(category = "padding") | 
|  | @UnsupportedAppUsage | 
|  | 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); | 
|  | saveAttributeDataForStyleable(context, R.styleable.FrameLayout, | 
|  | attrs, a, 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. | 
|  | * | 
|  | * @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 | 
|  | * {@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. | 
|  | * | 
|  | * @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. | 
|  | * | 
|  | * @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. | 
|  | * | 
|  | * @return Whether all children are considered when measuring. | 
|  | */ | 
|  | @InspectableProperty | 
|  | 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 */ | 
|  | @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 {@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 {@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. | 
|  | * <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 | 
|  | */ | 
|  | @InspectableProperty( | 
|  | name = "layout_gravity", | 
|  | valueType = InspectableProperty.ValueType.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. | 
|  | * | 
|  | * @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. | 
|  | * | 
|  | * @param source The layout params to copy from. | 
|  | */ | 
|  | public LayoutParams(@NonNull LayoutParams source) { | 
|  | super(source); | 
|  |  | 
|  | this.gravity = source.gravity; | 
|  | } | 
|  | } | 
|  | } |