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 java.util.ArrayList; 20 21 import android.content.Context; 22 import android.content.res.TypedArray; 23 import android.graphics.Canvas; 24 import android.graphics.Rect; 25 import android.graphics.Region; 26 import android.graphics.drawable.Drawable; 27 import android.util.AttributeSet; 28 import android.view.Gravity; 29 import android.view.View; 30 import android.view.ViewDebug; 31 import android.view.ViewGroup; 32 import android.widget.RemoteViews.RemoteView; 33 34 35 /** 36 * FrameLayout is designed to block out an area on the screen to display 37 * a single item. Generally, FrameLayout should be used to hold a single child view, because it can 38 * be difficult to organize child views in a way that's scalable to different screen sizes without 39 * the children overlapping each other. You can, however, add multiple children to a FrameLayout 40 * and control their position within the FrameLayout by assigning gravity to each child, using the 41 * <a href="FrameLayout.LayoutParams.html#attr_android:layout_gravity">{@code 42 * android:layout_gravity}</a> attribute. 43 * <p>Child views are drawn in a stack, with the most recently added child on top. 44 * The size of the FrameLayout is the size of its largest child (plus padding), visible 45 * or not (if the FrameLayout's parent permits). Views that are {@link android.view.View#GONE} are 46 * used for sizing 47 * only if {@link #setMeasureAllChildren(boolean) setConsiderGoneChildrenWhenMeasuring()} 48 * is set to true. 49 * 50 * @attr ref android.R.styleable#FrameLayout_foreground 51 * @attr ref android.R.styleable#FrameLayout_foregroundGravity 52 * @attr ref android.R.styleable#FrameLayout_measureAllChildren 53 */ 54 @RemoteView 55 public class FrameLayout extends ViewGroup { 56 private static final int DEFAULT_CHILD_GRAVITY = Gravity.TOP | Gravity.LEFT; 57 58 @ViewDebug.ExportedProperty(category = "measurement") 59 boolean mMeasureAllChildren = false; 60 61 @ViewDebug.ExportedProperty(category = "drawing") 62 private Drawable mForeground; 63 64 @ViewDebug.ExportedProperty(category = "padding") 65 private int mForegroundPaddingLeft = 0; 66 67 @ViewDebug.ExportedProperty(category = "padding") 68 private int mForegroundPaddingTop = 0; 69 70 @ViewDebug.ExportedProperty(category = "padding") 71 private int mForegroundPaddingRight = 0; 72 73 @ViewDebug.ExportedProperty(category = "padding") 74 private int mForegroundPaddingBottom = 0; 75 76 private final Rect mSelfBounds = new Rect(); 77 private final Rect mOverlayBounds = new Rect(); 78 79 @ViewDebug.ExportedProperty(category = "drawing") 80 private int mForegroundGravity = Gravity.FILL; 81 82 /** {@hide} */ 83 @ViewDebug.ExportedProperty(category = "drawing") 84 protected boolean mForegroundInPadding = true; 85 86 boolean mForegroundBoundsChanged = false; 87 88 private final ArrayList<View> mMatchParentChildren = new ArrayList<View>(1); 89 90 public FrameLayout(Context context) { 91 super(context); 92 } 93 94 public FrameLayout(Context context, AttributeSet attrs) { 95 this(context, attrs, 0); 96 } 97 98 public FrameLayout(Context context, AttributeSet attrs, int defStyle) { 99 super(context, attrs, defStyle); 100 101 TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.FrameLayout, 102 defStyle, 0); 103 104 mForegroundGravity = a.getInt( 105 com.android.internal.R.styleable.FrameLayout_foregroundGravity, mForegroundGravity); 106 107 final Drawable d = a.getDrawable(com.android.internal.R.styleable.FrameLayout_foreground); 108 if (d != null) { 109 setForeground(d); 110 } 111 112 if (a.getBoolean(com.android.internal.R.styleable.FrameLayout_measureAllChildren, false)) { 113 setMeasureAllChildren(true); 114 } 115 116 mForegroundInPadding = a.getBoolean( 117 com.android.internal.R.styleable.FrameLayout_foregroundInsidePadding, true); 118 119 a.recycle(); 120 } 121 122 /** 123 * Describes how the foreground is positioned. Defaults to START and TOP. 124 * 125 * @param foregroundGravity See {@link android.view.Gravity} 126 * 127 * @attr ref android.R.styleable#FrameLayout_foregroundGravity 128 */ 129 @android.view.RemotableViewMethod 130 public void setForegroundGravity(int foregroundGravity) { 131 if (mForegroundGravity != foregroundGravity) { 132 if ((foregroundGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) == 0) { 133 foregroundGravity |= Gravity.START; 134 } 135 136 if ((foregroundGravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) { 137 foregroundGravity |= Gravity.TOP; 138 } 139 140 mForegroundGravity = foregroundGravity; 141 142 143 if (mForegroundGravity == Gravity.FILL && mForeground != null) { 144 Rect padding = new Rect(); 145 if (mForeground.getPadding(padding)) { 146 mForegroundPaddingLeft = padding.left; 147 mForegroundPaddingTop = padding.top; 148 mForegroundPaddingRight = padding.right; 149 mForegroundPaddingBottom = padding.bottom; 150 } 151 } else { 152 mForegroundPaddingLeft = 0; 153 mForegroundPaddingTop = 0; 154 mForegroundPaddingRight = 0; 155 mForegroundPaddingBottom = 0; 156 } 157 158 requestLayout(); 159 } 160 } 161 162 /** 163 * {@inheritDoc} 164 */ 165 @Override 166 protected boolean verifyDrawable(Drawable who) { 167 return super.verifyDrawable(who) || (who == mForeground); 168 } 169 170 @Override 171 public void jumpDrawablesToCurrentState() { 172 super.jumpDrawablesToCurrentState(); 173 if (mForeground != null) mForeground.jumpToCurrentState(); 174 } 175 176 /** 177 * {@inheritDoc} 178 */ 179 @Override 180 protected void drawableStateChanged() { 181 super.drawableStateChanged(); 182 if (mForeground != null && mForeground.isStateful()) { 183 mForeground.setState(getDrawableState()); 184 } 185 } 186 187 /** 188 * Returns a set of layout parameters with a width of 189 * {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT}, 190 * and a height of {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT}. 191 */ 192 @Override 193 protected LayoutParams generateDefaultLayoutParams() { 194 return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); 195 } 196 197 /** 198 * Supply a Drawable that is to be rendered on top of all of the child 199 * views in the frame layout. Any padding in the Drawable will be taken 200 * into account by ensuring that the children are inset to be placed 201 * inside of the padding area. 202 * 203 * @param drawable The Drawable to be drawn on top of the children. 204 * 205 * @attr ref android.R.styleable#FrameLayout_foreground 206 */ 207 public void setForeground(Drawable drawable) { 208 if (mForeground != drawable) { 209 if (mForeground != null) { 210 mForeground.setCallback(null); 211 unscheduleDrawable(mForeground); 212 } 213 214 mForeground = drawable; 215 mForegroundPaddingLeft = 0; 216 mForegroundPaddingTop = 0; 217 mForegroundPaddingRight = 0; 218 mForegroundPaddingBottom = 0; 219 220 if (drawable != null) { 221 setWillNotDraw(false); 222 drawable.setCallback(this); 223 if (drawable.isStateful()) { 224 drawable.setState(getDrawableState()); 225 } 226 if (mForegroundGravity == Gravity.FILL) { 227 Rect padding = new Rect(); 228 if (drawable.getPadding(padding)) { 229 mForegroundPaddingLeft = padding.left; 230 mForegroundPaddingTop = padding.top; 231 mForegroundPaddingRight = padding.right; 232 mForegroundPaddingBottom = padding.bottom; 233 } 234 } 235 } else { 236 setWillNotDraw(true); 237 } 238 requestLayout(); 239 invalidate(); 240 } 241 } 242 243 /** 244 * Returns the drawable used as the foreground of this FrameLayout. The 245 * foreground drawable, if non-null, is always drawn on top of the children. 246 * 247 * @return A Drawable or null if no foreground was set. 248 */ 249 public Drawable getForeground() { 250 return mForeground; 251 } 252 253 private int getPaddingLeftWithForeground() { 254 return mForegroundInPadding ? Math.max(mPaddingLeft, mForegroundPaddingLeft) : 255 mPaddingLeft + mForegroundPaddingLeft; 256 } 257 258 private int getPaddingRightWithForeground() { 259 return mForegroundInPadding ? Math.max(mPaddingRight, mForegroundPaddingRight) : 260 mPaddingRight + mForegroundPaddingRight; 261 } 262 263 private int getPaddingTopWithForeground() { 264 return mForegroundInPadding ? Math.max(mPaddingTop, mForegroundPaddingTop) : 265 mPaddingTop + mForegroundPaddingTop; 266 } 267 268 private int getPaddingBottomWithForeground() { 269 return mForegroundInPadding ? Math.max(mPaddingBottom, mForegroundPaddingBottom) : 270 mPaddingBottom + mForegroundPaddingBottom; 271 } 272 273 274 /** 275 * {@inheritDoc} 276 */ 277 @Override 278 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 279 int count = getChildCount(); 280 281 final boolean measureMatchParentChildren = 282 MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY || 283 MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY; 284 mMatchParentChildren.clear(); 285 286 int maxHeight = 0; 287 int maxWidth = 0; 288 int childState = 0; 289 290 for (int i = 0; i < count; i++) { 291 final View child = getChildAt(i); 292 if (mMeasureAllChildren || child.getVisibility() != GONE) { 293 measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0); 294 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 295 maxWidth = Math.max(maxWidth, 296 child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin); 297 maxHeight = Math.max(maxHeight, 298 child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin); 299 childState = combineMeasuredStates(childState, child.getMeasuredState()); 300 if (measureMatchParentChildren) { 301 if (lp.width == LayoutParams.MATCH_PARENT || 302 lp.height == LayoutParams.MATCH_PARENT) { 303 mMatchParentChildren.add(child); 304 } 305 } 306 } 307 } 308 309 // Account for padding too 310 maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground(); 311 maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground(); 312 313 // Check against our minimum height and width 314 maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight()); 315 maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth()); 316 317 // Check against our foreground's minimum height and width 318 final Drawable drawable = getForeground(); 319 if (drawable != null) { 320 maxHeight = Math.max(maxHeight, drawable.getMinimumHeight()); 321 maxWidth = Math.max(maxWidth, drawable.getMinimumWidth()); 322 } 323 324 setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState), 325 resolveSizeAndState(maxHeight, heightMeasureSpec, 326 childState << MEASURED_HEIGHT_STATE_SHIFT)); 327 328 count = mMatchParentChildren.size(); 329 if (count > 1) { 330 for (int i = 0; i < count; i++) { 331 final View child = mMatchParentChildren.get(i); 332 333 final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); 334 int childWidthMeasureSpec; 335 int childHeightMeasureSpec; 336 337 if (lp.width == LayoutParams.MATCH_PARENT) { 338 childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth() - 339 getPaddingLeftWithForeground() - getPaddingRightWithForeground() - 340 lp.leftMargin - lp.rightMargin, 341 MeasureSpec.EXACTLY); 342 } else { 343 childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, 344 getPaddingLeftWithForeground() + getPaddingRightWithForeground() + 345 lp.leftMargin + lp.rightMargin, 346 lp.width); 347 } 348 349 if (lp.height == LayoutParams.MATCH_PARENT) { 350 childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight() - 351 getPaddingTopWithForeground() - getPaddingBottomWithForeground() - 352 lp.topMargin - lp.bottomMargin, 353 MeasureSpec.EXACTLY); 354 } else { 355 childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, 356 getPaddingTopWithForeground() + getPaddingBottomWithForeground() + 357 lp.topMargin + lp.bottomMargin, 358 lp.height); 359 } 360 361 child.measure(childWidthMeasureSpec, childHeightMeasureSpec); 362 } 363 } 364 } 365 366 /** 367 * {@inheritDoc} 368 */ 369 @Override 370 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 371 final int count = getChildCount(); 372 373 final int parentLeft = getPaddingLeftWithForeground(); 374 final int parentRight = right - left - getPaddingRightWithForeground(); 375 376 final int parentTop = getPaddingTopWithForeground(); 377 final int parentBottom = bottom - top - getPaddingBottomWithForeground(); 378 379 mForegroundBoundsChanged = true; 380 381 for (int i = 0; i < count; i++) { 382 final View child = getChildAt(i); 383 if (child.getVisibility() != GONE) { 384 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 385 386 final int width = child.getMeasuredWidth(); 387 final int height = child.getMeasuredHeight(); 388 389 int childLeft; 390 int childTop; 391 392 int gravity = lp.gravity; 393 if (gravity == -1) { 394 gravity = DEFAULT_CHILD_GRAVITY; 395 } 396 397 final int layoutDirection = getResolvedLayoutDirection(); 398 final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection); 399 final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK; 400 401 switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { 402 case Gravity.LEFT: 403 childLeft = parentLeft + lp.leftMargin; 404 break; 405 case Gravity.CENTER_HORIZONTAL: 406 childLeft = parentLeft + (parentRight - parentLeft - width) / 2 + 407 lp.leftMargin - lp.rightMargin; 408 break; 409 case Gravity.RIGHT: 410 childLeft = parentRight - width - lp.rightMargin; 411 break; 412 default: 413 childLeft = parentLeft + lp.leftMargin; 414 } 415 416 switch (verticalGravity) { 417 case Gravity.TOP: 418 childTop = parentTop + lp.topMargin; 419 break; 420 case Gravity.CENTER_VERTICAL: 421 childTop = parentTop + (parentBottom - parentTop - height) / 2 + 422 lp.topMargin - lp.bottomMargin; 423 break; 424 case Gravity.BOTTOM: 425 childTop = parentBottom - height - lp.bottomMargin; 426 break; 427 default: 428 childTop = parentTop + lp.topMargin; 429 } 430 431 child.layout(childLeft, childTop, childLeft + width, childTop + height); 432 } 433 } 434 } 435 436 /** 437 * {@inheritDoc} 438 */ 439 @Override 440 protected void onSizeChanged(int w, int h, int oldw, int oldh) { 441 super.onSizeChanged(w, h, oldw, oldh); 442 mForegroundBoundsChanged = true; 443 } 444 445 /** 446 * {@inheritDoc} 447 */ 448 @Override 449 public void draw(Canvas canvas) { 450 super.draw(canvas); 451 452 if (mForeground != null) { 453 final Drawable foreground = mForeground; 454 455 if (mForegroundBoundsChanged) { 456 mForegroundBoundsChanged = false; 457 final Rect selfBounds = mSelfBounds; 458 final Rect overlayBounds = mOverlayBounds; 459 460 final int w = mRight-mLeft; 461 final int h = mBottom-mTop; 462 463 if (mForegroundInPadding) { 464 selfBounds.set(0, 0, w, h); 465 } else { 466 selfBounds.set(mPaddingLeft, mPaddingTop, w - mPaddingRight, h - mPaddingBottom); 467 } 468 469 final int layoutDirection = getResolvedLayoutDirection(); 470 Gravity.apply(mForegroundGravity, foreground.getIntrinsicWidth(), 471 foreground.getIntrinsicHeight(), selfBounds, overlayBounds, 472 layoutDirection); 473 foreground.setBounds(overlayBounds); 474 } 475 476 foreground.draw(canvas); 477 } 478 } 479 480 /** 481 * {@inheritDoc} 482 */ 483 @Override 484 public boolean gatherTransparentRegion(Region region) { 485 boolean opaque = super.gatherTransparentRegion(region); 486 if (region != null && mForeground != null) { 487 applyDrawableToTransparentRegion(mForeground, region); 488 } 489 return opaque; 490 } 491 492 /** 493 * Sets whether to consider all children, or just those in 494 * the VISIBLE or INVISIBLE state, when measuring. Defaults to false. 495 * 496 * @param measureAll true to consider children marked GONE, false otherwise. 497 * Default value is false. 498 * 499 * @attr ref android.R.styleable#FrameLayout_measureAllChildren 500 */ 501 @android.view.RemotableViewMethod 502 public void setMeasureAllChildren(boolean measureAll) { 503 mMeasureAllChildren = measureAll; 504 } 505 506 /** 507 * Determines whether all children, or just those in the VISIBLE or 508 * INVISIBLE state, are considered when measuring. 509 * 510 * @return Whether all children are considered when measuring. 511 * 512 * @deprecated This method is deprecated in favor of 513 * {@link #getMeasureAllChildren() getMeasureAllChildren()}, which was 514 * renamed for consistency with 515 * {@link #setMeasureAllChildren(boolean) setMeasureAllChildren()}. 516 */ 517 @Deprecated 518 public boolean getConsiderGoneChildrenWhenMeasuring() { 519 return getMeasureAllChildren(); 520 } 521 522 /** 523 * Determines whether all children, or just those in the VISIBLE or 524 * INVISIBLE state, are considered when measuring. 525 * 526 * @return Whether all children are considered when measuring. 527 */ 528 public boolean getMeasureAllChildren() { 529 return mMeasureAllChildren; 530 } 531 532 /** 533 * {@inheritDoc} 534 */ 535 @Override 536 public LayoutParams generateLayoutParams(AttributeSet attrs) { 537 return new FrameLayout.LayoutParams(getContext(), attrs); 538 } 539 540 @Override 541 public boolean shouldDelayChildPressedState() { 542 return false; 543 } 544 545 /** 546 * {@inheritDoc} 547 */ 548 @Override 549 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { 550 return p instanceof LayoutParams; 551 } 552 553 @Override 554 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { 555 return new LayoutParams(p); 556 } 557 558 /** 559 * Per-child layout information for layouts that support margins. 560 * See {@link android.R.styleable#FrameLayout_Layout FrameLayout Layout Attributes} 561 * for a list of all child view attributes that this class supports. 562 * 563 * @attr ref android.R.styleable#FrameLayout_Layout_layout_gravity 564 */ 565 public static class LayoutParams extends MarginLayoutParams { 566 /** 567 * The gravity to apply with the View to which these layout parameters 568 * are associated. 569 * 570 * @see android.view.Gravity 571 * 572 * @attr ref android.R.styleable#FrameLayout_Layout_layout_gravity 573 */ 574 public int gravity = -1; 575 576 /** 577 * {@inheritDoc} 578 */ 579 public LayoutParams(Context c, AttributeSet attrs) { 580 super(c, attrs); 581 582 TypedArray a = c.obtainStyledAttributes(attrs, com.android.internal.R.styleable.FrameLayout_Layout); 583 gravity = a.getInt(com.android.internal.R.styleable.FrameLayout_Layout_layout_gravity, -1); 584 a.recycle(); 585 } 586 587 /** 588 * {@inheritDoc} 589 */ 590 public LayoutParams(int width, int height) { 591 super(width, height); 592 } 593 594 /** 595 * Creates a new set of layout parameters with the specified width, height 596 * and weight. 597 * 598 * @param width the width, either {@link #MATCH_PARENT}, 599 * {@link #WRAP_CONTENT} or a fixed size in pixels 600 * @param height the height, either {@link #MATCH_PARENT}, 601 * {@link #WRAP_CONTENT} or a fixed size in pixels 602 * @param gravity the gravity 603 * 604 * @see android.view.Gravity 605 */ 606 public LayoutParams(int width, int height, int gravity) { 607 super(width, height); 608 this.gravity = gravity; 609 } 610 611 /** 612 * {@inheritDoc} 613 */ 614 public LayoutParams(ViewGroup.LayoutParams source) { 615 super(source); 616 } 617 618 /** 619 * {@inheritDoc} 620 */ 621 public LayoutParams(ViewGroup.MarginLayoutParams source) { 622 super(source); 623 } 624 } 625 } 626