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