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