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