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