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