Home | History | Annotate | Download | only in widget
      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.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 
     84     public FrameLayout(@NonNull Context context) {
     85         super(context);
     86     }
     87 
     88     public FrameLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
     89         this(context, attrs, 0);
     90     }
     91 
     92     public FrameLayout(@NonNull Context context, @Nullable AttributeSet attrs,
     93             @AttrRes int defStyleAttr) {
     94         this(context, attrs, defStyleAttr, 0);
     95     }
     96 
     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
    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
    154     protected LayoutParams generateDefaultLayoutParams() {
    155         return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
    156     }
    157 
    158     int getPaddingLeftWithForeground() {
    159         return isForegroundInsidePadding() ? Math.max(mPaddingLeft, mForegroundPaddingLeft) :
    160             mPaddingLeft + mForegroundPaddingLeft;
    161     }
    162 
    163     int getPaddingRightWithForeground() {
    164         return isForegroundInsidePadding() ? Math.max(mPaddingRight, mForegroundPaddingRight) :
    165             mPaddingRight + mForegroundPaddingRight;
    166     }
    167 
    168     private int getPaddingTopWithForeground() {
    169         return isForegroundInsidePadding() ? Math.max(mPaddingTop, mForegroundPaddingTop) :
    170             mPaddingTop + mForegroundPaddingTop;
    171     }
    172 
    173     private int getPaddingBottomWithForeground() {
    174         return isForegroundInsidePadding() ? Math.max(mPaddingBottom, mForegroundPaddingBottom) :
    175             mPaddingBottom + mForegroundPaddingBottom;
    176     }
    177 
    178     @Override
    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
    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 
    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
    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
    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
    374     public boolean getMeasureAllChildren() {
    375         return mMeasureAllChildren;
    376     }
    377 
    378     @Override
    379     public LayoutParams generateLayoutParams(AttributeSet attrs) {
    380         return new FrameLayout.LayoutParams(getContext(), attrs);
    381     }
    382 
    383     @Override
    384     public boolean shouldDelayChildPressedState() {
    385         return false;
    386     }
    387 
    388     @Override
    389     protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
    390         return p instanceof LayoutParams;
    391     }
    392 
    393     @Override
    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
    406     public CharSequence getAccessibilityClassName() {
    407         return FrameLayout.class.getName();
    408     }
    409 
    410     /** @hide */
    411     @Override
    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 
    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 
    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          */
    475         public LayoutParams(int width, int height, int gravity) {
    476             super(width, height);
    477             this.gravity = gravity;
    478         }
    479 
    480         public LayoutParams(@NonNull ViewGroup.LayoutParams source) {
    481             super(source);
    482         }
    483 
    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          */
    494         public LayoutParams(@NonNull LayoutParams source) {
    495             super(source);
    496 
    497             this.gravity = source.gravity;
    498         }
    499     }
    500 }
    501