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    private int getPaddingLeftWithForeground() {
271        return mForegroundInPadding ? Math.max(mPaddingLeft, mForegroundPaddingLeft) :
272            mPaddingLeft + mForegroundPaddingLeft;
273    }
274
275    private 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        final int count = getChildCount();
389
390        final int parentLeft = getPaddingLeftWithForeground();
391        final int parentRight = right - left - getPaddingRightWithForeground();
392
393        final int parentTop = getPaddingTopWithForeground();
394        final int parentBottom = bottom - top - getPaddingBottomWithForeground();
395
396        mForegroundBoundsChanged = true;
397
398        for (int i = 0; i < count; i++) {
399            final View child = getChildAt(i);
400            if (child.getVisibility() != GONE) {
401                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
402
403                final int width = child.getMeasuredWidth();
404                final int height = child.getMeasuredHeight();
405
406                int childLeft;
407                int childTop;
408
409                int gravity = lp.gravity;
410                if (gravity == -1) {
411                    gravity = DEFAULT_CHILD_GRAVITY;
412                }
413
414                final int layoutDirection = getLayoutDirection();
415                final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
416                final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
417
418                switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
419                    case Gravity.LEFT:
420                        childLeft = parentLeft + lp.leftMargin;
421                        break;
422                    case Gravity.CENTER_HORIZONTAL:
423                        childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
424                        lp.leftMargin - lp.rightMargin;
425                        break;
426                    case Gravity.RIGHT:
427                        childLeft = parentRight - width - lp.rightMargin;
428                        break;
429                    default:
430                        childLeft = parentLeft + lp.leftMargin;
431                }
432
433                switch (verticalGravity) {
434                    case Gravity.TOP:
435                        childTop = parentTop + lp.topMargin;
436                        break;
437                    case Gravity.CENTER_VERTICAL:
438                        childTop = parentTop + (parentBottom - parentTop - height) / 2 +
439                        lp.topMargin - lp.bottomMargin;
440                        break;
441                    case Gravity.BOTTOM:
442                        childTop = parentBottom - height - lp.bottomMargin;
443                        break;
444                    default:
445                        childTop = parentTop + lp.topMargin;
446                }
447
448                child.layout(childLeft, childTop, childLeft + width, childTop + height);
449            }
450        }
451    }
452
453    /**
454     * {@inheritDoc}
455     */
456    @Override
457    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
458        super.onSizeChanged(w, h, oldw, oldh);
459        mForegroundBoundsChanged = true;
460    }
461
462    /**
463     * {@inheritDoc}
464     */
465    @Override
466    public void draw(Canvas canvas) {
467        super.draw(canvas);
468
469        if (mForeground != null) {
470            final Drawable foreground = mForeground;
471
472            if (mForegroundBoundsChanged) {
473                mForegroundBoundsChanged = false;
474                final Rect selfBounds = mSelfBounds;
475                final Rect overlayBounds = mOverlayBounds;
476
477                final int w = mRight-mLeft;
478                final int h = mBottom-mTop;
479
480                if (mForegroundInPadding) {
481                    selfBounds.set(0, 0, w, h);
482                } else {
483                    selfBounds.set(mPaddingLeft, mPaddingTop, w - mPaddingRight, h - mPaddingBottom);
484                }
485
486                final int layoutDirection = getLayoutDirection();
487                Gravity.apply(mForegroundGravity, foreground.getIntrinsicWidth(),
488                        foreground.getIntrinsicHeight(), selfBounds, overlayBounds,
489                        layoutDirection);
490                foreground.setBounds(overlayBounds);
491            }
492
493            foreground.draw(canvas);
494        }
495    }
496
497    /**
498     * {@inheritDoc}
499     */
500    @Override
501    public boolean gatherTransparentRegion(Region region) {
502        boolean opaque = super.gatherTransparentRegion(region);
503        if (region != null && mForeground != null) {
504            applyDrawableToTransparentRegion(mForeground, region);
505        }
506        return opaque;
507    }
508
509    /**
510     * Sets whether to consider all children, or just those in
511     * the VISIBLE or INVISIBLE state, when measuring. Defaults to false.
512     *
513     * @param measureAll true to consider children marked GONE, false otherwise.
514     * Default value is false.
515     *
516     * @attr ref android.R.styleable#FrameLayout_measureAllChildren
517     */
518    @android.view.RemotableViewMethod
519    public void setMeasureAllChildren(boolean measureAll) {
520        mMeasureAllChildren = measureAll;
521    }
522
523    /**
524     * Determines whether all children, or just those in the VISIBLE or
525     * INVISIBLE state, are considered when measuring.
526     *
527     * @return Whether all children are considered when measuring.
528     *
529     * @deprecated This method is deprecated in favor of
530     * {@link #getMeasureAllChildren() getMeasureAllChildren()}, which was
531     * renamed for consistency with
532     * {@link #setMeasureAllChildren(boolean) setMeasureAllChildren()}.
533     */
534    @Deprecated
535    public boolean getConsiderGoneChildrenWhenMeasuring() {
536        return getMeasureAllChildren();
537    }
538
539    /**
540     * Determines whether all children, or just those in the VISIBLE or
541     * INVISIBLE state, are considered when measuring.
542     *
543     * @return Whether all children are considered when measuring.
544     */
545    public boolean getMeasureAllChildren() {
546        return mMeasureAllChildren;
547    }
548
549    /**
550     * {@inheritDoc}
551     */
552    @Override
553    public LayoutParams generateLayoutParams(AttributeSet attrs) {
554        return new FrameLayout.LayoutParams(getContext(), attrs);
555    }
556
557    @Override
558    public boolean shouldDelayChildPressedState() {
559        return false;
560    }
561
562    /**
563     * {@inheritDoc}
564     */
565    @Override
566    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
567        return p instanceof LayoutParams;
568    }
569
570    @Override
571    protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
572        return new LayoutParams(p);
573    }
574
575
576    @Override
577    public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
578        super.onInitializeAccessibilityEvent(event);
579        event.setClassName(FrameLayout.class.getName());
580    }
581
582    @Override
583    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
584        super.onInitializeAccessibilityNodeInfo(info);
585        info.setClassName(FrameLayout.class.getName());
586    }
587
588    /**
589     * Per-child layout information for layouts that support margins.
590     * See {@link android.R.styleable#FrameLayout_Layout FrameLayout Layout Attributes}
591     * for a list of all child view attributes that this class supports.
592     *
593     * @attr ref android.R.styleable#FrameLayout_Layout_layout_gravity
594     */
595    public static class LayoutParams extends MarginLayoutParams {
596        /**
597         * The gravity to apply with the View to which these layout parameters
598         * are associated.
599         *
600         * @see android.view.Gravity
601         *
602         * @attr ref android.R.styleable#FrameLayout_Layout_layout_gravity
603         */
604        public int gravity = -1;
605
606        /**
607         * {@inheritDoc}
608         */
609        public LayoutParams(Context c, AttributeSet attrs) {
610            super(c, attrs);
611
612            TypedArray a = c.obtainStyledAttributes(attrs, com.android.internal.R.styleable.FrameLayout_Layout);
613            gravity = a.getInt(com.android.internal.R.styleable.FrameLayout_Layout_layout_gravity, -1);
614            a.recycle();
615        }
616
617        /**
618         * {@inheritDoc}
619         */
620        public LayoutParams(int width, int height) {
621            super(width, height);
622        }
623
624        /**
625         * Creates a new set of layout parameters with the specified width, height
626         * and weight.
627         *
628         * @param width the width, either {@link #MATCH_PARENT},
629         *        {@link #WRAP_CONTENT} or a fixed size in pixels
630         * @param height the height, either {@link #MATCH_PARENT},
631         *        {@link #WRAP_CONTENT} or a fixed size in pixels
632         * @param gravity the gravity
633         *
634         * @see android.view.Gravity
635         */
636        public LayoutParams(int width, int height, int gravity) {
637            super(width, height);
638            this.gravity = gravity;
639        }
640
641        /**
642         * {@inheritDoc}
643         */
644        public LayoutParams(ViewGroup.LayoutParams source) {
645            super(source);
646        }
647
648        /**
649         * {@inheritDoc}
650         */
651        public LayoutParams(ViewGroup.MarginLayoutParams source) {
652            super(source);
653        }
654    }
655}
656