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.START;
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.
126     *
127     * @return foreground gravity.
128     *
129     * @see #setForegroundGravity(int)
130     *
131     * @attr ref android.R.styleable#FrameLayout_foregroundGravity
132     */
133    public int getForegroundGravity() {
134        return mForegroundGravity;
135    }
136
137    /**
138     * Describes how the foreground is positioned. Defaults to START and TOP.
139     *
140     * @param foregroundGravity See {@link android.view.Gravity}
141     *
142     * @see #getForegroundGravity()
143     *
144     * @attr ref android.R.styleable#FrameLayout_foregroundGravity
145     */
146    @android.view.RemotableViewMethod
147    public void setForegroundGravity(int foregroundGravity) {
148        if (mForegroundGravity != foregroundGravity) {
149            if ((foregroundGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) == 0) {
150                foregroundGravity |= Gravity.START;
151            }
152
153            if ((foregroundGravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) {
154                foregroundGravity |= Gravity.TOP;
155            }
156
157            mForegroundGravity = foregroundGravity;
158
159
160            if (mForegroundGravity == Gravity.FILL && mForeground != null) {
161                Rect padding = new Rect();
162                if (mForeground.getPadding(padding)) {
163                    mForegroundPaddingLeft = padding.left;
164                    mForegroundPaddingTop = padding.top;
165                    mForegroundPaddingRight = padding.right;
166                    mForegroundPaddingBottom = padding.bottom;
167                }
168            } else {
169                mForegroundPaddingLeft = 0;
170                mForegroundPaddingTop = 0;
171                mForegroundPaddingRight = 0;
172                mForegroundPaddingBottom = 0;
173            }
174
175            requestLayout();
176        }
177    }
178
179    /**
180     * {@inheritDoc}
181     */
182    @Override
183    protected boolean verifyDrawable(Drawable who) {
184        return super.verifyDrawable(who) || (who == mForeground);
185    }
186
187    @Override
188    public void jumpDrawablesToCurrentState() {
189        super.jumpDrawablesToCurrentState();
190        if (mForeground != null) mForeground.jumpToCurrentState();
191    }
192
193    /**
194     * {@inheritDoc}
195     */
196    @Override
197    protected void drawableStateChanged() {
198        super.drawableStateChanged();
199        if (mForeground != null && mForeground.isStateful()) {
200            mForeground.setState(getDrawableState());
201        }
202    }
203
204    /**
205     * Returns a set of layout parameters with a width of
206     * {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT},
207     * and a height of {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT}.
208     */
209    @Override
210    protected LayoutParams generateDefaultLayoutParams() {
211        return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
212    }
213
214    /**
215     * Supply a Drawable that is to be rendered on top of all of the child
216     * views in the frame layout.  Any padding in the Drawable will be taken
217     * into account by ensuring that the children are inset to be placed
218     * inside of the padding area.
219     *
220     * @param drawable The Drawable to be drawn on top of the children.
221     *
222     * @attr ref android.R.styleable#FrameLayout_foreground
223     */
224    public void setForeground(Drawable drawable) {
225        if (mForeground != drawable) {
226            if (mForeground != null) {
227                mForeground.setCallback(null);
228                unscheduleDrawable(mForeground);
229            }
230
231            mForeground = drawable;
232            mForegroundPaddingLeft = 0;
233            mForegroundPaddingTop = 0;
234            mForegroundPaddingRight = 0;
235            mForegroundPaddingBottom = 0;
236
237            if (drawable != null) {
238                setWillNotDraw(false);
239                drawable.setCallback(this);
240                if (drawable.isStateful()) {
241                    drawable.setState(getDrawableState());
242                }
243                if (mForegroundGravity == Gravity.FILL) {
244                    Rect padding = new Rect();
245                    if (drawable.getPadding(padding)) {
246                        mForegroundPaddingLeft = padding.left;
247                        mForegroundPaddingTop = padding.top;
248                        mForegroundPaddingRight = padding.right;
249                        mForegroundPaddingBottom = padding.bottom;
250                    }
251                }
252            }  else {
253                setWillNotDraw(true);
254            }
255            requestLayout();
256            invalidate();
257        }
258    }
259
260    /**
261     * Returns the drawable used as the foreground of this FrameLayout. The
262     * foreground drawable, if non-null, is always drawn on top of the children.
263     *
264     * @return A Drawable or null if no foreground was set.
265     */
266    public Drawable getForeground() {
267        return mForeground;
268    }
269
270    int getPaddingLeftWithForeground() {
271        return mForegroundInPadding ? Math.max(mPaddingLeft, mForegroundPaddingLeft) :
272            mPaddingLeft + mForegroundPaddingLeft;
273    }
274
275    int getPaddingRightWithForeground() {
276        return mForegroundInPadding ? Math.max(mPaddingRight, mForegroundPaddingRight) :
277            mPaddingRight + mForegroundPaddingRight;
278    }
279
280    private int getPaddingTopWithForeground() {
281        return mForegroundInPadding ? Math.max(mPaddingTop, mForegroundPaddingTop) :
282            mPaddingTop + mForegroundPaddingTop;
283    }
284
285    private int getPaddingBottomWithForeground() {
286        return mForegroundInPadding ? Math.max(mPaddingBottom, mForegroundPaddingBottom) :
287            mPaddingBottom + mForegroundPaddingBottom;
288    }
289
290
291    /**
292     * {@inheritDoc}
293     */
294    @Override
295    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
296        int count = getChildCount();
297
298        final boolean measureMatchParentChildren =
299                MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
300                MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
301        mMatchParentChildren.clear();
302
303        int maxHeight = 0;
304        int maxWidth = 0;
305        int childState = 0;
306
307        for (int i = 0; i < count; i++) {
308            final View child = getChildAt(i);
309            if (mMeasureAllChildren || child.getVisibility() != GONE) {
310                measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
311                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
312                maxWidth = Math.max(maxWidth,
313                        child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
314                maxHeight = Math.max(maxHeight,
315                        child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
316                childState = combineMeasuredStates(childState, child.getMeasuredState());
317                if (measureMatchParentChildren) {
318                    if (lp.width == LayoutParams.MATCH_PARENT ||
319                            lp.height == LayoutParams.MATCH_PARENT) {
320                        mMatchParentChildren.add(child);
321                    }
322                }
323            }
324        }
325
326        // Account for padding too
327        maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
328        maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();
329
330        // Check against our minimum height and width
331        maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
332        maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
333
334        // Check against our foreground's minimum height and width
335        final Drawable drawable = getForeground();
336        if (drawable != null) {
337            maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
338            maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
339        }
340
341        setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
342                resolveSizeAndState(maxHeight, heightMeasureSpec,
343                        childState << MEASURED_HEIGHT_STATE_SHIFT));
344
345        count = mMatchParentChildren.size();
346        if (count > 1) {
347            for (int i = 0; i < count; i++) {
348                final View child = mMatchParentChildren.get(i);
349
350                final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
351                int childWidthMeasureSpec;
352                int childHeightMeasureSpec;
353
354                if (lp.width == LayoutParams.MATCH_PARENT) {
355                    childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth() -
356                            getPaddingLeftWithForeground() - getPaddingRightWithForeground() -
357                            lp.leftMargin - lp.rightMargin,
358                            MeasureSpec.EXACTLY);
359                } else {
360                    childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
361                            getPaddingLeftWithForeground() + getPaddingRightWithForeground() +
362                            lp.leftMargin + lp.rightMargin,
363                            lp.width);
364                }
365
366                if (lp.height == LayoutParams.MATCH_PARENT) {
367                    childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight() -
368                            getPaddingTopWithForeground() - getPaddingBottomWithForeground() -
369                            lp.topMargin - lp.bottomMargin,
370                            MeasureSpec.EXACTLY);
371                } else {
372                    childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
373                            getPaddingTopWithForeground() + getPaddingBottomWithForeground() +
374                            lp.topMargin + lp.bottomMargin,
375                            lp.height);
376                }
377
378                child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
379            }
380        }
381    }
382
383    /**
384     * {@inheritDoc}
385     */
386    @Override
387    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
388        layoutChildren(left, top, right, bottom, false /* no force left gravity */);
389    }
390
391    void layoutChildren(int left, int top, int right, int bottom,
392                                  boolean forceLeftGravity) {
393        final int count = getChildCount();
394
395        final int parentLeft = getPaddingLeftWithForeground();
396        final int parentRight = right - left - getPaddingRightWithForeground();
397
398        final int parentTop = getPaddingTopWithForeground();
399        final int parentBottom = bottom - top - getPaddingBottomWithForeground();
400
401        mForegroundBoundsChanged = true;
402
403        for (int i = 0; i < count; i++) {
404            final View child = getChildAt(i);
405            if (child.getVisibility() != GONE) {
406                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
407
408                final int width = child.getMeasuredWidth();
409                final int height = child.getMeasuredHeight();
410
411                int childLeft;
412                int childTop;
413
414                int gravity = lp.gravity;
415                if (gravity == -1) {
416                    gravity = DEFAULT_CHILD_GRAVITY;
417                }
418
419                final int layoutDirection = getLayoutDirection();
420                final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
421                final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
422
423                switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
424                    case Gravity.CENTER_HORIZONTAL:
425                        childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
426                        lp.leftMargin - lp.rightMargin;
427                        break;
428                    case Gravity.RIGHT:
429                        if (!forceLeftGravity) {
430                            childLeft = parentRight - width - lp.rightMargin;
431                            break;
432                        }
433                    case Gravity.LEFT:
434                    default:
435                        childLeft = parentLeft + lp.leftMargin;
436                }
437
438                switch (verticalGravity) {
439                    case Gravity.TOP:
440                        childTop = parentTop + lp.topMargin;
441                        break;
442                    case Gravity.CENTER_VERTICAL:
443                        childTop = parentTop + (parentBottom - parentTop - height) / 2 +
444                        lp.topMargin - lp.bottomMargin;
445                        break;
446                    case Gravity.BOTTOM:
447                        childTop = parentBottom - height - lp.bottomMargin;
448                        break;
449                    default:
450                        childTop = parentTop + lp.topMargin;
451                }
452
453                child.layout(childLeft, childTop, childLeft + width, childTop + height);
454            }
455        }
456    }
457
458    /**
459     * {@inheritDoc}
460     */
461    @Override
462    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
463        super.onSizeChanged(w, h, oldw, oldh);
464        mForegroundBoundsChanged = true;
465    }
466
467    /**
468     * {@inheritDoc}
469     */
470    @Override
471    public void draw(Canvas canvas) {
472        super.draw(canvas);
473
474        if (mForeground != null) {
475            final Drawable foreground = mForeground;
476
477            if (mForegroundBoundsChanged) {
478                mForegroundBoundsChanged = false;
479                final Rect selfBounds = mSelfBounds;
480                final Rect overlayBounds = mOverlayBounds;
481
482                final int w = mRight-mLeft;
483                final int h = mBottom-mTop;
484
485                if (mForegroundInPadding) {
486                    selfBounds.set(0, 0, w, h);
487                } else {
488                    selfBounds.set(mPaddingLeft, mPaddingTop, w - mPaddingRight, h - mPaddingBottom);
489                }
490
491                final int layoutDirection = getLayoutDirection();
492                Gravity.apply(mForegroundGravity, foreground.getIntrinsicWidth(),
493                        foreground.getIntrinsicHeight(), selfBounds, overlayBounds,
494                        layoutDirection);
495                foreground.setBounds(overlayBounds);
496            }
497
498            foreground.draw(canvas);
499        }
500    }
501
502    /**
503     * {@inheritDoc}
504     */
505    @Override
506    public boolean gatherTransparentRegion(Region region) {
507        boolean opaque = super.gatherTransparentRegion(region);
508        if (region != null && mForeground != null) {
509            applyDrawableToTransparentRegion(mForeground, region);
510        }
511        return opaque;
512    }
513
514    /**
515     * Sets whether to consider all children, or just those in
516     * the VISIBLE or INVISIBLE state, when measuring. Defaults to false.
517     *
518     * @param measureAll true to consider children marked GONE, false otherwise.
519     * Default value is false.
520     *
521     * @attr ref android.R.styleable#FrameLayout_measureAllChildren
522     */
523    @android.view.RemotableViewMethod
524    public void setMeasureAllChildren(boolean measureAll) {
525        mMeasureAllChildren = measureAll;
526    }
527
528    /**
529     * Determines whether all children, or just those in the VISIBLE or
530     * INVISIBLE state, are considered when measuring.
531     *
532     * @return Whether all children are considered when measuring.
533     *
534     * @deprecated This method is deprecated in favor of
535     * {@link #getMeasureAllChildren() getMeasureAllChildren()}, which was
536     * renamed for consistency with
537     * {@link #setMeasureAllChildren(boolean) setMeasureAllChildren()}.
538     */
539    @Deprecated
540    public boolean getConsiderGoneChildrenWhenMeasuring() {
541        return getMeasureAllChildren();
542    }
543
544    /**
545     * Determines whether all children, or just those in the VISIBLE or
546     * INVISIBLE state, are considered when measuring.
547     *
548     * @return Whether all children are considered when measuring.
549     */
550    public boolean getMeasureAllChildren() {
551        return mMeasureAllChildren;
552    }
553
554    /**
555     * {@inheritDoc}
556     */
557    @Override
558    public LayoutParams generateLayoutParams(AttributeSet attrs) {
559        return new FrameLayout.LayoutParams(getContext(), attrs);
560    }
561
562    @Override
563    public boolean shouldDelayChildPressedState() {
564        return false;
565    }
566
567    /**
568     * {@inheritDoc}
569     */
570    @Override
571    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
572        return p instanceof LayoutParams;
573    }
574
575    @Override
576    protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
577        return new LayoutParams(p);
578    }
579
580
581    @Override
582    public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
583        super.onInitializeAccessibilityEvent(event);
584        event.setClassName(FrameLayout.class.getName());
585    }
586
587    @Override
588    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
589        super.onInitializeAccessibilityNodeInfo(info);
590        info.setClassName(FrameLayout.class.getName());
591    }
592
593    /**
594     * Per-child layout information for layouts that support margins.
595     * See {@link android.R.styleable#FrameLayout_Layout FrameLayout Layout Attributes}
596     * for a list of all child view attributes that this class supports.
597     *
598     * @attr ref android.R.styleable#FrameLayout_Layout_layout_gravity
599     */
600    public static class LayoutParams extends MarginLayoutParams {
601        /**
602         * The gravity to apply with the View to which these layout parameters
603         * are associated.
604         *
605         * @see android.view.Gravity
606         *
607         * @attr ref android.R.styleable#FrameLayout_Layout_layout_gravity
608         */
609        public int gravity = -1;
610
611        /**
612         * {@inheritDoc}
613         */
614        public LayoutParams(Context c, AttributeSet attrs) {
615            super(c, attrs);
616
617            TypedArray a = c.obtainStyledAttributes(attrs, com.android.internal.R.styleable.FrameLayout_Layout);
618            gravity = a.getInt(com.android.internal.R.styleable.FrameLayout_Layout_layout_gravity, -1);
619            a.recycle();
620        }
621
622        /**
623         * {@inheritDoc}
624         */
625        public LayoutParams(int width, int height) {
626            super(width, height);
627        }
628
629        /**
630         * Creates a new set of layout parameters with the specified width, height
631         * and weight.
632         *
633         * @param width the width, either {@link #MATCH_PARENT},
634         *        {@link #WRAP_CONTENT} or a fixed size in pixels
635         * @param height the height, either {@link #MATCH_PARENT},
636         *        {@link #WRAP_CONTENT} or a fixed size in pixels
637         * @param gravity the gravity
638         *
639         * @see android.view.Gravity
640         */
641        public LayoutParams(int width, int height, int gravity) {
642            super(width, height);
643            this.gravity = gravity;
644        }
645
646        /**
647         * {@inheritDoc}
648         */
649        public LayoutParams(ViewGroup.LayoutParams source) {
650            super(source);
651        }
652
653        /**
654         * {@inheritDoc}
655         */
656        public LayoutParams(ViewGroup.MarginLayoutParams source) {
657            super(source);
658        }
659
660        /**
661         * Copy constructor. Clones the width, height, margin values, and
662         * gravity of the source.
663         *
664         * @param source The layout params to copy from.
665         */
666        public LayoutParams(LayoutParams source) {
667            super(source);
668
669            this.gravity = source.gravity;
670        }
671    }
672}
673