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