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.annotation.AttrRes;
20import android.annotation.NonNull;
21import android.annotation.Nullable;
22import android.annotation.StyleRes;
23import android.content.Context;
24import android.content.res.TypedArray;
25import android.graphics.Rect;
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.ViewHierarchyEncoder;
33import android.widget.RemoteViews.RemoteView;
34
35import com.android.internal.R;
36
37import java.util.ArrayList;
38
39/**
40 * FrameLayout is designed to block out an area on the screen to display
41 * a single item. Generally, FrameLayout should be used to hold a single child view, because it can
42 * be difficult to organize child views in a way that's scalable to different screen sizes without
43 * the children overlapping each other. You can, however, add multiple children to a FrameLayout
44 * and control their position within the FrameLayout by assigning gravity to each child, using the
45 * <a href="FrameLayout.LayoutParams.html#attr_android:layout_gravity">{@code
46 * android:layout_gravity}</a> attribute.
47 * <p>Child views are drawn in a stack, with the most recently added child on top.
48 * The size of the FrameLayout is the size of its largest child (plus padding), visible
49 * or not (if the FrameLayout's parent permits). Views that are {@link android.view.View#GONE} are
50 * used for sizing
51 * only if {@link #setMeasureAllChildren(boolean) setConsiderGoneChildrenWhenMeasuring()}
52 * is set to true.
53 *
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 = "padding")
64    private int mForegroundPaddingLeft = 0;
65
66    @ViewDebug.ExportedProperty(category = "padding")
67    private int mForegroundPaddingTop = 0;
68
69    @ViewDebug.ExportedProperty(category = "padding")
70    private int mForegroundPaddingRight = 0;
71
72    @ViewDebug.ExportedProperty(category = "padding")
73    private int mForegroundPaddingBottom = 0;
74
75    private final ArrayList<View> mMatchParentChildren = new ArrayList<>(1);
76
77    public FrameLayout(@NonNull Context context) {
78        super(context);
79    }
80
81    public FrameLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
82        this(context, attrs, 0);
83    }
84
85    public FrameLayout(@NonNull Context context, @Nullable AttributeSet attrs,
86            @AttrRes int defStyleAttr) {
87        this(context, attrs, defStyleAttr, 0);
88    }
89
90    public FrameLayout(@NonNull Context context, @Nullable AttributeSet attrs,
91            @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
92        super(context, attrs, defStyleAttr, defStyleRes);
93
94        final TypedArray a = context.obtainStyledAttributes(
95                attrs, R.styleable.FrameLayout, defStyleAttr, defStyleRes);
96
97        if (a.getBoolean(R.styleable.FrameLayout_measureAllChildren, false)) {
98            setMeasureAllChildren(true);
99        }
100
101        a.recycle();
102    }
103
104    /**
105     * Describes how the foreground is positioned. Defaults to START and TOP.
106     *
107     * @param foregroundGravity See {@link android.view.Gravity}
108     *
109     * @see #getForegroundGravity()
110     *
111     * @attr ref android.R.styleable#View_foregroundGravity
112     */
113    @android.view.RemotableViewMethod
114    public void setForegroundGravity(int foregroundGravity) {
115        if (getForegroundGravity() != foregroundGravity) {
116            super.setForegroundGravity(foregroundGravity);
117
118            // calling get* again here because the set above may apply default constraints
119            final Drawable foreground = getForeground();
120            if (getForegroundGravity() == Gravity.FILL && foreground != null) {
121                Rect padding = new Rect();
122                if (foreground.getPadding(padding)) {
123                    mForegroundPaddingLeft = padding.left;
124                    mForegroundPaddingTop = padding.top;
125                    mForegroundPaddingRight = padding.right;
126                    mForegroundPaddingBottom = padding.bottom;
127                }
128            } else {
129                mForegroundPaddingLeft = 0;
130                mForegroundPaddingTop = 0;
131                mForegroundPaddingRight = 0;
132                mForegroundPaddingBottom = 0;
133            }
134
135            requestLayout();
136        }
137    }
138
139    /**
140     * Returns a set of layout parameters with a width of
141     * {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT},
142     * and a height of {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT}.
143     */
144    @Override
145    protected LayoutParams generateDefaultLayoutParams() {
146        return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
147    }
148
149    int getPaddingLeftWithForeground() {
150        return isForegroundInsidePadding() ? Math.max(mPaddingLeft, mForegroundPaddingLeft) :
151            mPaddingLeft + mForegroundPaddingLeft;
152    }
153
154    int getPaddingRightWithForeground() {
155        return isForegroundInsidePadding() ? Math.max(mPaddingRight, mForegroundPaddingRight) :
156            mPaddingRight + mForegroundPaddingRight;
157    }
158
159    private int getPaddingTopWithForeground() {
160        return isForegroundInsidePadding() ? Math.max(mPaddingTop, mForegroundPaddingTop) :
161            mPaddingTop + mForegroundPaddingTop;
162    }
163
164    private int getPaddingBottomWithForeground() {
165        return isForegroundInsidePadding() ? Math.max(mPaddingBottom, mForegroundPaddingBottom) :
166            mPaddingBottom + mForegroundPaddingBottom;
167    }
168
169    @Override
170    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
171        int count = getChildCount();
172
173        final boolean measureMatchParentChildren =
174                MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
175                MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
176        mMatchParentChildren.clear();
177
178        int maxHeight = 0;
179        int maxWidth = 0;
180        int childState = 0;
181
182        for (int i = 0; i < count; i++) {
183            final View child = getChildAt(i);
184            if (mMeasureAllChildren || child.getVisibility() != GONE) {
185                measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
186                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
187                maxWidth = Math.max(maxWidth,
188                        child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
189                maxHeight = Math.max(maxHeight,
190                        child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
191                childState = combineMeasuredStates(childState, child.getMeasuredState());
192                if (measureMatchParentChildren) {
193                    if (lp.width == LayoutParams.MATCH_PARENT ||
194                            lp.height == LayoutParams.MATCH_PARENT) {
195                        mMatchParentChildren.add(child);
196                    }
197                }
198            }
199        }
200
201        // Account for padding too
202        maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
203        maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();
204
205        // Check against our minimum height and width
206        maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
207        maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
208
209        // Check against our foreground's minimum height and width
210        final Drawable drawable = getForeground();
211        if (drawable != null) {
212            maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
213            maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
214        }
215
216        setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
217                resolveSizeAndState(maxHeight, heightMeasureSpec,
218                        childState << MEASURED_HEIGHT_STATE_SHIFT));
219
220        count = mMatchParentChildren.size();
221        if (count > 1) {
222            for (int i = 0; i < count; i++) {
223                final View child = mMatchParentChildren.get(i);
224                final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
225
226                final int childWidthMeasureSpec;
227                if (lp.width == LayoutParams.MATCH_PARENT) {
228                    final int width = Math.max(0, getMeasuredWidth()
229                            - getPaddingLeftWithForeground() - getPaddingRightWithForeground()
230                            - lp.leftMargin - lp.rightMargin);
231                    childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
232                            width, MeasureSpec.EXACTLY);
233                } else {
234                    childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
235                            getPaddingLeftWithForeground() + getPaddingRightWithForeground() +
236                            lp.leftMargin + lp.rightMargin,
237                            lp.width);
238                }
239
240                final int childHeightMeasureSpec;
241                if (lp.height == LayoutParams.MATCH_PARENT) {
242                    final int height = Math.max(0, getMeasuredHeight()
243                            - getPaddingTopWithForeground() - getPaddingBottomWithForeground()
244                            - lp.topMargin - lp.bottomMargin);
245                    childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
246                            height, MeasureSpec.EXACTLY);
247                } else {
248                    childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
249                            getPaddingTopWithForeground() + getPaddingBottomWithForeground() +
250                            lp.topMargin + lp.bottomMargin,
251                            lp.height);
252                }
253
254                child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
255            }
256        }
257    }
258
259    @Override
260    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
261        layoutChildren(left, top, right, bottom, false /* no force left gravity */);
262    }
263
264    void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
265        final int count = getChildCount();
266
267        final int parentLeft = getPaddingLeftWithForeground();
268        final int parentRight = right - left - getPaddingRightWithForeground();
269
270        final int parentTop = getPaddingTopWithForeground();
271        final int parentBottom = bottom - top - getPaddingBottomWithForeground();
272
273        for (int i = 0; i < count; i++) {
274            final View child = getChildAt(i);
275            if (child.getVisibility() != GONE) {
276                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
277
278                final int width = child.getMeasuredWidth();
279                final int height = child.getMeasuredHeight();
280
281                int childLeft;
282                int childTop;
283
284                int gravity = lp.gravity;
285                if (gravity == -1) {
286                    gravity = DEFAULT_CHILD_GRAVITY;
287                }
288
289                final int layoutDirection = getLayoutDirection();
290                final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
291                final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
292
293                switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
294                    case Gravity.CENTER_HORIZONTAL:
295                        childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
296                        lp.leftMargin - lp.rightMargin;
297                        break;
298                    case Gravity.RIGHT:
299                        if (!forceLeftGravity) {
300                            childLeft = parentRight - width - lp.rightMargin;
301                            break;
302                        }
303                    case Gravity.LEFT:
304                    default:
305                        childLeft = parentLeft + lp.leftMargin;
306                }
307
308                switch (verticalGravity) {
309                    case Gravity.TOP:
310                        childTop = parentTop + lp.topMargin;
311                        break;
312                    case Gravity.CENTER_VERTICAL:
313                        childTop = parentTop + (parentBottom - parentTop - height) / 2 +
314                        lp.topMargin - lp.bottomMargin;
315                        break;
316                    case Gravity.BOTTOM:
317                        childTop = parentBottom - height - lp.bottomMargin;
318                        break;
319                    default:
320                        childTop = parentTop + lp.topMargin;
321                }
322
323                child.layout(childLeft, childTop, childLeft + width, childTop + height);
324            }
325        }
326    }
327
328    /**
329     * Sets whether to consider all children, or just those in
330     * the VISIBLE or INVISIBLE state, when measuring. Defaults to false.
331     *
332     * @param measureAll true to consider children marked GONE, false otherwise.
333     * Default value is false.
334     *
335     * @attr ref android.R.styleable#FrameLayout_measureAllChildren
336     */
337    @android.view.RemotableViewMethod
338    public void setMeasureAllChildren(boolean measureAll) {
339        mMeasureAllChildren = measureAll;
340    }
341
342    /**
343     * Determines whether all children, or just those in the VISIBLE or
344     * INVISIBLE state, are considered when measuring.
345     *
346     * @return Whether all children are considered when measuring.
347     *
348     * @deprecated This method is deprecated in favor of
349     * {@link #getMeasureAllChildren() getMeasureAllChildren()}, which was
350     * renamed for consistency with
351     * {@link #setMeasureAllChildren(boolean) setMeasureAllChildren()}.
352     */
353    @Deprecated
354    public boolean getConsiderGoneChildrenWhenMeasuring() {
355        return getMeasureAllChildren();
356    }
357
358    /**
359     * Determines whether all children, or just those in the VISIBLE or
360     * INVISIBLE state, are considered when measuring.
361     *
362     * @return Whether all children are considered when measuring.
363     */
364    public boolean getMeasureAllChildren() {
365        return mMeasureAllChildren;
366    }
367
368    @Override
369    public LayoutParams generateLayoutParams(AttributeSet attrs) {
370        return new FrameLayout.LayoutParams(getContext(), attrs);
371    }
372
373    @Override
374    public boolean shouldDelayChildPressedState() {
375        return false;
376    }
377
378    @Override
379    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
380        return p instanceof LayoutParams;
381    }
382
383    @Override
384    protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) {
385        if (sPreserveMarginParamsInLayoutParamConversion) {
386            if (lp instanceof LayoutParams) {
387                return new LayoutParams((LayoutParams) lp);
388            } else if (lp instanceof MarginLayoutParams) {
389                return new LayoutParams((MarginLayoutParams) lp);
390            }
391        }
392        return new LayoutParams(lp);
393    }
394
395    @Override
396    public CharSequence getAccessibilityClassName() {
397        return FrameLayout.class.getName();
398    }
399
400    /** @hide */
401    @Override
402    protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) {
403        super.encodeProperties(encoder);
404
405        encoder.addProperty("measurement:measureAllChildren", mMeasureAllChildren);
406        encoder.addProperty("padding:foregroundPaddingLeft", mForegroundPaddingLeft);
407        encoder.addProperty("padding:foregroundPaddingTop", mForegroundPaddingTop);
408        encoder.addProperty("padding:foregroundPaddingRight", mForegroundPaddingRight);
409        encoder.addProperty("padding:foregroundPaddingBottom", mForegroundPaddingBottom);
410    }
411
412    /**
413     * Per-child layout information for layouts that support margins.
414     * See {@link android.R.styleable#FrameLayout_Layout FrameLayout Layout Attributes}
415     * for a list of all child view attributes that this class supports.
416     *
417     * @attr ref android.R.styleable#FrameLayout_Layout_layout_gravity
418     */
419    public static class LayoutParams extends MarginLayoutParams {
420        /**
421         * Value for {@link #gravity} indicating that a gravity has not been
422         * explicitly specified.
423         */
424        public static final int UNSPECIFIED_GRAVITY = -1;
425
426        /**
427         * The gravity to apply with the View to which these layout parameters
428         * are associated.
429         * <p>
430         * The default value is {@link #UNSPECIFIED_GRAVITY}, which is treated
431         * by FrameLayout as {@code Gravity.TOP | Gravity.START}.
432         *
433         * @see android.view.Gravity
434         * @attr ref android.R.styleable#FrameLayout_Layout_layout_gravity
435         */
436        public int gravity = UNSPECIFIED_GRAVITY;
437
438        public LayoutParams(@NonNull Context c, @Nullable AttributeSet attrs) {
439            super(c, attrs);
440
441            final TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.FrameLayout_Layout);
442            gravity = a.getInt(R.styleable.FrameLayout_Layout_layout_gravity, UNSPECIFIED_GRAVITY);
443            a.recycle();
444        }
445
446        public LayoutParams(int width, int height) {
447            super(width, height);
448        }
449
450        /**
451         * Creates a new set of layout parameters with the specified width, height
452         * and weight.
453         *
454         * @param width the width, either {@link #MATCH_PARENT},
455         *              {@link #WRAP_CONTENT} or a fixed size in pixels
456         * @param height the height, either {@link #MATCH_PARENT},
457         *               {@link #WRAP_CONTENT} or a fixed size in pixels
458         * @param gravity the gravity
459         *
460         * @see android.view.Gravity
461         */
462        public LayoutParams(int width, int height, int gravity) {
463            super(width, height);
464            this.gravity = gravity;
465        }
466
467        public LayoutParams(@NonNull ViewGroup.LayoutParams source) {
468            super(source);
469        }
470
471        public LayoutParams(@NonNull ViewGroup.MarginLayoutParams source) {
472            super(source);
473        }
474
475        /**
476         * Copy constructor. Clones the width, height, margin values, and
477         * gravity of the source.
478         *
479         * @param source The layout params to copy from.
480         */
481        public LayoutParams(@NonNull LayoutParams source) {
482            super(source);
483
484            this.gravity = source.gravity;
485        }
486    }
487}
488