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 com.android.internal.R; 20 21import android.annotation.AttrRes; 22import android.annotation.NonNull; 23import android.annotation.Nullable; 24import android.annotation.StyleRes; 25import android.content.Context; 26import android.content.res.TypedArray; 27import android.graphics.Rect; 28import android.graphics.drawable.Drawable; 29import android.util.AttributeSet; 30import android.view.Gravity; 31import android.view.View; 32import android.view.ViewDebug; 33import android.view.ViewGroup; 34import android.view.ViewHierarchyEncoder; 35import android.widget.RemoteViews.RemoteView; 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 (lp instanceof LayoutParams) { 386 return new LayoutParams((LayoutParams) lp); 387 } else if (lp instanceof MarginLayoutParams) { 388 return new LayoutParams((MarginLayoutParams) lp); 389 } else { 390 return new LayoutParams(lp); 391 } 392 } 393 394 @Override 395 public CharSequence getAccessibilityClassName() { 396 return FrameLayout.class.getName(); 397 } 398 399 /** @hide */ 400 @Override 401 protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) { 402 super.encodeProperties(encoder); 403 404 encoder.addProperty("measurement:measureAllChildren", mMeasureAllChildren); 405 encoder.addProperty("padding:foregroundPaddingLeft", mForegroundPaddingLeft); 406 encoder.addProperty("padding:foregroundPaddingTop", mForegroundPaddingTop); 407 encoder.addProperty("padding:foregroundPaddingRight", mForegroundPaddingRight); 408 encoder.addProperty("padding:foregroundPaddingBottom", mForegroundPaddingBottom); 409 } 410 411 /** 412 * Per-child layout information for layouts that support margins. 413 * See {@link android.R.styleable#FrameLayout_Layout FrameLayout Layout Attributes} 414 * for a list of all child view attributes that this class supports. 415 * 416 * @attr ref android.R.styleable#FrameLayout_Layout_layout_gravity 417 */ 418 public static class LayoutParams extends MarginLayoutParams { 419 /** 420 * Value for {@link #gravity} indicating that a gravity has not been 421 * explicitly specified. 422 */ 423 public static final int UNSPECIFIED_GRAVITY = -1; 424 425 /** 426 * The gravity to apply with the View to which these layout parameters 427 * are associated. 428 * <p> 429 * The default value is {@link #UNSPECIFIED_GRAVITY}, which is treated 430 * by FrameLayout as {@code Gravity.TOP | Gravity.START}. 431 * 432 * @see android.view.Gravity 433 * @attr ref android.R.styleable#FrameLayout_Layout_layout_gravity 434 */ 435 public int gravity = UNSPECIFIED_GRAVITY; 436 437 public LayoutParams(@NonNull Context c, @Nullable AttributeSet attrs) { 438 super(c, attrs); 439 440 final TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.FrameLayout_Layout); 441 gravity = a.getInt(R.styleable.FrameLayout_Layout_layout_gravity, UNSPECIFIED_GRAVITY); 442 a.recycle(); 443 } 444 445 public LayoutParams(int width, int height) { 446 super(width, height); 447 } 448 449 /** 450 * Creates a new set of layout parameters with the specified width, height 451 * and weight. 452 * 453 * @param width the width, either {@link #MATCH_PARENT}, 454 * {@link #WRAP_CONTENT} or a fixed size in pixels 455 * @param height the height, either {@link #MATCH_PARENT}, 456 * {@link #WRAP_CONTENT} or a fixed size in pixels 457 * @param gravity the gravity 458 * 459 * @see android.view.Gravity 460 */ 461 public LayoutParams(int width, int height, int gravity) { 462 super(width, height); 463 this.gravity = gravity; 464 } 465 466 public LayoutParams(@NonNull ViewGroup.LayoutParams source) { 467 super(source); 468 } 469 470 public LayoutParams(@NonNull ViewGroup.MarginLayoutParams source) { 471 super(source); 472 } 473 474 /** 475 * Copy constructor. Clones the width, height, margin values, and 476 * gravity of the source. 477 * 478 * @param source The layout params to copy from. 479 */ 480 public LayoutParams(@NonNull LayoutParams source) { 481 super(source); 482 483 this.gravity = source.gravity; 484 } 485 } 486} 487