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 com.android.internal.R; 20 21 import java.util.ArrayDeque; 22 import java.util.ArrayList; 23 import java.util.Comparator; 24 import java.util.HashMap; 25 import java.util.SortedSet; 26 import java.util.TreeSet; 27 28 import android.content.Context; 29 import android.content.res.Resources; 30 import android.content.res.TypedArray; 31 import android.graphics.Rect; 32 import android.util.AttributeSet; 33 import android.util.Pool; 34 import android.util.Poolable; 35 import android.util.PoolableManager; 36 import android.util.Pools; 37 import android.util.SparseArray; 38 import android.view.Gravity; 39 import android.view.View; 40 import android.view.ViewDebug; 41 import android.view.ViewGroup; 42 import android.view.accessibility.AccessibilityEvent; 43 import android.view.accessibility.AccessibilityNodeInfo; 44 import android.widget.RemoteViews.RemoteView; 45 46 import static android.util.Log.d; 47 48 /** 49 * A Layout where the positions of the children can be described in relation to each other or to the 50 * parent. 51 * 52 * <p> 53 * Note that you cannot have a circular dependency between the size of the RelativeLayout and the 54 * position of its children. For example, you cannot have a RelativeLayout whose height is set to 55 * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT WRAP_CONTENT} and a child set to 56 * {@link #ALIGN_PARENT_BOTTOM}. 57 * </p> 58 * 59 * <p>See the <a href="{@docRoot}guide/topics/ui/layout/relative.html">Relative 60 * Layout</a> guide.</p> 61 * 62 * <p> 63 * Also see {@link android.widget.RelativeLayout.LayoutParams RelativeLayout.LayoutParams} for 64 * layout attributes 65 * </p> 66 * 67 * @attr ref android.R.styleable#RelativeLayout_gravity 68 * @attr ref android.R.styleable#RelativeLayout_ignoreGravity 69 */ 70 @RemoteView 71 public class RelativeLayout extends ViewGroup { 72 private static final String LOG_TAG = "RelativeLayout"; 73 74 private static final boolean DEBUG_GRAPH = false; 75 76 public static final int TRUE = -1; 77 78 /** 79 * Rule that aligns a child's right edge with another child's left edge. 80 */ 81 public static final int LEFT_OF = 0; 82 /** 83 * Rule that aligns a child's left edge with another child's right edge. 84 */ 85 public static final int RIGHT_OF = 1; 86 /** 87 * Rule that aligns a child's bottom edge with another child's top edge. 88 */ 89 public static final int ABOVE = 2; 90 /** 91 * Rule that aligns a child's top edge with another child's bottom edge. 92 */ 93 public static final int BELOW = 3; 94 95 /** 96 * Rule that aligns a child's baseline with another child's baseline. 97 */ 98 public static final int ALIGN_BASELINE = 4; 99 /** 100 * Rule that aligns a child's left edge with another child's left edge. 101 */ 102 public static final int ALIGN_LEFT = 5; 103 /** 104 * Rule that aligns a child's top edge with another child's top edge. 105 */ 106 public static final int ALIGN_TOP = 6; 107 /** 108 * Rule that aligns a child's right edge with another child's right edge. 109 */ 110 public static final int ALIGN_RIGHT = 7; 111 /** 112 * Rule that aligns a child's bottom edge with another child's bottom edge. 113 */ 114 public static final int ALIGN_BOTTOM = 8; 115 116 /** 117 * Rule that aligns the child's left edge with its RelativeLayout 118 * parent's left edge. 119 */ 120 public static final int ALIGN_PARENT_LEFT = 9; 121 /** 122 * Rule that aligns the child's top edge with its RelativeLayout 123 * parent's top edge. 124 */ 125 public static final int ALIGN_PARENT_TOP = 10; 126 /** 127 * Rule that aligns the child's right edge with its RelativeLayout 128 * parent's right edge. 129 */ 130 public static final int ALIGN_PARENT_RIGHT = 11; 131 /** 132 * Rule that aligns the child's bottom edge with its RelativeLayout 133 * parent's bottom edge. 134 */ 135 public static final int ALIGN_PARENT_BOTTOM = 12; 136 137 /** 138 * Rule that centers the child with respect to the bounds of its 139 * RelativeLayout parent. 140 */ 141 public static final int CENTER_IN_PARENT = 13; 142 /** 143 * Rule that centers the child horizontally with respect to the 144 * bounds of its RelativeLayout parent. 145 */ 146 public static final int CENTER_HORIZONTAL = 14; 147 /** 148 * Rule that centers the child vertically with respect to the 149 * bounds of its RelativeLayout parent. 150 */ 151 public static final int CENTER_VERTICAL = 15; 152 /** 153 * Rule that aligns a child's end edge with another child's start edge. 154 */ 155 public static final int START_OF = 16; 156 /** 157 * Rule that aligns a child's start edge with another child's end edge. 158 */ 159 public static final int END_OF = 17; 160 /** 161 * Rule that aligns a child's start edge with another child's start edge. 162 */ 163 public static final int ALIGN_START = 18; 164 /** 165 * Rule that aligns a child's end edge with another child's end edge. 166 */ 167 public static final int ALIGN_END = 19; 168 /** 169 * Rule that aligns the child's start edge with its RelativeLayout 170 * parent's start edge. 171 */ 172 public static final int ALIGN_PARENT_START = 20; 173 /** 174 * Rule that aligns the child's end edge with its RelativeLayout 175 * parent's end edge. 176 */ 177 public static final int ALIGN_PARENT_END = 21; 178 179 private static final int VERB_COUNT = 22; 180 181 182 private static final int[] RULES_VERTICAL = { 183 ABOVE, BELOW, ALIGN_BASELINE, ALIGN_TOP, ALIGN_BOTTOM 184 }; 185 186 private static final int[] RULES_HORIZONTAL = { 187 LEFT_OF, RIGHT_OF, ALIGN_LEFT, ALIGN_RIGHT, START_OF, END_OF, ALIGN_START, ALIGN_END 188 }; 189 190 private View mBaselineView = null; 191 private boolean mHasBaselineAlignedChild; 192 193 private int mGravity = Gravity.START | Gravity.TOP; 194 private final Rect mContentBounds = new Rect(); 195 private final Rect mSelfBounds = new Rect(); 196 private int mIgnoreGravity; 197 198 private SortedSet<View> mTopToBottomLeftToRightSet = null; 199 200 private boolean mDirtyHierarchy; 201 private View[] mSortedHorizontalChildren = new View[0]; 202 private View[] mSortedVerticalChildren = new View[0]; 203 private final DependencyGraph mGraph = new DependencyGraph(); 204 RelativeLayout(Context context)205 public RelativeLayout(Context context) { 206 super(context); 207 } 208 RelativeLayout(Context context, AttributeSet attrs)209 public RelativeLayout(Context context, AttributeSet attrs) { 210 super(context, attrs); 211 initFromAttributes(context, attrs); 212 } 213 RelativeLayout(Context context, AttributeSet attrs, int defStyle)214 public RelativeLayout(Context context, AttributeSet attrs, int defStyle) { 215 super(context, attrs, defStyle); 216 initFromAttributes(context, attrs); 217 } 218 initFromAttributes(Context context, AttributeSet attrs)219 private void initFromAttributes(Context context, AttributeSet attrs) { 220 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RelativeLayout); 221 mIgnoreGravity = a.getResourceId(R.styleable.RelativeLayout_ignoreGravity, View.NO_ID); 222 mGravity = a.getInt(R.styleable.RelativeLayout_gravity, mGravity); 223 a.recycle(); 224 } 225 226 @Override shouldDelayChildPressedState()227 public boolean shouldDelayChildPressedState() { 228 return false; 229 } 230 231 /** 232 * Defines which View is ignored when the gravity is applied. This setting has no 233 * effect if the gravity is <code>Gravity.START | Gravity.TOP</code>. 234 * 235 * @param viewId The id of the View to be ignored by gravity, or 0 if no View 236 * should be ignored. 237 * 238 * @see #setGravity(int) 239 * 240 * @attr ref android.R.styleable#RelativeLayout_ignoreGravity 241 */ 242 @android.view.RemotableViewMethod setIgnoreGravity(int viewId)243 public void setIgnoreGravity(int viewId) { 244 mIgnoreGravity = viewId; 245 } 246 247 /** 248 * Describes how the child views are positioned. 249 * 250 * @return the gravity. 251 * 252 * @see #setGravity(int) 253 * @see android.view.Gravity 254 * 255 * @attr ref android.R.styleable#RelativeLayout_gravity 256 */ getGravity()257 public int getGravity() { 258 return mGravity; 259 } 260 261 /** 262 * Describes how the child views are positioned. Defaults to 263 * <code>Gravity.START | Gravity.TOP</code>. 264 * 265 * <p>Note that since RelativeLayout considers the positioning of each child 266 * relative to one another to be significant, setting gravity will affect 267 * the positioning of all children as a single unit within the parent. 268 * This happens after children have been relatively positioned.</p> 269 * 270 * @param gravity See {@link android.view.Gravity} 271 * 272 * @see #setHorizontalGravity(int) 273 * @see #setVerticalGravity(int) 274 * 275 * @attr ref android.R.styleable#RelativeLayout_gravity 276 */ 277 @android.view.RemotableViewMethod setGravity(int gravity)278 public void setGravity(int gravity) { 279 if (mGravity != gravity) { 280 if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) == 0) { 281 gravity |= Gravity.START; 282 } 283 284 if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) { 285 gravity |= Gravity.TOP; 286 } 287 288 mGravity = gravity; 289 requestLayout(); 290 } 291 } 292 293 @android.view.RemotableViewMethod setHorizontalGravity(int horizontalGravity)294 public void setHorizontalGravity(int horizontalGravity) { 295 final int gravity = horizontalGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK; 296 if ((mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) != gravity) { 297 mGravity = (mGravity & ~Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) | gravity; 298 requestLayout(); 299 } 300 } 301 302 @android.view.RemotableViewMethod setVerticalGravity(int verticalGravity)303 public void setVerticalGravity(int verticalGravity) { 304 final int gravity = verticalGravity & Gravity.VERTICAL_GRAVITY_MASK; 305 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != gravity) { 306 mGravity = (mGravity & ~Gravity.VERTICAL_GRAVITY_MASK) | gravity; 307 requestLayout(); 308 } 309 } 310 311 @Override getBaseline()312 public int getBaseline() { 313 return mBaselineView != null ? mBaselineView.getBaseline() : super.getBaseline(); 314 } 315 316 @Override requestLayout()317 public void requestLayout() { 318 super.requestLayout(); 319 mDirtyHierarchy = true; 320 } 321 sortChildren()322 private void sortChildren() { 323 int count = getChildCount(); 324 if (mSortedVerticalChildren.length != count) mSortedVerticalChildren = new View[count]; 325 if (mSortedHorizontalChildren.length != count) mSortedHorizontalChildren = new View[count]; 326 327 final DependencyGraph graph = mGraph; 328 graph.clear(); 329 330 for (int i = 0; i < count; i++) { 331 final View child = getChildAt(i); 332 graph.add(child); 333 } 334 335 if (DEBUG_GRAPH) { 336 d(LOG_TAG, "=== Sorted vertical children"); 337 graph.log(getResources(), RULES_VERTICAL); 338 d(LOG_TAG, "=== Sorted horizontal children"); 339 graph.log(getResources(), RULES_HORIZONTAL); 340 } 341 342 graph.getSortedViews(mSortedVerticalChildren, RULES_VERTICAL); 343 graph.getSortedViews(mSortedHorizontalChildren, RULES_HORIZONTAL); 344 345 if (DEBUG_GRAPH) { 346 d(LOG_TAG, "=== Ordered list of vertical children"); 347 for (View view : mSortedVerticalChildren) { 348 DependencyGraph.printViewId(getResources(), view); 349 } 350 d(LOG_TAG, "=== Ordered list of horizontal children"); 351 for (View view : mSortedHorizontalChildren) { 352 DependencyGraph.printViewId(getResources(), view); 353 } 354 } 355 } 356 357 // TODO: we need to find another way to implement RelativeLayout 358 // This implementation cannot handle every case 359 @Override onMeasure(int widthMeasureSpec, int heightMeasureSpec)360 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 361 if (mDirtyHierarchy) { 362 mDirtyHierarchy = false; 363 sortChildren(); 364 } 365 366 int myWidth = -1; 367 int myHeight = -1; 368 369 int width = 0; 370 int height = 0; 371 372 final int widthMode = MeasureSpec.getMode(widthMeasureSpec); 373 final int heightMode = MeasureSpec.getMode(heightMeasureSpec); 374 final int widthSize = MeasureSpec.getSize(widthMeasureSpec); 375 final int heightSize = MeasureSpec.getSize(heightMeasureSpec); 376 377 // Record our dimensions if they are known; 378 if (widthMode != MeasureSpec.UNSPECIFIED) { 379 myWidth = widthSize; 380 } 381 382 if (heightMode != MeasureSpec.UNSPECIFIED) { 383 myHeight = heightSize; 384 } 385 386 if (widthMode == MeasureSpec.EXACTLY) { 387 width = myWidth; 388 } 389 390 if (heightMode == MeasureSpec.EXACTLY) { 391 height = myHeight; 392 } 393 394 mHasBaselineAlignedChild = false; 395 396 View ignore = null; 397 int gravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK; 398 final boolean horizontalGravity = gravity != Gravity.START && gravity != 0; 399 gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; 400 final boolean verticalGravity = gravity != Gravity.TOP && gravity != 0; 401 402 int left = Integer.MAX_VALUE; 403 int top = Integer.MAX_VALUE; 404 int right = Integer.MIN_VALUE; 405 int bottom = Integer.MIN_VALUE; 406 407 boolean offsetHorizontalAxis = false; 408 boolean offsetVerticalAxis = false; 409 410 if ((horizontalGravity || verticalGravity) && mIgnoreGravity != View.NO_ID) { 411 ignore = findViewById(mIgnoreGravity); 412 } 413 414 final boolean isWrapContentWidth = widthMode != MeasureSpec.EXACTLY; 415 final boolean isWrapContentHeight = heightMode != MeasureSpec.EXACTLY; 416 417 View[] views = mSortedHorizontalChildren; 418 int count = views.length; 419 420 // We need to know our size for doing the correct computation of positioning in RTL mode 421 if (isLayoutRtl() && (myWidth == -1 || isWrapContentWidth)) { 422 int w = getPaddingStart() + getPaddingEnd(); 423 final int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); 424 for (int i = 0; i < count; i++) { 425 View child = views[i]; 426 if (child.getVisibility() != GONE) { 427 LayoutParams params = (LayoutParams) child.getLayoutParams(); 428 // Would be similar to a call to measureChildHorizontal(child, params, -1, myHeight) 429 // but we cannot change for now the behavior of measureChildHorizontal() for 430 // taking care or a "-1" for "mywidth" so use here our own version of that code. 431 int childHeightMeasureSpec; 432 if (params.width == LayoutParams.MATCH_PARENT) { 433 childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(myHeight, MeasureSpec.EXACTLY); 434 } else { 435 childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(myHeight, MeasureSpec.AT_MOST); 436 } 437 child.measure(childWidthMeasureSpec, childHeightMeasureSpec); 438 439 w += child.getMeasuredWidth(); 440 w += params.leftMargin + params.rightMargin; 441 } 442 } 443 if (myWidth == -1) { 444 // Easy case: "myWidth" was undefined before so use the width we have just computed 445 myWidth = w; 446 } else { 447 // "myWidth" was defined before, so take the min of it and the computed width if it 448 // is a non null one 449 if (w > 0) { 450 myWidth = Math.min(myWidth, w); 451 } 452 } 453 } 454 455 for (int i = 0; i < count; i++) { 456 View child = views[i]; 457 if (child.getVisibility() != GONE) { 458 LayoutParams params = (LayoutParams) child.getLayoutParams(); 459 460 applyHorizontalSizeRules(params, myWidth); 461 measureChildHorizontal(child, params, myWidth, myHeight); 462 if (positionChildHorizontal(child, params, myWidth, isWrapContentWidth)) { 463 offsetHorizontalAxis = true; 464 } 465 } 466 } 467 468 views = mSortedVerticalChildren; 469 count = views.length; 470 471 for (int i = 0; i < count; i++) { 472 View child = views[i]; 473 if (child.getVisibility() != GONE) { 474 LayoutParams params = (LayoutParams) child.getLayoutParams(); 475 476 applyVerticalSizeRules(params, myHeight); 477 measureChild(child, params, myWidth, myHeight); 478 if (positionChildVertical(child, params, myHeight, isWrapContentHeight)) { 479 offsetVerticalAxis = true; 480 } 481 482 if (isWrapContentWidth) { 483 width = Math.max(width, params.mRight); 484 } 485 486 if (isWrapContentHeight) { 487 height = Math.max(height, params.mBottom); 488 } 489 490 if (child != ignore || verticalGravity) { 491 left = Math.min(left, params.mLeft - params.leftMargin); 492 top = Math.min(top, params.mTop - params.topMargin); 493 } 494 495 if (child != ignore || horizontalGravity) { 496 right = Math.max(right, params.mRight + params.rightMargin); 497 bottom = Math.max(bottom, params.mBottom + params.bottomMargin); 498 } 499 } 500 } 501 502 if (mHasBaselineAlignedChild) { 503 for (int i = 0; i < count; i++) { 504 View child = getChildAt(i); 505 if (child.getVisibility() != GONE) { 506 LayoutParams params = (LayoutParams) child.getLayoutParams(); 507 alignBaseline(child, params); 508 509 if (child != ignore || verticalGravity) { 510 left = Math.min(left, params.mLeft - params.leftMargin); 511 top = Math.min(top, params.mTop - params.topMargin); 512 } 513 514 if (child != ignore || horizontalGravity) { 515 right = Math.max(right, params.mRight + params.rightMargin); 516 bottom = Math.max(bottom, params.mBottom + params.bottomMargin); 517 } 518 } 519 } 520 } 521 522 final int layoutDirection = getLayoutDirection(); 523 524 if (isWrapContentWidth) { 525 // Width already has left padding in it since it was calculated by looking at 526 // the right of each child view 527 width += mPaddingRight; 528 529 if (mLayoutParams.width >= 0) { 530 width = Math.max(width, mLayoutParams.width); 531 } 532 533 width = Math.max(width, getSuggestedMinimumWidth()); 534 width = resolveSize(width, widthMeasureSpec); 535 536 if (offsetHorizontalAxis) { 537 for (int i = 0; i < count; i++) { 538 View child = getChildAt(i); 539 if (child.getVisibility() != GONE) { 540 LayoutParams params = (LayoutParams) child.getLayoutParams(); 541 final int[] rules = params.getRules(layoutDirection); 542 if (rules[CENTER_IN_PARENT] != 0 || rules[CENTER_HORIZONTAL] != 0) { 543 centerHorizontal(child, params, width); 544 } else if (rules[ALIGN_PARENT_RIGHT] != 0) { 545 final int childWidth = child.getMeasuredWidth(); 546 params.mLeft = width - mPaddingRight - childWidth; 547 params.mRight = params.mLeft + childWidth; 548 } 549 } 550 } 551 } 552 } 553 554 if (isWrapContentHeight) { 555 // Height already has top padding in it since it was calculated by looking at 556 // the bottom of each child view 557 height += mPaddingBottom; 558 559 if (mLayoutParams.height >= 0) { 560 height = Math.max(height, mLayoutParams.height); 561 } 562 563 height = Math.max(height, getSuggestedMinimumHeight()); 564 height = resolveSize(height, heightMeasureSpec); 565 566 if (offsetVerticalAxis) { 567 for (int i = 0; i < count; i++) { 568 View child = getChildAt(i); 569 if (child.getVisibility() != GONE) { 570 LayoutParams params = (LayoutParams) child.getLayoutParams(); 571 final int[] rules = params.getRules(layoutDirection); 572 if (rules[CENTER_IN_PARENT] != 0 || rules[CENTER_VERTICAL] != 0) { 573 centerVertical(child, params, height); 574 } else if (rules[ALIGN_PARENT_BOTTOM] != 0) { 575 final int childHeight = child.getMeasuredHeight(); 576 params.mTop = height - mPaddingBottom - childHeight; 577 params.mBottom = params.mTop + childHeight; 578 } 579 } 580 } 581 } 582 } 583 584 if (horizontalGravity || verticalGravity) { 585 final Rect selfBounds = mSelfBounds; 586 selfBounds.set(mPaddingLeft, mPaddingTop, width - mPaddingRight, 587 height - mPaddingBottom); 588 589 final Rect contentBounds = mContentBounds; 590 Gravity.apply(mGravity, right - left, bottom - top, selfBounds, contentBounds, 591 layoutDirection); 592 593 final int horizontalOffset = contentBounds.left - left; 594 final int verticalOffset = contentBounds.top - top; 595 if (horizontalOffset != 0 || verticalOffset != 0) { 596 for (int i = 0; i < count; i++) { 597 View child = getChildAt(i); 598 if (child.getVisibility() != GONE && child != ignore) { 599 LayoutParams params = (LayoutParams) child.getLayoutParams(); 600 if (horizontalGravity) { 601 params.mLeft += horizontalOffset; 602 params.mRight += horizontalOffset; 603 } 604 if (verticalGravity) { 605 params.mTop += verticalOffset; 606 params.mBottom += verticalOffset; 607 } 608 } 609 } 610 } 611 } 612 613 setMeasuredDimension(width, height); 614 } 615 alignBaseline(View child, LayoutParams params)616 private void alignBaseline(View child, LayoutParams params) { 617 final int layoutDirection = getLayoutDirection(); 618 int[] rules = params.getRules(layoutDirection); 619 int anchorBaseline = getRelatedViewBaseline(rules, ALIGN_BASELINE); 620 621 if (anchorBaseline != -1) { 622 LayoutParams anchorParams = getRelatedViewParams(rules, ALIGN_BASELINE); 623 if (anchorParams != null) { 624 int offset = anchorParams.mTop + anchorBaseline; 625 int baseline = child.getBaseline(); 626 if (baseline != -1) { 627 offset -= baseline; 628 } 629 int height = params.mBottom - params.mTop; 630 params.mTop = offset; 631 params.mBottom = params.mTop + height; 632 } 633 } 634 635 if (mBaselineView == null) { 636 mBaselineView = child; 637 } else { 638 LayoutParams lp = (LayoutParams) mBaselineView.getLayoutParams(); 639 if (params.mTop < lp.mTop || (params.mTop == lp.mTop && params.mLeft < lp.mLeft)) { 640 mBaselineView = child; 641 } 642 } 643 } 644 645 /** 646 * Measure a child. The child should have left, top, right and bottom information 647 * stored in its LayoutParams. If any of these values is -1 it means that the view 648 * can extend up to the corresponding edge. 649 * 650 * @param child Child to measure 651 * @param params LayoutParams associated with child 652 * @param myWidth Width of the the RelativeLayout 653 * @param myHeight Height of the RelativeLayout 654 */ measureChild(View child, LayoutParams params, int myWidth, int myHeight)655 private void measureChild(View child, LayoutParams params, int myWidth, int myHeight) { 656 int childWidthMeasureSpec = getChildMeasureSpec(params.mLeft, 657 params.mRight, params.width, 658 params.leftMargin, params.rightMargin, 659 mPaddingLeft, mPaddingRight, 660 myWidth); 661 int childHeightMeasureSpec = getChildMeasureSpec(params.mTop, 662 params.mBottom, params.height, 663 params.topMargin, params.bottomMargin, 664 mPaddingTop, mPaddingBottom, 665 myHeight); 666 child.measure(childWidthMeasureSpec, childHeightMeasureSpec); 667 } 668 measureChildHorizontal(View child, LayoutParams params, int myWidth, int myHeight)669 private void measureChildHorizontal(View child, LayoutParams params, int myWidth, int myHeight) { 670 int childWidthMeasureSpec = getChildMeasureSpec(params.mLeft, 671 params.mRight, params.width, 672 params.leftMargin, params.rightMargin, 673 mPaddingLeft, mPaddingRight, 674 myWidth); 675 int childHeightMeasureSpec; 676 if (params.width == LayoutParams.MATCH_PARENT) { 677 childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(myHeight, MeasureSpec.EXACTLY); 678 } else { 679 childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(myHeight, MeasureSpec.AT_MOST); 680 } 681 child.measure(childWidthMeasureSpec, childHeightMeasureSpec); 682 } 683 684 /** 685 * Get a measure spec that accounts for all of the constraints on this view. 686 * This includes size constraints imposed by the RelativeLayout as well as 687 * the View's desired dimension. 688 * 689 * @param childStart The left or top field of the child's layout params 690 * @param childEnd The right or bottom field of the child's layout params 691 * @param childSize The child's desired size (the width or height field of 692 * the child's layout params) 693 * @param startMargin The left or top margin 694 * @param endMargin The right or bottom margin 695 * @param startPadding mPaddingLeft or mPaddingTop 696 * @param endPadding mPaddingRight or mPaddingBottom 697 * @param mySize The width or height of this view (the RelativeLayout) 698 * @return MeasureSpec for the child 699 */ getChildMeasureSpec(int childStart, int childEnd, int childSize, int startMargin, int endMargin, int startPadding, int endPadding, int mySize)700 private int getChildMeasureSpec(int childStart, int childEnd, 701 int childSize, int startMargin, int endMargin, int startPadding, 702 int endPadding, int mySize) { 703 int childSpecMode = 0; 704 int childSpecSize = 0; 705 706 // Figure out start and end bounds. 707 int tempStart = childStart; 708 int tempEnd = childEnd; 709 710 // If the view did not express a layout constraint for an edge, use 711 // view's margins and our padding 712 if (tempStart < 0) { 713 tempStart = startPadding + startMargin; 714 } 715 if (tempEnd < 0) { 716 tempEnd = mySize - endPadding - endMargin; 717 } 718 719 // Figure out maximum size available to this view 720 int maxAvailable = tempEnd - tempStart; 721 722 if (childStart >= 0 && childEnd >= 0) { 723 // Constraints fixed both edges, so child must be an exact size 724 childSpecMode = MeasureSpec.EXACTLY; 725 childSpecSize = maxAvailable; 726 } else { 727 if (childSize >= 0) { 728 // Child wanted an exact size. Give as much as possible 729 childSpecMode = MeasureSpec.EXACTLY; 730 731 if (maxAvailable >= 0) { 732 // We have a maxmum size in this dimension. 733 childSpecSize = Math.min(maxAvailable, childSize); 734 } else { 735 // We can grow in this dimension. 736 childSpecSize = childSize; 737 } 738 } else if (childSize == LayoutParams.MATCH_PARENT) { 739 // Child wanted to be as big as possible. Give all available 740 // space 741 childSpecMode = MeasureSpec.EXACTLY; 742 childSpecSize = maxAvailable; 743 } else if (childSize == LayoutParams.WRAP_CONTENT) { 744 // Child wants to wrap content. Use AT_MOST 745 // to communicate available space if we know 746 // our max size 747 if (maxAvailable >= 0) { 748 // We have a maximum size in this dimension. 749 childSpecMode = MeasureSpec.AT_MOST; 750 childSpecSize = maxAvailable; 751 } else { 752 // We can grow in this dimension. Child can be as big as it 753 // wants 754 childSpecMode = MeasureSpec.UNSPECIFIED; 755 childSpecSize = 0; 756 } 757 } 758 } 759 760 return MeasureSpec.makeMeasureSpec(childSpecSize, childSpecMode); 761 } 762 positionChildHorizontal(View child, LayoutParams params, int myWidth, boolean wrapContent)763 private boolean positionChildHorizontal(View child, LayoutParams params, int myWidth, 764 boolean wrapContent) { 765 766 final int layoutDirection = getLayoutDirection(); 767 int[] rules = params.getRules(layoutDirection); 768 769 if (params.mLeft < 0 && params.mRight >= 0) { 770 // Right is fixed, but left varies 771 params.mLeft = params.mRight - child.getMeasuredWidth(); 772 } else if (params.mLeft >= 0 && params.mRight < 0) { 773 // Left is fixed, but right varies 774 params.mRight = params.mLeft + child.getMeasuredWidth(); 775 } else if (params.mLeft < 0 && params.mRight < 0) { 776 // Both left and right vary 777 if (rules[CENTER_IN_PARENT] != 0 || rules[CENTER_HORIZONTAL] != 0) { 778 if (!wrapContent) { 779 centerHorizontal(child, params, myWidth); 780 } else { 781 params.mLeft = mPaddingLeft + params.leftMargin; 782 params.mRight = params.mLeft + child.getMeasuredWidth(); 783 } 784 return true; 785 } else { 786 // This is the default case. For RTL we start from the right and for LTR we start 787 // from the left. This will give LEFT/TOP for LTR and RIGHT/TOP for RTL. 788 if (isLayoutRtl()) { 789 params.mRight = myWidth - mPaddingRight- params.rightMargin; 790 params.mLeft = params.mRight - child.getMeasuredWidth(); 791 } else { 792 params.mLeft = mPaddingLeft + params.leftMargin; 793 params.mRight = params.mLeft + child.getMeasuredWidth(); 794 } 795 } 796 } 797 return rules[ALIGN_PARENT_END] != 0; 798 } 799 positionChildVertical(View child, LayoutParams params, int myHeight, boolean wrapContent)800 private boolean positionChildVertical(View child, LayoutParams params, int myHeight, 801 boolean wrapContent) { 802 803 int[] rules = params.getRules(); 804 805 if (params.mTop < 0 && params.mBottom >= 0) { 806 // Bottom is fixed, but top varies 807 params.mTop = params.mBottom - child.getMeasuredHeight(); 808 } else if (params.mTop >= 0 && params.mBottom < 0) { 809 // Top is fixed, but bottom varies 810 params.mBottom = params.mTop + child.getMeasuredHeight(); 811 } else if (params.mTop < 0 && params.mBottom < 0) { 812 // Both top and bottom vary 813 if (rules[CENTER_IN_PARENT] != 0 || rules[CENTER_VERTICAL] != 0) { 814 if (!wrapContent) { 815 centerVertical(child, params, myHeight); 816 } else { 817 params.mTop = mPaddingTop + params.topMargin; 818 params.mBottom = params.mTop + child.getMeasuredHeight(); 819 } 820 return true; 821 } else { 822 params.mTop = mPaddingTop + params.topMargin; 823 params.mBottom = params.mTop + child.getMeasuredHeight(); 824 } 825 } 826 return rules[ALIGN_PARENT_BOTTOM] != 0; 827 } 828 applyHorizontalSizeRules(LayoutParams childParams, int myWidth)829 private void applyHorizontalSizeRules(LayoutParams childParams, int myWidth) { 830 final int layoutDirection = getLayoutDirection(); 831 int[] rules = childParams.getRules(layoutDirection); 832 RelativeLayout.LayoutParams anchorParams; 833 834 // -1 indicated a "soft requirement" in that direction. For example: 835 // left=10, right=-1 means the view must start at 10, but can go as far as it wants to the right 836 // left =-1, right=10 means the view must end at 10, but can go as far as it wants to the left 837 // left=10, right=20 means the left and right ends are both fixed 838 childParams.mLeft = -1; 839 childParams.mRight = -1; 840 841 anchorParams = getRelatedViewParams(rules, LEFT_OF); 842 if (anchorParams != null) { 843 childParams.mRight = anchorParams.mLeft - (anchorParams.leftMargin + 844 childParams.rightMargin); 845 } else if (childParams.alignWithParent && rules[LEFT_OF] != 0) { 846 if (myWidth >= 0) { 847 childParams.mRight = myWidth - mPaddingRight - childParams.rightMargin; 848 } else { 849 // FIXME uh oh... 850 } 851 } 852 853 anchorParams = getRelatedViewParams(rules, RIGHT_OF); 854 if (anchorParams != null) { 855 childParams.mLeft = anchorParams.mRight + (anchorParams.rightMargin + 856 childParams.leftMargin); 857 } else if (childParams.alignWithParent && rules[RIGHT_OF] != 0) { 858 childParams.mLeft = mPaddingLeft + childParams.leftMargin; 859 } 860 861 anchorParams = getRelatedViewParams(rules, ALIGN_LEFT); 862 if (anchorParams != null) { 863 childParams.mLeft = anchorParams.mLeft + childParams.leftMargin; 864 } else if (childParams.alignWithParent && rules[ALIGN_LEFT] != 0) { 865 childParams.mLeft = mPaddingLeft + childParams.leftMargin; 866 } 867 868 anchorParams = getRelatedViewParams(rules, ALIGN_RIGHT); 869 if (anchorParams != null) { 870 childParams.mRight = anchorParams.mRight - childParams.rightMargin; 871 } else if (childParams.alignWithParent && rules[ALIGN_RIGHT] != 0) { 872 if (myWidth >= 0) { 873 childParams.mRight = myWidth - mPaddingRight - childParams.rightMargin; 874 } else { 875 // FIXME uh oh... 876 } 877 } 878 879 if (0 != rules[ALIGN_PARENT_LEFT]) { 880 childParams.mLeft = mPaddingLeft + childParams.leftMargin; 881 } 882 883 if (0 != rules[ALIGN_PARENT_RIGHT]) { 884 if (myWidth >= 0) { 885 childParams.mRight = myWidth - mPaddingRight - childParams.rightMargin; 886 } else { 887 // FIXME uh oh... 888 } 889 } 890 } 891 applyVerticalSizeRules(LayoutParams childParams, int myHeight)892 private void applyVerticalSizeRules(LayoutParams childParams, int myHeight) { 893 int[] rules = childParams.getRules(); 894 RelativeLayout.LayoutParams anchorParams; 895 896 childParams.mTop = -1; 897 childParams.mBottom = -1; 898 899 anchorParams = getRelatedViewParams(rules, ABOVE); 900 if (anchorParams != null) { 901 childParams.mBottom = anchorParams.mTop - (anchorParams.topMargin + 902 childParams.bottomMargin); 903 } else if (childParams.alignWithParent && rules[ABOVE] != 0) { 904 if (myHeight >= 0) { 905 childParams.mBottom = myHeight - mPaddingBottom - childParams.bottomMargin; 906 } else { 907 // FIXME uh oh... 908 } 909 } 910 911 anchorParams = getRelatedViewParams(rules, BELOW); 912 if (anchorParams != null) { 913 childParams.mTop = anchorParams.mBottom + (anchorParams.bottomMargin + 914 childParams.topMargin); 915 } else if (childParams.alignWithParent && rules[BELOW] != 0) { 916 childParams.mTop = mPaddingTop + childParams.topMargin; 917 } 918 919 anchorParams = getRelatedViewParams(rules, ALIGN_TOP); 920 if (anchorParams != null) { 921 childParams.mTop = anchorParams.mTop + childParams.topMargin; 922 } else if (childParams.alignWithParent && rules[ALIGN_TOP] != 0) { 923 childParams.mTop = mPaddingTop + childParams.topMargin; 924 } 925 926 anchorParams = getRelatedViewParams(rules, ALIGN_BOTTOM); 927 if (anchorParams != null) { 928 childParams.mBottom = anchorParams.mBottom - childParams.bottomMargin; 929 } else if (childParams.alignWithParent && rules[ALIGN_BOTTOM] != 0) { 930 if (myHeight >= 0) { 931 childParams.mBottom = myHeight - mPaddingBottom - childParams.bottomMargin; 932 } else { 933 // FIXME uh oh... 934 } 935 } 936 937 if (0 != rules[ALIGN_PARENT_TOP]) { 938 childParams.mTop = mPaddingTop + childParams.topMargin; 939 } 940 941 if (0 != rules[ALIGN_PARENT_BOTTOM]) { 942 if (myHeight >= 0) { 943 childParams.mBottom = myHeight - mPaddingBottom - childParams.bottomMargin; 944 } else { 945 // FIXME uh oh... 946 } 947 } 948 949 if (rules[ALIGN_BASELINE] != 0) { 950 mHasBaselineAlignedChild = true; 951 } 952 } 953 getRelatedView(int[] rules, int relation)954 private View getRelatedView(int[] rules, int relation) { 955 int id = rules[relation]; 956 if (id != 0) { 957 DependencyGraph.Node node = mGraph.mKeyNodes.get(id); 958 if (node == null) return null; 959 View v = node.view; 960 961 // Find the first non-GONE view up the chain 962 while (v.getVisibility() == View.GONE) { 963 rules = ((LayoutParams) v.getLayoutParams()).getRules(v.getLayoutDirection()); 964 node = mGraph.mKeyNodes.get((rules[relation])); 965 if (node == null) return null; 966 v = node.view; 967 } 968 969 return v; 970 } 971 972 return null; 973 } 974 getRelatedViewParams(int[] rules, int relation)975 private LayoutParams getRelatedViewParams(int[] rules, int relation) { 976 View v = getRelatedView(rules, relation); 977 if (v != null) { 978 ViewGroup.LayoutParams params = v.getLayoutParams(); 979 if (params instanceof LayoutParams) { 980 return (LayoutParams) v.getLayoutParams(); 981 } 982 } 983 return null; 984 } 985 getRelatedViewBaseline(int[] rules, int relation)986 private int getRelatedViewBaseline(int[] rules, int relation) { 987 View v = getRelatedView(rules, relation); 988 if (v != null) { 989 return v.getBaseline(); 990 } 991 return -1; 992 } 993 centerHorizontal(View child, LayoutParams params, int myWidth)994 private void centerHorizontal(View child, LayoutParams params, int myWidth) { 995 int childWidth = child.getMeasuredWidth(); 996 int left = (myWidth - childWidth) / 2; 997 998 params.mLeft = left; 999 params.mRight = left + childWidth; 1000 } 1001 centerVertical(View child, LayoutParams params, int myHeight)1002 private void centerVertical(View child, LayoutParams params, int myHeight) { 1003 int childHeight = child.getMeasuredHeight(); 1004 int top = (myHeight - childHeight) / 2; 1005 1006 params.mTop = top; 1007 params.mBottom = top + childHeight; 1008 } 1009 1010 @Override onLayout(boolean changed, int l, int t, int r, int b)1011 protected void onLayout(boolean changed, int l, int t, int r, int b) { 1012 // The layout has actually already been performed and the positions 1013 // cached. Apply the cached values to the children. 1014 final int count = getChildCount(); 1015 1016 for (int i = 0; i < count; i++) { 1017 View child = getChildAt(i); 1018 if (child.getVisibility() != GONE) { 1019 RelativeLayout.LayoutParams st = 1020 (RelativeLayout.LayoutParams) child.getLayoutParams(); 1021 child.layout(st.mLeft, st.mTop, st.mRight, st.mBottom); 1022 } 1023 } 1024 } 1025 1026 @Override generateLayoutParams(AttributeSet attrs)1027 public LayoutParams generateLayoutParams(AttributeSet attrs) { 1028 return new RelativeLayout.LayoutParams(getContext(), attrs); 1029 } 1030 1031 /** 1032 * Returns a set of layout parameters with a width of 1033 * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT}, 1034 * a height of {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} and no spanning. 1035 */ 1036 @Override generateDefaultLayoutParams()1037 protected ViewGroup.LayoutParams generateDefaultLayoutParams() { 1038 return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); 1039 } 1040 1041 // Override to allow type-checking of LayoutParams. 1042 @Override checkLayoutParams(ViewGroup.LayoutParams p)1043 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { 1044 return p instanceof RelativeLayout.LayoutParams; 1045 } 1046 1047 @Override generateLayoutParams(ViewGroup.LayoutParams p)1048 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { 1049 return new LayoutParams(p); 1050 } 1051 1052 @Override dispatchPopulateAccessibilityEvent(AccessibilityEvent event)1053 public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { 1054 if (mTopToBottomLeftToRightSet == null) { 1055 mTopToBottomLeftToRightSet = new TreeSet<View>(new TopToBottomLeftToRightComparator()); 1056 } 1057 1058 // sort children top-to-bottom and left-to-right 1059 for (int i = 0, count = getChildCount(); i < count; i++) { 1060 mTopToBottomLeftToRightSet.add(getChildAt(i)); 1061 } 1062 1063 for (View view : mTopToBottomLeftToRightSet) { 1064 if (view.getVisibility() == View.VISIBLE 1065 && view.dispatchPopulateAccessibilityEvent(event)) { 1066 mTopToBottomLeftToRightSet.clear(); 1067 return true; 1068 } 1069 } 1070 1071 mTopToBottomLeftToRightSet.clear(); 1072 return false; 1073 } 1074 1075 @Override onInitializeAccessibilityEvent(AccessibilityEvent event)1076 public void onInitializeAccessibilityEvent(AccessibilityEvent event) { 1077 super.onInitializeAccessibilityEvent(event); 1078 event.setClassName(RelativeLayout.class.getName()); 1079 } 1080 1081 @Override onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info)1082 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { 1083 super.onInitializeAccessibilityNodeInfo(info); 1084 info.setClassName(RelativeLayout.class.getName()); 1085 } 1086 1087 /** 1088 * Compares two views in left-to-right and top-to-bottom fashion. 1089 */ 1090 private class TopToBottomLeftToRightComparator implements Comparator<View> { compare(View first, View second)1091 public int compare(View first, View second) { 1092 // top - bottom 1093 int topDifference = first.getTop() - second.getTop(); 1094 if (topDifference != 0) { 1095 return topDifference; 1096 } 1097 // left - right 1098 int leftDifference = first.getLeft() - second.getLeft(); 1099 if (leftDifference != 0) { 1100 return leftDifference; 1101 } 1102 // break tie by height 1103 int heightDiference = first.getHeight() - second.getHeight(); 1104 if (heightDiference != 0) { 1105 return heightDiference; 1106 } 1107 // break tie by width 1108 int widthDiference = first.getWidth() - second.getWidth(); 1109 if (widthDiference != 0) { 1110 return widthDiference; 1111 } 1112 return 0; 1113 } 1114 } 1115 1116 /** 1117 * Per-child layout information associated with RelativeLayout. 1118 * 1119 * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignWithParentIfMissing 1120 * @attr ref android.R.styleable#RelativeLayout_Layout_layout_toLeftOf 1121 * @attr ref android.R.styleable#RelativeLayout_Layout_layout_toRightOf 1122 * @attr ref android.R.styleable#RelativeLayout_Layout_layout_above 1123 * @attr ref android.R.styleable#RelativeLayout_Layout_layout_below 1124 * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignBaseline 1125 * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignLeft 1126 * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignTop 1127 * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignRight 1128 * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignBottom 1129 * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignParentLeft 1130 * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignParentTop 1131 * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignParentRight 1132 * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignParentBottom 1133 * @attr ref android.R.styleable#RelativeLayout_Layout_layout_centerInParent 1134 * @attr ref android.R.styleable#RelativeLayout_Layout_layout_centerHorizontal 1135 * @attr ref android.R.styleable#RelativeLayout_Layout_layout_centerVertical 1136 * @attr ref android.R.styleable#RelativeLayout_Layout_layout_toStartOf 1137 * @attr ref android.R.styleable#RelativeLayout_Layout_layout_toEndOf 1138 * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignStart 1139 * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignEnd 1140 * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignParentStart 1141 * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignParentEnd 1142 */ 1143 public static class LayoutParams extends ViewGroup.MarginLayoutParams { 1144 @ViewDebug.ExportedProperty(category = "layout", resolveId = true, indexMapping = { 1145 @ViewDebug.IntToString(from = ABOVE, to = "above"), 1146 @ViewDebug.IntToString(from = ALIGN_BASELINE, to = "alignBaseline"), 1147 @ViewDebug.IntToString(from = ALIGN_BOTTOM, to = "alignBottom"), 1148 @ViewDebug.IntToString(from = ALIGN_LEFT, to = "alignLeft"), 1149 @ViewDebug.IntToString(from = ALIGN_PARENT_BOTTOM, to = "alignParentBottom"), 1150 @ViewDebug.IntToString(from = ALIGN_PARENT_LEFT, to = "alignParentLeft"), 1151 @ViewDebug.IntToString(from = ALIGN_PARENT_RIGHT, to = "alignParentRight"), 1152 @ViewDebug.IntToString(from = ALIGN_PARENT_TOP, to = "alignParentTop"), 1153 @ViewDebug.IntToString(from = ALIGN_RIGHT, to = "alignRight"), 1154 @ViewDebug.IntToString(from = ALIGN_TOP, to = "alignTop"), 1155 @ViewDebug.IntToString(from = BELOW, to = "below"), 1156 @ViewDebug.IntToString(from = CENTER_HORIZONTAL, to = "centerHorizontal"), 1157 @ViewDebug.IntToString(from = CENTER_IN_PARENT, to = "center"), 1158 @ViewDebug.IntToString(from = CENTER_VERTICAL, to = "centerVertical"), 1159 @ViewDebug.IntToString(from = LEFT_OF, to = "leftOf"), 1160 @ViewDebug.IntToString(from = RIGHT_OF, to = "rightOf"), 1161 @ViewDebug.IntToString(from = ALIGN_START, to = "alignStart"), 1162 @ViewDebug.IntToString(from = ALIGN_END, to = "alignEnd"), 1163 @ViewDebug.IntToString(from = ALIGN_PARENT_START, to = "alignParentStart"), 1164 @ViewDebug.IntToString(from = ALIGN_PARENT_END, to = "alignParentEnd"), 1165 @ViewDebug.IntToString(from = START_OF, to = "startOf"), 1166 @ViewDebug.IntToString(from = END_OF, to = "endOf") 1167 }, mapping = { 1168 @ViewDebug.IntToString(from = TRUE, to = "true"), 1169 @ViewDebug.IntToString(from = 0, to = "false/NO_ID") 1170 }) 1171 1172 private int[] mRules = new int[VERB_COUNT]; 1173 private int[] mInitialRules = new int[VERB_COUNT]; 1174 1175 private int mLeft, mTop, mRight, mBottom; 1176 1177 private int mStart = DEFAULT_RELATIVE; 1178 private int mEnd = DEFAULT_RELATIVE; 1179 1180 private boolean mRulesChanged = false; 1181 1182 /** 1183 * When true, uses the parent as the anchor if the anchor doesn't exist or if 1184 * the anchor's visibility is GONE. 1185 */ 1186 @ViewDebug.ExportedProperty(category = "layout") 1187 public boolean alignWithParent; 1188 LayoutParams(Context c, AttributeSet attrs)1189 public LayoutParams(Context c, AttributeSet attrs) { 1190 super(c, attrs); 1191 1192 TypedArray a = c.obtainStyledAttributes(attrs, 1193 com.android.internal.R.styleable.RelativeLayout_Layout); 1194 1195 final int[] rules = mRules; 1196 final int[] initialRules = mInitialRules; 1197 1198 final int N = a.getIndexCount(); 1199 for (int i = 0; i < N; i++) { 1200 int attr = a.getIndex(i); 1201 switch (attr) { 1202 case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignWithParentIfMissing: 1203 alignWithParent = a.getBoolean(attr, false); 1204 break; 1205 case com.android.internal.R.styleable.RelativeLayout_Layout_layout_toLeftOf: 1206 rules[LEFT_OF] = a.getResourceId(attr, 0); 1207 break; 1208 case com.android.internal.R.styleable.RelativeLayout_Layout_layout_toRightOf: 1209 rules[RIGHT_OF] = a.getResourceId(attr, 0); 1210 break; 1211 case com.android.internal.R.styleable.RelativeLayout_Layout_layout_above: 1212 rules[ABOVE] = a.getResourceId(attr, 0); 1213 break; 1214 case com.android.internal.R.styleable.RelativeLayout_Layout_layout_below: 1215 rules[BELOW] = a.getResourceId(attr, 0); 1216 break; 1217 case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignBaseline: 1218 rules[ALIGN_BASELINE] = a.getResourceId(attr, 0); 1219 break; 1220 case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignLeft: 1221 rules[ALIGN_LEFT] = a.getResourceId(attr, 0); 1222 break; 1223 case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignTop: 1224 rules[ALIGN_TOP] = a.getResourceId(attr, 0); 1225 break; 1226 case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignRight: 1227 rules[ALIGN_RIGHT] = a.getResourceId(attr, 0); 1228 break; 1229 case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignBottom: 1230 rules[ALIGN_BOTTOM] = a.getResourceId(attr, 0); 1231 break; 1232 case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignParentLeft: 1233 rules[ALIGN_PARENT_LEFT] = a.getBoolean(attr, false) ? TRUE : 0; 1234 break; 1235 case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignParentTop: 1236 rules[ALIGN_PARENT_TOP] = a.getBoolean(attr, false) ? TRUE : 0; 1237 break; 1238 case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignParentRight: 1239 rules[ALIGN_PARENT_RIGHT] = a.getBoolean(attr, false) ? TRUE : 0; 1240 break; 1241 case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignParentBottom: 1242 rules[ALIGN_PARENT_BOTTOM] = a.getBoolean(attr, false) ? TRUE : 0; 1243 break; 1244 case com.android.internal.R.styleable.RelativeLayout_Layout_layout_centerInParent: 1245 rules[CENTER_IN_PARENT] = a.getBoolean(attr, false) ? TRUE : 0; 1246 break; 1247 case com.android.internal.R.styleable.RelativeLayout_Layout_layout_centerHorizontal: 1248 rules[CENTER_HORIZONTAL] = a.getBoolean(attr, false) ? TRUE : 0; 1249 break; 1250 case com.android.internal.R.styleable.RelativeLayout_Layout_layout_centerVertical: 1251 rules[CENTER_VERTICAL] = a.getBoolean(attr, false) ? TRUE : 0; 1252 break; 1253 case com.android.internal.R.styleable.RelativeLayout_Layout_layout_toStartOf: 1254 rules[START_OF] = a.getResourceId(attr, 0); 1255 break; 1256 case com.android.internal.R.styleable.RelativeLayout_Layout_layout_toEndOf: 1257 rules[END_OF] = a.getResourceId(attr, 0); 1258 break; 1259 case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignStart: 1260 rules[ALIGN_START] = a.getResourceId(attr, 0); 1261 break; 1262 case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignEnd: 1263 rules[ALIGN_END] = a.getResourceId(attr, 0); 1264 break; 1265 case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignParentStart: 1266 rules[ALIGN_PARENT_START] = a.getBoolean(attr, false) ? TRUE : 0; 1267 break; 1268 case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignParentEnd: 1269 rules[ALIGN_PARENT_END] = a.getBoolean(attr, false) ? TRUE : 0; 1270 break; 1271 } 1272 } 1273 1274 for (int n = LEFT_OF; n < VERB_COUNT; n++) { 1275 initialRules[n] = rules[n]; 1276 } 1277 1278 a.recycle(); 1279 } 1280 LayoutParams(int w, int h)1281 public LayoutParams(int w, int h) { 1282 super(w, h); 1283 } 1284 1285 /** 1286 * {@inheritDoc} 1287 */ LayoutParams(ViewGroup.LayoutParams source)1288 public LayoutParams(ViewGroup.LayoutParams source) { 1289 super(source); 1290 } 1291 1292 /** 1293 * {@inheritDoc} 1294 */ LayoutParams(ViewGroup.MarginLayoutParams source)1295 public LayoutParams(ViewGroup.MarginLayoutParams source) { 1296 super(source); 1297 } 1298 1299 @Override debug(String output)1300 public String debug(String output) { 1301 return output + "ViewGroup.LayoutParams={ width=" + sizeToString(width) + 1302 ", height=" + sizeToString(height) + " }"; 1303 } 1304 1305 /** 1306 * Adds a layout rule to be interpreted by the RelativeLayout. This 1307 * method should only be used for constraints that don't refer to another sibling 1308 * (e.g., CENTER_IN_PARENT) or take a boolean value ({@link RelativeLayout#TRUE} 1309 * for true or 0 for false). To specify a verb that takes a subject, use 1310 * {@link #addRule(int, int)} instead. 1311 * 1312 * @param verb One of the verbs defined by 1313 * {@link android.widget.RelativeLayout RelativeLayout}, such as 1314 * ALIGN_WITH_PARENT_LEFT. 1315 * @see #addRule(int, int) 1316 */ addRule(int verb)1317 public void addRule(int verb) { 1318 mRules[verb] = TRUE; 1319 mInitialRules[verb] = TRUE; 1320 mRulesChanged = true; 1321 } 1322 1323 /** 1324 * Adds a layout rule to be interpreted by the RelativeLayout. Use this for 1325 * verbs that take a target, such as a sibling (ALIGN_RIGHT) or a boolean 1326 * value (VISIBLE). 1327 * 1328 * @param verb One of the verbs defined by 1329 * {@link android.widget.RelativeLayout RelativeLayout}, such as 1330 * ALIGN_WITH_PARENT_LEFT. 1331 * @param anchor The id of another view to use as an anchor, 1332 * or a boolean value(represented as {@link RelativeLayout#TRUE}) 1333 * for true or 0 for false). For verbs that don't refer to another sibling 1334 * (for example, ALIGN_WITH_PARENT_BOTTOM) just use -1. 1335 * @see #addRule(int) 1336 */ addRule(int verb, int anchor)1337 public void addRule(int verb, int anchor) { 1338 mRules[verb] = anchor; 1339 mInitialRules[verb] = anchor; 1340 mRulesChanged = true; 1341 } 1342 1343 /** 1344 * Removes a layout rule to be interpreted by the RelativeLayout. 1345 * 1346 * @param verb One of the verbs defined by 1347 * {@link android.widget.RelativeLayout RelativeLayout}, such as 1348 * ALIGN_WITH_PARENT_LEFT. 1349 * @see #addRule(int) 1350 * @see #addRule(int, int) 1351 */ removeRule(int verb)1352 public void removeRule(int verb) { 1353 mRules[verb] = 0; 1354 mInitialRules[verb] = 0; 1355 mRulesChanged = true; 1356 } 1357 hasRelativeRules()1358 private boolean hasRelativeRules() { 1359 return (mInitialRules[START_OF] != 0 || mInitialRules[END_OF] != 0 || 1360 mInitialRules[ALIGN_START] != 0 || mInitialRules[ALIGN_END] != 0 || 1361 mInitialRules[ALIGN_PARENT_START] != 0 || mInitialRules[ALIGN_PARENT_END] != 0); 1362 } 1363 resolveRules(int layoutDirection)1364 private void resolveRules(int layoutDirection) { 1365 final boolean isLayoutRtl = (layoutDirection == View.LAYOUT_DIRECTION_RTL); 1366 // Reset to initial state 1367 for (int n = LEFT_OF; n < VERB_COUNT; n++) { 1368 mRules[n] = mInitialRules[n]; 1369 } 1370 // Apply rules depending on direction 1371 if (mRules[ALIGN_START] != 0) { 1372 mRules[isLayoutRtl ? ALIGN_RIGHT : ALIGN_LEFT] = mRules[ALIGN_START]; 1373 } 1374 if (mRules[ALIGN_END] != 0) { 1375 mRules[isLayoutRtl ? ALIGN_LEFT : ALIGN_RIGHT] = mRules[ALIGN_END]; 1376 } 1377 if (mRules[START_OF] != 0) { 1378 mRules[isLayoutRtl ? RIGHT_OF : LEFT_OF] = mRules[START_OF]; 1379 } 1380 if (mRules[END_OF] != 0) { 1381 mRules[isLayoutRtl ? LEFT_OF : RIGHT_OF] = mRules[END_OF]; 1382 } 1383 if (mRules[ALIGN_PARENT_START] != 0) { 1384 mRules[isLayoutRtl ? ALIGN_PARENT_RIGHT : ALIGN_PARENT_LEFT] = mRules[ALIGN_PARENT_START]; 1385 } 1386 if (mRules[ALIGN_PARENT_END] != 0) { 1387 mRules[isLayoutRtl ? ALIGN_PARENT_LEFT : ALIGN_PARENT_RIGHT] = mRules[ALIGN_PARENT_END]; 1388 } 1389 mRulesChanged = false; 1390 } 1391 1392 /** 1393 * Retrieves a complete list of all supported rules, where the index is the rule 1394 * verb, and the element value is the value specified, or "false" if it was never 1395 * set. If there are relative rules defined (*_START / *_END), they will be resolved 1396 * depending on the layout direction. 1397 * 1398 * @param layoutDirection the direction of the layout. 1399 * Should be either {@link View#LAYOUT_DIRECTION_LTR} 1400 * or {@link View#LAYOUT_DIRECTION_RTL} 1401 * @return the supported rules 1402 * @see #addRule(int, int) 1403 * 1404 * @hide 1405 */ getRules(int layoutDirection)1406 public int[] getRules(int layoutDirection) { 1407 if (hasRelativeRules() && 1408 (mRulesChanged || layoutDirection != getLayoutDirection())) { 1409 resolveRules(layoutDirection); 1410 if (layoutDirection != getLayoutDirection()) { 1411 setLayoutDirection(layoutDirection); 1412 } 1413 } 1414 return mRules; 1415 } 1416 1417 /** 1418 * Retrieves a complete list of all supported rules, where the index is the rule 1419 * verb, and the element value is the value specified, or "false" if it was never 1420 * set. There will be no resolution of relative rules done. 1421 * 1422 * @return the supported rules 1423 * @see #addRule(int, int) 1424 */ getRules()1425 public int[] getRules() { 1426 return mRules; 1427 } 1428 1429 @Override resolveLayoutDirection(int layoutDirection)1430 public void resolveLayoutDirection(int layoutDirection) { 1431 final boolean isLayoutRtl = isLayoutRtl(); 1432 if (isLayoutRtl) { 1433 if (mStart != DEFAULT_RELATIVE) mRight = mStart; 1434 if (mEnd != DEFAULT_RELATIVE) mLeft = mEnd; 1435 } else { 1436 if (mStart != DEFAULT_RELATIVE) mLeft = mStart; 1437 if (mEnd != DEFAULT_RELATIVE) mRight = mEnd; 1438 } 1439 1440 if (hasRelativeRules() && layoutDirection != getLayoutDirection()) { 1441 resolveRules(layoutDirection); 1442 } 1443 // This will set the layout direction 1444 super.resolveLayoutDirection(layoutDirection); 1445 } 1446 } 1447 1448 private static class DependencyGraph { 1449 /** 1450 * List of all views in the graph. 1451 */ 1452 private ArrayList<Node> mNodes = new ArrayList<Node>(); 1453 1454 /** 1455 * List of nodes in the graph. Each node is identified by its 1456 * view id (see View#getId()). 1457 */ 1458 private SparseArray<Node> mKeyNodes = new SparseArray<Node>(); 1459 1460 /** 1461 * Temporary data structure used to build the list of roots 1462 * for this graph. 1463 */ 1464 private ArrayDeque<Node> mRoots = new ArrayDeque<Node>(); 1465 1466 /** 1467 * Clears the graph. 1468 */ clear()1469 void clear() { 1470 final ArrayList<Node> nodes = mNodes; 1471 final int count = nodes.size(); 1472 1473 for (int i = 0; i < count; i++) { 1474 nodes.get(i).release(); 1475 } 1476 nodes.clear(); 1477 1478 mKeyNodes.clear(); 1479 mRoots.clear(); 1480 } 1481 1482 /** 1483 * Adds a view to the graph. 1484 * 1485 * @param view The view to be added as a node to the graph. 1486 */ add(View view)1487 void add(View view) { 1488 final int id = view.getId(); 1489 final Node node = Node.acquire(view); 1490 1491 if (id != View.NO_ID) { 1492 mKeyNodes.put(id, node); 1493 } 1494 1495 mNodes.add(node); 1496 } 1497 1498 /** 1499 * Builds a sorted list of views. The sorting order depends on the dependencies 1500 * between the view. For instance, if view C needs view A to be processed first 1501 * and view A needs view B to be processed first, the dependency graph 1502 * is: B -> A -> C. The sorted array will contain views B, A and C in this order. 1503 * 1504 * @param sorted The sorted list of views. The length of this array must 1505 * be equal to getChildCount(). 1506 * @param rules The list of rules to take into account. 1507 */ getSortedViews(View[] sorted, int... rules)1508 void getSortedViews(View[] sorted, int... rules) { 1509 final ArrayDeque<Node> roots = findRoots(rules); 1510 int index = 0; 1511 1512 Node node; 1513 while ((node = roots.pollLast()) != null) { 1514 final View view = node.view; 1515 final int key = view.getId(); 1516 1517 sorted[index++] = view; 1518 1519 final HashMap<Node, DependencyGraph> dependents = node.dependents; 1520 for (Node dependent : dependents.keySet()) { 1521 final SparseArray<Node> dependencies = dependent.dependencies; 1522 1523 dependencies.remove(key); 1524 if (dependencies.size() == 0) { 1525 roots.add(dependent); 1526 } 1527 } 1528 } 1529 1530 if (index < sorted.length) { 1531 throw new IllegalStateException("Circular dependencies cannot exist" 1532 + " in RelativeLayout"); 1533 } 1534 } 1535 1536 /** 1537 * Finds the roots of the graph. A root is a node with no dependency and 1538 * with [0..n] dependents. 1539 * 1540 * @param rulesFilter The list of rules to consider when building the 1541 * dependencies 1542 * 1543 * @return A list of node, each being a root of the graph 1544 */ findRoots(int[] rulesFilter)1545 private ArrayDeque<Node> findRoots(int[] rulesFilter) { 1546 final SparseArray<Node> keyNodes = mKeyNodes; 1547 final ArrayList<Node> nodes = mNodes; 1548 final int count = nodes.size(); 1549 1550 // Find roots can be invoked several times, so make sure to clear 1551 // all dependents and dependencies before running the algorithm 1552 for (int i = 0; i < count; i++) { 1553 final Node node = nodes.get(i); 1554 node.dependents.clear(); 1555 node.dependencies.clear(); 1556 } 1557 1558 // Builds up the dependents and dependencies for each node of the graph 1559 for (int i = 0; i < count; i++) { 1560 final Node node = nodes.get(i); 1561 1562 final LayoutParams layoutParams = (LayoutParams) node.view.getLayoutParams(); 1563 final int[] rules = layoutParams.mRules; 1564 final int rulesCount = rulesFilter.length; 1565 1566 // Look only the the rules passed in parameter, this way we build only the 1567 // dependencies for a specific set of rules 1568 for (int j = 0; j < rulesCount; j++) { 1569 final int rule = rules[rulesFilter[j]]; 1570 if (rule > 0) { 1571 // The node this node depends on 1572 final Node dependency = keyNodes.get(rule); 1573 // Skip unknowns and self dependencies 1574 if (dependency == null || dependency == node) { 1575 continue; 1576 } 1577 // Add the current node as a dependent 1578 dependency.dependents.put(node, this); 1579 // Add a dependency to the current node 1580 node.dependencies.put(rule, dependency); 1581 } 1582 } 1583 } 1584 1585 final ArrayDeque<Node> roots = mRoots; 1586 roots.clear(); 1587 1588 // Finds all the roots in the graph: all nodes with no dependencies 1589 for (int i = 0; i < count; i++) { 1590 final Node node = nodes.get(i); 1591 if (node.dependencies.size() == 0) roots.addLast(node); 1592 } 1593 1594 return roots; 1595 } 1596 1597 /** 1598 * Prints the dependency graph for the specified rules. 1599 * 1600 * @param resources The context's resources to print the ids. 1601 * @param rules The list of rules to take into account. 1602 */ log(Resources resources, int... rules)1603 void log(Resources resources, int... rules) { 1604 final ArrayDeque<Node> roots = findRoots(rules); 1605 for (Node node : roots) { 1606 printNode(resources, node); 1607 } 1608 } 1609 printViewId(Resources resources, View view)1610 static void printViewId(Resources resources, View view) { 1611 if (view.getId() != View.NO_ID) { 1612 d(LOG_TAG, resources.getResourceEntryName(view.getId())); 1613 } else { 1614 d(LOG_TAG, "NO_ID"); 1615 } 1616 } 1617 appendViewId(Resources resources, Node node, StringBuilder buffer)1618 private static void appendViewId(Resources resources, Node node, StringBuilder buffer) { 1619 if (node.view.getId() != View.NO_ID) { 1620 buffer.append(resources.getResourceEntryName(node.view.getId())); 1621 } else { 1622 buffer.append("NO_ID"); 1623 } 1624 } 1625 printNode(Resources resources, Node node)1626 private static void printNode(Resources resources, Node node) { 1627 if (node.dependents.size() == 0) { 1628 printViewId(resources, node.view); 1629 } else { 1630 for (Node dependent : node.dependents.keySet()) { 1631 StringBuilder buffer = new StringBuilder(); 1632 appendViewId(resources, node, buffer); 1633 printdependents(resources, dependent, buffer); 1634 } 1635 } 1636 } 1637 printdependents(Resources resources, Node node, StringBuilder buffer)1638 private static void printdependents(Resources resources, Node node, StringBuilder buffer) { 1639 buffer.append(" -> "); 1640 appendViewId(resources, node, buffer); 1641 1642 if (node.dependents.size() == 0) { 1643 d(LOG_TAG, buffer.toString()); 1644 } else { 1645 for (Node dependent : node.dependents.keySet()) { 1646 StringBuilder subBuffer = new StringBuilder(buffer); 1647 printdependents(resources, dependent, subBuffer); 1648 } 1649 } 1650 } 1651 1652 /** 1653 * A node in the dependency graph. A node is a view, its list of dependencies 1654 * and its list of dependents. 1655 * 1656 * A node with no dependent is considered a root of the graph. 1657 */ 1658 static class Node implements Poolable<Node> { 1659 /** 1660 * The view representing this node in the layout. 1661 */ 1662 View view; 1663 1664 /** 1665 * The list of dependents for this node; a dependent is a node 1666 * that needs this node to be processed first. 1667 */ 1668 final HashMap<Node, DependencyGraph> dependents = new HashMap<Node, DependencyGraph>(); 1669 1670 /** 1671 * The list of dependencies for this node. 1672 */ 1673 final SparseArray<Node> dependencies = new SparseArray<Node>(); 1674 1675 /* 1676 * START POOL IMPLEMENTATION 1677 */ 1678 // The pool is static, so all nodes instances are shared across 1679 // activities, that's why we give it a rather high limit 1680 private static final int POOL_LIMIT = 100; 1681 private static final Pool<Node> sPool = Pools.synchronizedPool( 1682 Pools.finitePool(new PoolableManager<Node>() { 1683 public Node newInstance() { 1684 return new Node(); 1685 } 1686 1687 public void onAcquired(Node element) { 1688 } 1689 1690 public void onReleased(Node element) { 1691 } 1692 }, POOL_LIMIT) 1693 ); 1694 1695 private Node mNext; 1696 private boolean mIsPooled; 1697 setNextPoolable(Node element)1698 public void setNextPoolable(Node element) { 1699 mNext = element; 1700 } 1701 getNextPoolable()1702 public Node getNextPoolable() { 1703 return mNext; 1704 } 1705 isPooled()1706 public boolean isPooled() { 1707 return mIsPooled; 1708 } 1709 setPooled(boolean isPooled)1710 public void setPooled(boolean isPooled) { 1711 mIsPooled = isPooled; 1712 } 1713 acquire(View view)1714 static Node acquire(View view) { 1715 final Node node = sPool.acquire(); 1716 node.view = view; 1717 1718 return node; 1719 } 1720 release()1721 void release() { 1722 view = null; 1723 dependents.clear(); 1724 dependencies.clear(); 1725 1726 sPool.release(this); 1727 } 1728 /* 1729 * END POOL IMPLEMENTATION 1730 */ 1731 } 1732 } 1733 } 1734