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