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 java.util.ArrayList;
     20 
     21 import android.content.Context;
     22 import android.content.res.TypedArray;
     23 import android.graphics.Canvas;
     24 import android.graphics.Rect;
     25 import android.graphics.Region;
     26 import android.graphics.drawable.Drawable;
     27 import android.util.AttributeSet;
     28 import android.view.Gravity;
     29 import android.view.View;
     30 import android.view.ViewDebug;
     31 import android.view.ViewGroup;
     32 import android.widget.RemoteViews.RemoteView;
     33 
     34 
     35 /**
     36  * FrameLayout is designed to block out an area on the screen to display
     37  * a single item. Generally, FrameLayout should be used to hold a single child view, because it can
     38  * be difficult to organize child views in a way that's scalable to different screen sizes without
     39  * the children overlapping each other. You can, however, add multiple children to a FrameLayout
     40  * and control their position within the FrameLayout by assigning gravity to each child, using the
     41  * <a href="FrameLayout.LayoutParams.html#attr_android:layout_gravity">{@code
     42  * android:layout_gravity}</a> attribute.
     43  * <p>Child views are drawn in a stack, with the most recently added child on top.
     44  * The size of the FrameLayout is the size of its largest child (plus padding), visible
     45  * or not (if the FrameLayout's parent permits). Views that are {@link android.view.View#GONE} are
     46  * used for sizing
     47  * only if {@link #setMeasureAllChildren(boolean) setConsiderGoneChildrenWhenMeasuring()}
     48  * is set to true.
     49  *
     50  * @attr ref android.R.styleable#FrameLayout_foreground
     51  * @attr ref android.R.styleable#FrameLayout_foregroundGravity
     52  * @attr ref android.R.styleable#FrameLayout_measureAllChildren
     53  */
     54 @RemoteView
     55 public class FrameLayout extends ViewGroup {
     56     private static final int DEFAULT_CHILD_GRAVITY = Gravity.TOP | Gravity.LEFT;
     57 
     58     @ViewDebug.ExportedProperty(category = "measurement")
     59     boolean mMeasureAllChildren = false;
     60 
     61     @ViewDebug.ExportedProperty(category = "drawing")
     62     private Drawable mForeground;
     63 
     64     @ViewDebug.ExportedProperty(category = "padding")
     65     private int mForegroundPaddingLeft = 0;
     66 
     67     @ViewDebug.ExportedProperty(category = "padding")
     68     private int mForegroundPaddingTop = 0;
     69 
     70     @ViewDebug.ExportedProperty(category = "padding")
     71     private int mForegroundPaddingRight = 0;
     72 
     73     @ViewDebug.ExportedProperty(category = "padding")
     74     private int mForegroundPaddingBottom = 0;
     75 
     76     private final Rect mSelfBounds = new Rect();
     77     private final Rect mOverlayBounds = new Rect();
     78 
     79     @ViewDebug.ExportedProperty(category = "drawing")
     80     private int mForegroundGravity = Gravity.FILL;
     81 
     82     /** {@hide} */
     83     @ViewDebug.ExportedProperty(category = "drawing")
     84     protected boolean mForegroundInPadding = true;
     85 
     86     boolean mForegroundBoundsChanged = false;
     87 
     88     private final ArrayList<View> mMatchParentChildren = new ArrayList<View>(1);
     89 
     90     public FrameLayout(Context context) {
     91         super(context);
     92     }
     93 
     94     public FrameLayout(Context context, AttributeSet attrs) {
     95         this(context, attrs, 0);
     96     }
     97 
     98     public FrameLayout(Context context, AttributeSet attrs, int defStyle) {
     99         super(context, attrs, defStyle);
    100 
    101         TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.FrameLayout,
    102                     defStyle, 0);
    103 
    104         mForegroundGravity = a.getInt(
    105                 com.android.internal.R.styleable.FrameLayout_foregroundGravity, mForegroundGravity);
    106 
    107         final Drawable d = a.getDrawable(com.android.internal.R.styleable.FrameLayout_foreground);
    108         if (d != null) {
    109             setForeground(d);
    110         }
    111 
    112         if (a.getBoolean(com.android.internal.R.styleable.FrameLayout_measureAllChildren, false)) {
    113             setMeasureAllChildren(true);
    114         }
    115 
    116         mForegroundInPadding = a.getBoolean(
    117                 com.android.internal.R.styleable.FrameLayout_foregroundInsidePadding, true);
    118 
    119         a.recycle();
    120     }
    121 
    122     /**
    123      * Describes how the foreground is positioned. Defaults to START and TOP.
    124      *
    125      * @param foregroundGravity See {@link android.view.Gravity}
    126      *
    127      * @attr ref android.R.styleable#FrameLayout_foregroundGravity
    128      */
    129     @android.view.RemotableViewMethod
    130     public void setForegroundGravity(int foregroundGravity) {
    131         if (mForegroundGravity != foregroundGravity) {
    132             if ((foregroundGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) == 0) {
    133                 foregroundGravity |= Gravity.START;
    134             }
    135 
    136             if ((foregroundGravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) {
    137                 foregroundGravity |= Gravity.TOP;
    138             }
    139 
    140             mForegroundGravity = foregroundGravity;
    141 
    142 
    143             if (mForegroundGravity == Gravity.FILL && mForeground != null) {
    144                 Rect padding = new Rect();
    145                 if (mForeground.getPadding(padding)) {
    146                     mForegroundPaddingLeft = padding.left;
    147                     mForegroundPaddingTop = padding.top;
    148                     mForegroundPaddingRight = padding.right;
    149                     mForegroundPaddingBottom = padding.bottom;
    150                 }
    151             } else {
    152                 mForegroundPaddingLeft = 0;
    153                 mForegroundPaddingTop = 0;
    154                 mForegroundPaddingRight = 0;
    155                 mForegroundPaddingBottom = 0;
    156             }
    157 
    158             requestLayout();
    159         }
    160     }
    161 
    162     /**
    163      * {@inheritDoc}
    164      */
    165     @Override
    166     protected boolean verifyDrawable(Drawable who) {
    167         return super.verifyDrawable(who) || (who == mForeground);
    168     }
    169 
    170     @Override
    171     public void jumpDrawablesToCurrentState() {
    172         super.jumpDrawablesToCurrentState();
    173         if (mForeground != null) mForeground.jumpToCurrentState();
    174     }
    175 
    176     /**
    177      * {@inheritDoc}
    178      */
    179     @Override
    180     protected void drawableStateChanged() {
    181         super.drawableStateChanged();
    182         if (mForeground != null && mForeground.isStateful()) {
    183             mForeground.setState(getDrawableState());
    184         }
    185     }
    186 
    187     /**
    188      * Returns a set of layout parameters with a width of
    189      * {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT},
    190      * and a height of {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT}.
    191      */
    192     @Override
    193     protected LayoutParams generateDefaultLayoutParams() {
    194         return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
    195     }
    196 
    197     /**
    198      * Supply a Drawable that is to be rendered on top of all of the child
    199      * views in the frame layout.  Any padding in the Drawable will be taken
    200      * into account by ensuring that the children are inset to be placed
    201      * inside of the padding area.
    202      *
    203      * @param drawable The Drawable to be drawn on top of the children.
    204      *
    205      * @attr ref android.R.styleable#FrameLayout_foreground
    206      */
    207     public void setForeground(Drawable drawable) {
    208         if (mForeground != drawable) {
    209             if (mForeground != null) {
    210                 mForeground.setCallback(null);
    211                 unscheduleDrawable(mForeground);
    212             }
    213 
    214             mForeground = drawable;
    215             mForegroundPaddingLeft = 0;
    216             mForegroundPaddingTop = 0;
    217             mForegroundPaddingRight = 0;
    218             mForegroundPaddingBottom = 0;
    219 
    220             if (drawable != null) {
    221                 setWillNotDraw(false);
    222                 drawable.setCallback(this);
    223                 if (drawable.isStateful()) {
    224                     drawable.setState(getDrawableState());
    225                 }
    226                 if (mForegroundGravity == Gravity.FILL) {
    227                     Rect padding = new Rect();
    228                     if (drawable.getPadding(padding)) {
    229                         mForegroundPaddingLeft = padding.left;
    230                         mForegroundPaddingTop = padding.top;
    231                         mForegroundPaddingRight = padding.right;
    232                         mForegroundPaddingBottom = padding.bottom;
    233                     }
    234                 }
    235             }  else {
    236                 setWillNotDraw(true);
    237             }
    238             requestLayout();
    239             invalidate();
    240         }
    241     }
    242 
    243     /**
    244      * Returns the drawable used as the foreground of this FrameLayout. The
    245      * foreground drawable, if non-null, is always drawn on top of the children.
    246      *
    247      * @return A Drawable or null if no foreground was set.
    248      */
    249     public Drawable getForeground() {
    250         return mForeground;
    251     }
    252 
    253     private int getPaddingLeftWithForeground() {
    254         return mForegroundInPadding ? Math.max(mPaddingLeft, mForegroundPaddingLeft) :
    255             mPaddingLeft + mForegroundPaddingLeft;
    256     }
    257 
    258     private int getPaddingRightWithForeground() {
    259         return mForegroundInPadding ? Math.max(mPaddingRight, mForegroundPaddingRight) :
    260             mPaddingRight + mForegroundPaddingRight;
    261     }
    262 
    263     private int getPaddingTopWithForeground() {
    264         return mForegroundInPadding ? Math.max(mPaddingTop, mForegroundPaddingTop) :
    265             mPaddingTop + mForegroundPaddingTop;
    266     }
    267 
    268     private int getPaddingBottomWithForeground() {
    269         return mForegroundInPadding ? Math.max(mPaddingBottom, mForegroundPaddingBottom) :
    270             mPaddingBottom + mForegroundPaddingBottom;
    271     }
    272 
    273 
    274     /**
    275      * {@inheritDoc}
    276      */
    277     @Override
    278     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    279         int count = getChildCount();
    280 
    281         final boolean measureMatchParentChildren =
    282                 MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
    283                 MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
    284         mMatchParentChildren.clear();
    285 
    286         int maxHeight = 0;
    287         int maxWidth = 0;
    288         int childState = 0;
    289 
    290         for (int i = 0; i < count; i++) {
    291             final View child = getChildAt(i);
    292             if (mMeasureAllChildren || child.getVisibility() != GONE) {
    293                 measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
    294                 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
    295                 maxWidth = Math.max(maxWidth,
    296                         child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
    297                 maxHeight = Math.max(maxHeight,
    298                         child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
    299                 childState = combineMeasuredStates(childState, child.getMeasuredState());
    300                 if (measureMatchParentChildren) {
    301                     if (lp.width == LayoutParams.MATCH_PARENT ||
    302                             lp.height == LayoutParams.MATCH_PARENT) {
    303                         mMatchParentChildren.add(child);
    304                     }
    305                 }
    306             }
    307         }
    308 
    309         // Account for padding too
    310         maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
    311         maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();
    312 
    313         // Check against our minimum height and width
    314         maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
    315         maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
    316 
    317         // Check against our foreground's minimum height and width
    318         final Drawable drawable = getForeground();
    319         if (drawable != null) {
    320             maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
    321             maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
    322         }
    323 
    324         setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
    325                 resolveSizeAndState(maxHeight, heightMeasureSpec,
    326                         childState << MEASURED_HEIGHT_STATE_SHIFT));
    327 
    328         count = mMatchParentChildren.size();
    329         if (count > 1) {
    330             for (int i = 0; i < count; i++) {
    331                 final View child = mMatchParentChildren.get(i);
    332 
    333                 final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
    334                 int childWidthMeasureSpec;
    335                 int childHeightMeasureSpec;
    336 
    337                 if (lp.width == LayoutParams.MATCH_PARENT) {
    338                     childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth() -
    339                             getPaddingLeftWithForeground() - getPaddingRightWithForeground() -
    340                             lp.leftMargin - lp.rightMargin,
    341                             MeasureSpec.EXACTLY);
    342                 } else {
    343                     childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
    344                             getPaddingLeftWithForeground() + getPaddingRightWithForeground() +
    345                             lp.leftMargin + lp.rightMargin,
    346                             lp.width);
    347                 }
    348 
    349                 if (lp.height == LayoutParams.MATCH_PARENT) {
    350                     childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight() -
    351                             getPaddingTopWithForeground() - getPaddingBottomWithForeground() -
    352                             lp.topMargin - lp.bottomMargin,
    353                             MeasureSpec.EXACTLY);
    354                 } else {
    355                     childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
    356                             getPaddingTopWithForeground() + getPaddingBottomWithForeground() +
    357                             lp.topMargin + lp.bottomMargin,
    358                             lp.height);
    359                 }
    360 
    361                 child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    362             }
    363         }
    364     }
    365 
    366     /**
    367      * {@inheritDoc}
    368      */
    369     @Override
    370     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    371         final int count = getChildCount();
    372 
    373         final int parentLeft = getPaddingLeftWithForeground();
    374         final int parentRight = right - left - getPaddingRightWithForeground();
    375 
    376         final int parentTop = getPaddingTopWithForeground();
    377         final int parentBottom = bottom - top - getPaddingBottomWithForeground();
    378 
    379         mForegroundBoundsChanged = true;
    380 
    381         for (int i = 0; i < count; i++) {
    382             final View child = getChildAt(i);
    383             if (child.getVisibility() != GONE) {
    384                 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
    385 
    386                 final int width = child.getMeasuredWidth();
    387                 final int height = child.getMeasuredHeight();
    388 
    389                 int childLeft;
    390                 int childTop;
    391 
    392                 int gravity = lp.gravity;
    393                 if (gravity == -1) {
    394                     gravity = DEFAULT_CHILD_GRAVITY;
    395                 }
    396 
    397                 final int layoutDirection = getResolvedLayoutDirection();
    398                 final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
    399                 final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
    400 
    401                 switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
    402                     case Gravity.LEFT:
    403                         childLeft = parentLeft + lp.leftMargin;
    404                         break;
    405                     case Gravity.CENTER_HORIZONTAL:
    406                         childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
    407                         lp.leftMargin - lp.rightMargin;
    408                         break;
    409                     case Gravity.RIGHT:
    410                         childLeft = parentRight - width - lp.rightMargin;
    411                         break;
    412                     default:
    413                         childLeft = parentLeft + lp.leftMargin;
    414                 }
    415 
    416                 switch (verticalGravity) {
    417                     case Gravity.TOP:
    418                         childTop = parentTop + lp.topMargin;
    419                         break;
    420                     case Gravity.CENTER_VERTICAL:
    421                         childTop = parentTop + (parentBottom - parentTop - height) / 2 +
    422                         lp.topMargin - lp.bottomMargin;
    423                         break;
    424                     case Gravity.BOTTOM:
    425                         childTop = parentBottom - height - lp.bottomMargin;
    426                         break;
    427                     default:
    428                         childTop = parentTop + lp.topMargin;
    429                 }
    430 
    431                 child.layout(childLeft, childTop, childLeft + width, childTop + height);
    432             }
    433         }
    434     }
    435 
    436     /**
    437      * {@inheritDoc}
    438      */
    439     @Override
    440     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    441         super.onSizeChanged(w, h, oldw, oldh);
    442         mForegroundBoundsChanged = true;
    443     }
    444 
    445     /**
    446      * {@inheritDoc}
    447      */
    448     @Override
    449     public void draw(Canvas canvas) {
    450         super.draw(canvas);
    451 
    452         if (mForeground != null) {
    453             final Drawable foreground = mForeground;
    454 
    455             if (mForegroundBoundsChanged) {
    456                 mForegroundBoundsChanged = false;
    457                 final Rect selfBounds = mSelfBounds;
    458                 final Rect overlayBounds = mOverlayBounds;
    459 
    460                 final int w = mRight-mLeft;
    461                 final int h = mBottom-mTop;
    462 
    463                 if (mForegroundInPadding) {
    464                     selfBounds.set(0, 0, w, h);
    465                 } else {
    466                     selfBounds.set(mPaddingLeft, mPaddingTop, w - mPaddingRight, h - mPaddingBottom);
    467                 }
    468 
    469                 final int layoutDirection = getResolvedLayoutDirection();
    470                 Gravity.apply(mForegroundGravity, foreground.getIntrinsicWidth(),
    471                         foreground.getIntrinsicHeight(), selfBounds, overlayBounds,
    472                         layoutDirection);
    473                 foreground.setBounds(overlayBounds);
    474             }
    475 
    476             foreground.draw(canvas);
    477         }
    478     }
    479 
    480     /**
    481      * {@inheritDoc}
    482      */
    483     @Override
    484     public boolean gatherTransparentRegion(Region region) {
    485         boolean opaque = super.gatherTransparentRegion(region);
    486         if (region != null && mForeground != null) {
    487             applyDrawableToTransparentRegion(mForeground, region);
    488         }
    489         return opaque;
    490     }
    491 
    492     /**
    493      * Sets whether to consider all children, or just those in
    494      * the VISIBLE or INVISIBLE state, when measuring. Defaults to false.
    495      *
    496      * @param measureAll true to consider children marked GONE, false otherwise.
    497      * Default value is false.
    498      *
    499      * @attr ref android.R.styleable#FrameLayout_measureAllChildren
    500      */
    501     @android.view.RemotableViewMethod
    502     public void setMeasureAllChildren(boolean measureAll) {
    503         mMeasureAllChildren = measureAll;
    504     }
    505 
    506     /**
    507      * Determines whether all children, or just those in the VISIBLE or
    508      * INVISIBLE state, are considered when measuring.
    509      *
    510      * @return Whether all children are considered when measuring.
    511      *
    512      * @deprecated This method is deprecated in favor of
    513      * {@link #getMeasureAllChildren() getMeasureAllChildren()}, which was
    514      * renamed for consistency with
    515      * {@link #setMeasureAllChildren(boolean) setMeasureAllChildren()}.
    516      */
    517     @Deprecated
    518     public boolean getConsiderGoneChildrenWhenMeasuring() {
    519         return getMeasureAllChildren();
    520     }
    521 
    522     /**
    523      * Determines whether all children, or just those in the VISIBLE or
    524      * INVISIBLE state, are considered when measuring.
    525      *
    526      * @return Whether all children are considered when measuring.
    527      */
    528     public boolean getMeasureAllChildren() {
    529         return mMeasureAllChildren;
    530     }
    531 
    532     /**
    533      * {@inheritDoc}
    534      */
    535     @Override
    536     public LayoutParams generateLayoutParams(AttributeSet attrs) {
    537         return new FrameLayout.LayoutParams(getContext(), attrs);
    538     }
    539 
    540     @Override
    541     public boolean shouldDelayChildPressedState() {
    542         return false;
    543     }
    544 
    545     /**
    546      * {@inheritDoc}
    547      */
    548     @Override
    549     protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
    550         return p instanceof LayoutParams;
    551     }
    552 
    553     @Override
    554     protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
    555         return new LayoutParams(p);
    556     }
    557 
    558     /**
    559      * Per-child layout information for layouts that support margins.
    560      * See {@link android.R.styleable#FrameLayout_Layout FrameLayout Layout Attributes}
    561      * for a list of all child view attributes that this class supports.
    562      *
    563      * @attr ref android.R.styleable#FrameLayout_Layout_layout_gravity
    564      */
    565     public static class LayoutParams extends MarginLayoutParams {
    566         /**
    567          * The gravity to apply with the View to which these layout parameters
    568          * are associated.
    569          *
    570          * @see android.view.Gravity
    571          *
    572          * @attr ref android.R.styleable#FrameLayout_Layout_layout_gravity
    573          */
    574         public int gravity = -1;
    575 
    576         /**
    577          * {@inheritDoc}
    578          */
    579         public LayoutParams(Context c, AttributeSet attrs) {
    580             super(c, attrs);
    581 
    582             TypedArray a = c.obtainStyledAttributes(attrs, com.android.internal.R.styleable.FrameLayout_Layout);
    583             gravity = a.getInt(com.android.internal.R.styleable.FrameLayout_Layout_layout_gravity, -1);
    584             a.recycle();
    585         }
    586 
    587         /**
    588          * {@inheritDoc}
    589          */
    590         public LayoutParams(int width, int height) {
    591             super(width, height);
    592         }
    593 
    594         /**
    595          * Creates a new set of layout parameters with the specified width, height
    596          * and weight.
    597          *
    598          * @param width the width, either {@link #MATCH_PARENT},
    599          *        {@link #WRAP_CONTENT} or a fixed size in pixels
    600          * @param height the height, either {@link #MATCH_PARENT},
    601          *        {@link #WRAP_CONTENT} or a fixed size in pixels
    602          * @param gravity the gravity
    603          *
    604          * @see android.view.Gravity
    605          */
    606         public LayoutParams(int width, int height, int gravity) {
    607             super(width, height);
    608             this.gravity = gravity;
    609         }
    610 
    611         /**
    612          * {@inheritDoc}
    613          */
    614         public LayoutParams(ViewGroup.LayoutParams source) {
    615             super(source);
    616         }
    617 
    618         /**
    619          * {@inheritDoc}
    620          */
    621         public LayoutParams(ViewGroup.MarginLayoutParams source) {
    622             super(source);
    623         }
    624     }
    625 }
    626