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