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 positionAtEdge(child, params, myWidth); 837 } 838 return true; 839 } else { 840 // This is the default case. For RTL we start from the right and for LTR we start 841 // from the left. This will give LEFT/TOP for LTR and RIGHT/TOP for RTL. 842 positionAtEdge(child, params, myWidth); 843 } 844 } 845 return rules[ALIGN_PARENT_END] != 0; 846 } 847 positionAtEdge(View child, LayoutParams params, int myWidth)848 private void positionAtEdge(View child, LayoutParams params, int myWidth) { 849 if (isLayoutRtl()) { 850 params.mRight = myWidth - mPaddingRight - params.rightMargin; 851 params.mLeft = params.mRight - child.getMeasuredWidth(); 852 } else { 853 params.mLeft = mPaddingLeft + params.leftMargin; 854 params.mRight = params.mLeft + child.getMeasuredWidth(); 855 } 856 } 857 positionChildVertical(View child, LayoutParams params, int myHeight, boolean wrapContent)858 private boolean positionChildVertical(View child, LayoutParams params, int myHeight, 859 boolean wrapContent) { 860 861 int[] rules = params.getRules(); 862 863 if (params.mTop == VALUE_NOT_SET && params.mBottom != VALUE_NOT_SET) { 864 // Bottom is fixed, but top varies 865 params.mTop = params.mBottom - child.getMeasuredHeight(); 866 } else if (params.mTop != VALUE_NOT_SET && params.mBottom == VALUE_NOT_SET) { 867 // Top is fixed, but bottom varies 868 params.mBottom = params.mTop + child.getMeasuredHeight(); 869 } else if (params.mTop == VALUE_NOT_SET && params.mBottom == VALUE_NOT_SET) { 870 // Both top and bottom vary 871 if (rules[CENTER_IN_PARENT] != 0 || rules[CENTER_VERTICAL] != 0) { 872 if (!wrapContent) { 873 centerVertical(child, params, myHeight); 874 } else { 875 params.mTop = mPaddingTop + params.topMargin; 876 params.mBottom = params.mTop + child.getMeasuredHeight(); 877 } 878 return true; 879 } else { 880 params.mTop = mPaddingTop + params.topMargin; 881 params.mBottom = params.mTop + child.getMeasuredHeight(); 882 } 883 } 884 return rules[ALIGN_PARENT_BOTTOM] != 0; 885 } 886 applyHorizontalSizeRules(LayoutParams childParams, int myWidth, int[] rules)887 private void applyHorizontalSizeRules(LayoutParams childParams, int myWidth, int[] rules) { 888 RelativeLayout.LayoutParams anchorParams; 889 890 // VALUE_NOT_SET indicates a "soft requirement" in that direction. For example: 891 // left=10, right=VALUE_NOT_SET means the view must start at 10, but can go as far as it 892 // wants to the right 893 // left=VALUE_NOT_SET, right=10 means the view must end at 10, but can go as far as it 894 // wants to the left 895 // left=10, right=20 means the left and right ends are both fixed 896 childParams.mLeft = VALUE_NOT_SET; 897 childParams.mRight = VALUE_NOT_SET; 898 899 anchorParams = getRelatedViewParams(rules, LEFT_OF); 900 if (anchorParams != null) { 901 childParams.mRight = anchorParams.mLeft - (anchorParams.leftMargin + 902 childParams.rightMargin); 903 } else if (childParams.alignWithParent && rules[LEFT_OF] != 0) { 904 if (myWidth >= 0) { 905 childParams.mRight = myWidth - mPaddingRight - childParams.rightMargin; 906 } 907 } 908 909 anchorParams = getRelatedViewParams(rules, RIGHT_OF); 910 if (anchorParams != null) { 911 childParams.mLeft = anchorParams.mRight + (anchorParams.rightMargin + 912 childParams.leftMargin); 913 } else if (childParams.alignWithParent && rules[RIGHT_OF] != 0) { 914 childParams.mLeft = mPaddingLeft + childParams.leftMargin; 915 } 916 917 anchorParams = getRelatedViewParams(rules, ALIGN_LEFT); 918 if (anchorParams != null) { 919 childParams.mLeft = anchorParams.mLeft + childParams.leftMargin; 920 } else if (childParams.alignWithParent && rules[ALIGN_LEFT] != 0) { 921 childParams.mLeft = mPaddingLeft + childParams.leftMargin; 922 } 923 924 anchorParams = getRelatedViewParams(rules, ALIGN_RIGHT); 925 if (anchorParams != null) { 926 childParams.mRight = anchorParams.mRight - childParams.rightMargin; 927 } else if (childParams.alignWithParent && rules[ALIGN_RIGHT] != 0) { 928 if (myWidth >= 0) { 929 childParams.mRight = myWidth - mPaddingRight - childParams.rightMargin; 930 } 931 } 932 933 if (0 != rules[ALIGN_PARENT_LEFT]) { 934 childParams.mLeft = mPaddingLeft + childParams.leftMargin; 935 } 936 937 if (0 != rules[ALIGN_PARENT_RIGHT]) { 938 if (myWidth >= 0) { 939 childParams.mRight = myWidth - mPaddingRight - childParams.rightMargin; 940 } 941 } 942 } 943 applyVerticalSizeRules(LayoutParams childParams, int myHeight, int myBaseline)944 private void applyVerticalSizeRules(LayoutParams childParams, int myHeight, int myBaseline) { 945 final int[] rules = childParams.getRules(); 946 947 // Baseline alignment overrides any explicitly specified top or bottom. 948 int baselineOffset = getRelatedViewBaselineOffset(rules); 949 if (baselineOffset != -1) { 950 if (myBaseline != -1) { 951 baselineOffset -= myBaseline; 952 } 953 childParams.mTop = baselineOffset; 954 childParams.mBottom = VALUE_NOT_SET; 955 return; 956 } 957 958 RelativeLayout.LayoutParams anchorParams; 959 960 childParams.mTop = VALUE_NOT_SET; 961 childParams.mBottom = VALUE_NOT_SET; 962 963 anchorParams = getRelatedViewParams(rules, ABOVE); 964 if (anchorParams != null) { 965 childParams.mBottom = anchorParams.mTop - (anchorParams.topMargin + 966 childParams.bottomMargin); 967 } else if (childParams.alignWithParent && rules[ABOVE] != 0) { 968 if (myHeight >= 0) { 969 childParams.mBottom = myHeight - mPaddingBottom - childParams.bottomMargin; 970 } 971 } 972 973 anchorParams = getRelatedViewParams(rules, BELOW); 974 if (anchorParams != null) { 975 childParams.mTop = anchorParams.mBottom + (anchorParams.bottomMargin + 976 childParams.topMargin); 977 } else if (childParams.alignWithParent && rules[BELOW] != 0) { 978 childParams.mTop = mPaddingTop + childParams.topMargin; 979 } 980 981 anchorParams = getRelatedViewParams(rules, ALIGN_TOP); 982 if (anchorParams != null) { 983 childParams.mTop = anchorParams.mTop + childParams.topMargin; 984 } else if (childParams.alignWithParent && rules[ALIGN_TOP] != 0) { 985 childParams.mTop = mPaddingTop + childParams.topMargin; 986 } 987 988 anchorParams = getRelatedViewParams(rules, ALIGN_BOTTOM); 989 if (anchorParams != null) { 990 childParams.mBottom = anchorParams.mBottom - childParams.bottomMargin; 991 } else if (childParams.alignWithParent && rules[ALIGN_BOTTOM] != 0) { 992 if (myHeight >= 0) { 993 childParams.mBottom = myHeight - mPaddingBottom - childParams.bottomMargin; 994 } 995 } 996 997 if (0 != rules[ALIGN_PARENT_TOP]) { 998 childParams.mTop = mPaddingTop + childParams.topMargin; 999 } 1000 1001 if (0 != rules[ALIGN_PARENT_BOTTOM]) { 1002 if (myHeight >= 0) { 1003 childParams.mBottom = myHeight - mPaddingBottom - childParams.bottomMargin; 1004 } 1005 } 1006 } 1007 getRelatedView(int[] rules, int relation)1008 private View getRelatedView(int[] rules, int relation) { 1009 int id = rules[relation]; 1010 if (id != 0) { 1011 DependencyGraph.Node node = mGraph.mKeyNodes.get(id); 1012 if (node == null) return null; 1013 View v = node.view; 1014 1015 // Find the first non-GONE view up the chain 1016 while (v.getVisibility() == View.GONE) { 1017 rules = ((LayoutParams) v.getLayoutParams()).getRules(v.getLayoutDirection()); 1018 node = mGraph.mKeyNodes.get((rules[relation])); 1019 // ignore self dependency. for more info look in git commit: da3003 1020 if (node == null || v == node.view) return null; 1021 v = node.view; 1022 } 1023 1024 return v; 1025 } 1026 1027 return null; 1028 } 1029 getRelatedViewParams(int[] rules, int relation)1030 private LayoutParams getRelatedViewParams(int[] rules, int relation) { 1031 View v = getRelatedView(rules, relation); 1032 if (v != null) { 1033 ViewGroup.LayoutParams params = v.getLayoutParams(); 1034 if (params instanceof LayoutParams) { 1035 return (LayoutParams) v.getLayoutParams(); 1036 } 1037 } 1038 return null; 1039 } 1040 getRelatedViewBaselineOffset(int[] rules)1041 private int getRelatedViewBaselineOffset(int[] rules) { 1042 final View v = getRelatedView(rules, ALIGN_BASELINE); 1043 if (v != null) { 1044 final int baseline = v.getBaseline(); 1045 if (baseline != -1) { 1046 final ViewGroup.LayoutParams params = v.getLayoutParams(); 1047 if (params instanceof LayoutParams) { 1048 final LayoutParams anchorParams = (LayoutParams) v.getLayoutParams(); 1049 return anchorParams.mTop + baseline; 1050 } 1051 } 1052 } 1053 return -1; 1054 } 1055 centerHorizontal(View child, LayoutParams params, int myWidth)1056 private static void centerHorizontal(View child, LayoutParams params, int myWidth) { 1057 int childWidth = child.getMeasuredWidth(); 1058 int left = (myWidth - childWidth) / 2; 1059 1060 params.mLeft = left; 1061 params.mRight = left + childWidth; 1062 } 1063 centerVertical(View child, LayoutParams params, int myHeight)1064 private static void centerVertical(View child, LayoutParams params, int myHeight) { 1065 int childHeight = child.getMeasuredHeight(); 1066 int top = (myHeight - childHeight) / 2; 1067 1068 params.mTop = top; 1069 params.mBottom = top + childHeight; 1070 } 1071 1072 @Override onLayout(boolean changed, int l, int t, int r, int b)1073 protected void onLayout(boolean changed, int l, int t, int r, int b) { 1074 // The layout has actually already been performed and the positions 1075 // cached. Apply the cached values to the children. 1076 final int count = getChildCount(); 1077 1078 for (int i = 0; i < count; i++) { 1079 View child = getChildAt(i); 1080 if (child.getVisibility() != GONE) { 1081 RelativeLayout.LayoutParams st = 1082 (RelativeLayout.LayoutParams) child.getLayoutParams(); 1083 child.layout(st.mLeft, st.mTop, st.mRight, st.mBottom); 1084 } 1085 } 1086 } 1087 1088 @Override generateLayoutParams(AttributeSet attrs)1089 public LayoutParams generateLayoutParams(AttributeSet attrs) { 1090 return new RelativeLayout.LayoutParams(getContext(), attrs); 1091 } 1092 1093 /** 1094 * Returns a set of layout parameters with a width of 1095 * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT}, 1096 * a height of {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} and no spanning. 1097 */ 1098 @Override generateDefaultLayoutParams()1099 protected ViewGroup.LayoutParams generateDefaultLayoutParams() { 1100 return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); 1101 } 1102 1103 // Override to allow type-checking of LayoutParams. 1104 @Override checkLayoutParams(ViewGroup.LayoutParams p)1105 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { 1106 return p instanceof RelativeLayout.LayoutParams; 1107 } 1108 1109 @Override generateLayoutParams(ViewGroup.LayoutParams lp)1110 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) { 1111 if (sPreserveMarginParamsInLayoutParamConversion) { 1112 if (lp instanceof LayoutParams) { 1113 return new LayoutParams((LayoutParams) lp); 1114 } else if (lp instanceof MarginLayoutParams) { 1115 return new LayoutParams((MarginLayoutParams) lp); 1116 } 1117 } 1118 return new LayoutParams(lp); 1119 } 1120 1121 /** @hide */ 1122 @Override dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event)1123 public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) { 1124 if (mTopToBottomLeftToRightSet == null) { 1125 mTopToBottomLeftToRightSet = new TreeSet<View>(new TopToBottomLeftToRightComparator()); 1126 } 1127 1128 // sort children top-to-bottom and left-to-right 1129 for (int i = 0, count = getChildCount(); i < count; i++) { 1130 mTopToBottomLeftToRightSet.add(getChildAt(i)); 1131 } 1132 1133 for (View view : mTopToBottomLeftToRightSet) { 1134 if (view.getVisibility() == View.VISIBLE 1135 && view.dispatchPopulateAccessibilityEvent(event)) { 1136 mTopToBottomLeftToRightSet.clear(); 1137 return true; 1138 } 1139 } 1140 1141 mTopToBottomLeftToRightSet.clear(); 1142 return false; 1143 } 1144 1145 @Override getAccessibilityClassName()1146 public CharSequence getAccessibilityClassName() { 1147 return RelativeLayout.class.getName(); 1148 } 1149 1150 /** 1151 * Compares two views in left-to-right and top-to-bottom fashion. 1152 */ 1153 private class TopToBottomLeftToRightComparator implements Comparator<View> { compare(View first, View second)1154 public int compare(View first, View second) { 1155 // top - bottom 1156 int topDifference = first.getTop() - second.getTop(); 1157 if (topDifference != 0) { 1158 return topDifference; 1159 } 1160 // left - right 1161 int leftDifference = first.getLeft() - second.getLeft(); 1162 if (leftDifference != 0) { 1163 return leftDifference; 1164 } 1165 // break tie by height 1166 int heightDiference = first.getHeight() - second.getHeight(); 1167 if (heightDiference != 0) { 1168 return heightDiference; 1169 } 1170 // break tie by width 1171 int widthDiference = first.getWidth() - second.getWidth(); 1172 if (widthDiference != 0) { 1173 return widthDiference; 1174 } 1175 return 0; 1176 } 1177 } 1178 1179 /** 1180 * Specifies how a view is positioned within a {@link RelativeLayout}. 1181 * The relative layout containing the view uses the value of these layout parameters to 1182 * determine where to position the view on the screen. If the view is not contained 1183 * within a relative layout, these attributes are ignored. 1184 * 1185 * See the <a href=“https://developer.android.com/guide/topics/ui/layout/relative.html”> 1186 * Relative Layout</a> guide for example code demonstrating how to use relative layout’s 1187 * layout parameters in a layout XML. 1188 * 1189 * To learn more about layout parameters and how they differ from typical view attributes, 1190 * see the <a href=“https://developer.android.com/guide/topics/ui/declaring-layout.html#attributes”> 1191 * Layouts guide</a>. 1192 * 1193 * 1194 * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignWithParentIfMissing 1195 * @attr ref android.R.styleable#RelativeLayout_Layout_layout_toLeftOf 1196 * @attr ref android.R.styleable#RelativeLayout_Layout_layout_toRightOf 1197 * @attr ref android.R.styleable#RelativeLayout_Layout_layout_above 1198 * @attr ref android.R.styleable#RelativeLayout_Layout_layout_below 1199 * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignBaseline 1200 * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignLeft 1201 * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignTop 1202 * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignRight 1203 * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignBottom 1204 * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignParentLeft 1205 * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignParentTop 1206 * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignParentRight 1207 * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignParentBottom 1208 * @attr ref android.R.styleable#RelativeLayout_Layout_layout_centerInParent 1209 * @attr ref android.R.styleable#RelativeLayout_Layout_layout_centerHorizontal 1210 * @attr ref android.R.styleable#RelativeLayout_Layout_layout_centerVertical 1211 * @attr ref android.R.styleable#RelativeLayout_Layout_layout_toStartOf 1212 * @attr ref android.R.styleable#RelativeLayout_Layout_layout_toEndOf 1213 * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignStart 1214 * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignEnd 1215 * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignParentStart 1216 * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignParentEnd 1217 */ 1218 public static class LayoutParams extends ViewGroup.MarginLayoutParams { 1219 @ViewDebug.ExportedProperty(category = "layout", resolveId = true, indexMapping = { 1220 @ViewDebug.IntToString(from = ABOVE, to = "above"), 1221 @ViewDebug.IntToString(from = ALIGN_BASELINE, to = "alignBaseline"), 1222 @ViewDebug.IntToString(from = ALIGN_BOTTOM, to = "alignBottom"), 1223 @ViewDebug.IntToString(from = ALIGN_LEFT, to = "alignLeft"), 1224 @ViewDebug.IntToString(from = ALIGN_PARENT_BOTTOM, to = "alignParentBottom"), 1225 @ViewDebug.IntToString(from = ALIGN_PARENT_LEFT, to = "alignParentLeft"), 1226 @ViewDebug.IntToString(from = ALIGN_PARENT_RIGHT, to = "alignParentRight"), 1227 @ViewDebug.IntToString(from = ALIGN_PARENT_TOP, to = "alignParentTop"), 1228 @ViewDebug.IntToString(from = ALIGN_RIGHT, to = "alignRight"), 1229 @ViewDebug.IntToString(from = ALIGN_TOP, to = "alignTop"), 1230 @ViewDebug.IntToString(from = BELOW, to = "below"), 1231 @ViewDebug.IntToString(from = CENTER_HORIZONTAL, to = "centerHorizontal"), 1232 @ViewDebug.IntToString(from = CENTER_IN_PARENT, to = "center"), 1233 @ViewDebug.IntToString(from = CENTER_VERTICAL, to = "centerVertical"), 1234 @ViewDebug.IntToString(from = LEFT_OF, to = "leftOf"), 1235 @ViewDebug.IntToString(from = RIGHT_OF, to = "rightOf"), 1236 @ViewDebug.IntToString(from = ALIGN_START, to = "alignStart"), 1237 @ViewDebug.IntToString(from = ALIGN_END, to = "alignEnd"), 1238 @ViewDebug.IntToString(from = ALIGN_PARENT_START, to = "alignParentStart"), 1239 @ViewDebug.IntToString(from = ALIGN_PARENT_END, to = "alignParentEnd"), 1240 @ViewDebug.IntToString(from = START_OF, to = "startOf"), 1241 @ViewDebug.IntToString(from = END_OF, to = "endOf") 1242 }, mapping = { 1243 @ViewDebug.IntToString(from = TRUE, to = "true"), 1244 @ViewDebug.IntToString(from = 0, to = "false/NO_ID") 1245 }) 1246 1247 private int[] mRules = new int[VERB_COUNT]; 1248 private int[] mInitialRules = new int[VERB_COUNT]; 1249 1250 private int mLeft, mTop, mRight, mBottom; 1251 1252 /** 1253 * Whether this view had any relative rules modified following the most 1254 * recent resolution of layout direction. 1255 */ 1256 private boolean mNeedsLayoutResolution; 1257 1258 private boolean mRulesChanged = false; 1259 private boolean mIsRtlCompatibilityMode = false; 1260 1261 /** 1262 * When true, uses the parent as the anchor if the anchor doesn't exist or if 1263 * the anchor's visibility is GONE. 1264 */ 1265 @ViewDebug.ExportedProperty(category = "layout") 1266 public boolean alignWithParent; 1267 LayoutParams(Context c, AttributeSet attrs)1268 public LayoutParams(Context c, AttributeSet attrs) { 1269 super(c, attrs); 1270 1271 TypedArray a = c.obtainStyledAttributes(attrs, 1272 com.android.internal.R.styleable.RelativeLayout_Layout); 1273 1274 final int targetSdkVersion = c.getApplicationInfo().targetSdkVersion; 1275 mIsRtlCompatibilityMode = (targetSdkVersion < JELLY_BEAN_MR1 || 1276 !c.getApplicationInfo().hasRtlSupport()); 1277 1278 final int[] rules = mRules; 1279 //noinspection MismatchedReadAndWriteOfArray 1280 final int[] initialRules = mInitialRules; 1281 1282 final int N = a.getIndexCount(); 1283 for (int i = 0; i < N; i++) { 1284 int attr = a.getIndex(i); 1285 switch (attr) { 1286 case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignWithParentIfMissing: 1287 alignWithParent = a.getBoolean(attr, false); 1288 break; 1289 case com.android.internal.R.styleable.RelativeLayout_Layout_layout_toLeftOf: 1290 rules[LEFT_OF] = a.getResourceId(attr, 0); 1291 break; 1292 case com.android.internal.R.styleable.RelativeLayout_Layout_layout_toRightOf: 1293 rules[RIGHT_OF] = a.getResourceId(attr, 0); 1294 break; 1295 case com.android.internal.R.styleable.RelativeLayout_Layout_layout_above: 1296 rules[ABOVE] = a.getResourceId(attr, 0); 1297 break; 1298 case com.android.internal.R.styleable.RelativeLayout_Layout_layout_below: 1299 rules[BELOW] = a.getResourceId(attr, 0); 1300 break; 1301 case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignBaseline: 1302 rules[ALIGN_BASELINE] = a.getResourceId(attr, 0); 1303 break; 1304 case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignLeft: 1305 rules[ALIGN_LEFT] = a.getResourceId(attr, 0); 1306 break; 1307 case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignTop: 1308 rules[ALIGN_TOP] = a.getResourceId(attr, 0); 1309 break; 1310 case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignRight: 1311 rules[ALIGN_RIGHT] = a.getResourceId(attr, 0); 1312 break; 1313 case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignBottom: 1314 rules[ALIGN_BOTTOM] = a.getResourceId(attr, 0); 1315 break; 1316 case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignParentLeft: 1317 rules[ALIGN_PARENT_LEFT] = a.getBoolean(attr, false) ? TRUE : 0; 1318 break; 1319 case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignParentTop: 1320 rules[ALIGN_PARENT_TOP] = a.getBoolean(attr, false) ? TRUE : 0; 1321 break; 1322 case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignParentRight: 1323 rules[ALIGN_PARENT_RIGHT] = a.getBoolean(attr, false) ? TRUE : 0; 1324 break; 1325 case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignParentBottom: 1326 rules[ALIGN_PARENT_BOTTOM] = a.getBoolean(attr, false) ? TRUE : 0; 1327 break; 1328 case com.android.internal.R.styleable.RelativeLayout_Layout_layout_centerInParent: 1329 rules[CENTER_IN_PARENT] = a.getBoolean(attr, false) ? TRUE : 0; 1330 break; 1331 case com.android.internal.R.styleable.RelativeLayout_Layout_layout_centerHorizontal: 1332 rules[CENTER_HORIZONTAL] = a.getBoolean(attr, false) ? TRUE : 0; 1333 break; 1334 case com.android.internal.R.styleable.RelativeLayout_Layout_layout_centerVertical: 1335 rules[CENTER_VERTICAL] = a.getBoolean(attr, false) ? TRUE : 0; 1336 break; 1337 case com.android.internal.R.styleable.RelativeLayout_Layout_layout_toStartOf: 1338 rules[START_OF] = a.getResourceId(attr, 0); 1339 break; 1340 case com.android.internal.R.styleable.RelativeLayout_Layout_layout_toEndOf: 1341 rules[END_OF] = a.getResourceId(attr, 0); 1342 break; 1343 case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignStart: 1344 rules[ALIGN_START] = a.getResourceId(attr, 0); 1345 break; 1346 case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignEnd: 1347 rules[ALIGN_END] = a.getResourceId(attr, 0); 1348 break; 1349 case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignParentStart: 1350 rules[ALIGN_PARENT_START] = a.getBoolean(attr, false) ? TRUE : 0; 1351 break; 1352 case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignParentEnd: 1353 rules[ALIGN_PARENT_END] = a.getBoolean(attr, false) ? TRUE : 0; 1354 break; 1355 } 1356 } 1357 mRulesChanged = true; 1358 System.arraycopy(rules, LEFT_OF, initialRules, LEFT_OF, VERB_COUNT); 1359 1360 a.recycle(); 1361 } 1362 LayoutParams(int w, int h)1363 public LayoutParams(int w, int h) { 1364 super(w, h); 1365 } 1366 1367 /** 1368 * {@inheritDoc} 1369 */ LayoutParams(ViewGroup.LayoutParams source)1370 public LayoutParams(ViewGroup.LayoutParams source) { 1371 super(source); 1372 } 1373 1374 /** 1375 * {@inheritDoc} 1376 */ LayoutParams(ViewGroup.MarginLayoutParams source)1377 public LayoutParams(ViewGroup.MarginLayoutParams source) { 1378 super(source); 1379 } 1380 1381 /** 1382 * Copy constructor. Clones the width, height, margin values, and rules 1383 * of the source. 1384 * 1385 * @param source The layout params to copy from. 1386 */ LayoutParams(LayoutParams source)1387 public LayoutParams(LayoutParams source) { 1388 super(source); 1389 1390 this.mIsRtlCompatibilityMode = source.mIsRtlCompatibilityMode; 1391 this.mRulesChanged = source.mRulesChanged; 1392 this.alignWithParent = source.alignWithParent; 1393 1394 System.arraycopy(source.mRules, LEFT_OF, this.mRules, LEFT_OF, VERB_COUNT); 1395 System.arraycopy( 1396 source.mInitialRules, LEFT_OF, this.mInitialRules, LEFT_OF, VERB_COUNT); 1397 } 1398 1399 @Override debug(String output)1400 public String debug(String output) { 1401 return output + "ViewGroup.LayoutParams={ width=" + sizeToString(width) + 1402 ", height=" + sizeToString(height) + " }"; 1403 } 1404 1405 /** 1406 * Adds a layout rule to be interpreted by the RelativeLayout. 1407 * <p> 1408 * This method should only be used for verbs that don't refer to a 1409 * sibling (ex. {@link #ALIGN_RIGHT}) or take a boolean 1410 * value ({@link #TRUE} for true or 0 for false). To 1411 * specify a verb that takes a subject, use {@link #addRule(int, int)}. 1412 * <p> 1413 * If the rule is relative to the layout direction (ex. 1414 * {@link #ALIGN_PARENT_START}), then the layout direction must be 1415 * resolved using {@link #resolveLayoutDirection(int)} before calling 1416 * {@link #getRule(int)} an absolute rule (ex. 1417 * {@link #ALIGN_PARENT_LEFT}. 1418 * 1419 * @param verb a layout verb, such as {@link #ALIGN_PARENT_LEFT} 1420 * @see #addRule(int, int) 1421 * @see #removeRule(int) 1422 * @see #getRule(int) 1423 */ addRule(int verb)1424 public void addRule(int verb) { 1425 addRule(verb, TRUE); 1426 } 1427 1428 /** 1429 * Adds a layout rule to be interpreted by the RelativeLayout. 1430 * <p> 1431 * Use this for verbs that refer to a sibling (ex. 1432 * {@link #ALIGN_RIGHT}) or take a boolean value (ex. 1433 * {@link #CENTER_IN_PARENT}). 1434 * <p> 1435 * If the rule is relative to the layout direction (ex. 1436 * {@link #START_OF}), then the layout direction must be resolved using 1437 * {@link #resolveLayoutDirection(int)} before calling 1438 * {@link #getRule(int)} with an absolute rule (ex. {@link #LEFT_OF}. 1439 * 1440 * @param verb a layout verb, such as {@link #ALIGN_RIGHT} 1441 * @param subject the ID of another view to use as an anchor, or a 1442 * boolean value (represented as {@link #TRUE} for true 1443 * or 0 for false) 1444 * @see #addRule(int) 1445 * @see #removeRule(int) 1446 * @see #getRule(int) 1447 */ addRule(int verb, int subject)1448 public void addRule(int verb, int subject) { 1449 // If we're removing a relative rule, we'll need to force layout 1450 // resolution the next time it's requested. 1451 if (!mNeedsLayoutResolution && isRelativeRule(verb) 1452 && mInitialRules[verb] != 0 && subject == 0) { 1453 mNeedsLayoutResolution = true; 1454 } 1455 1456 mRules[verb] = subject; 1457 mInitialRules[verb] = subject; 1458 mRulesChanged = true; 1459 } 1460 1461 /** 1462 * Removes a layout rule to be interpreted by the RelativeLayout. 1463 * <p> 1464 * If the rule is relative to the layout direction (ex. 1465 * {@link #START_OF}, {@link #ALIGN_PARENT_START}, etc.) then the 1466 * layout direction must be resolved using 1467 * {@link #resolveLayoutDirection(int)} before before calling 1468 * {@link #getRule(int)} with an absolute rule (ex. {@link #LEFT_OF}. 1469 * 1470 * @param verb One of the verbs defined by 1471 * {@link android.widget.RelativeLayout RelativeLayout}, such as 1472 * ALIGN_WITH_PARENT_LEFT. 1473 * @see #addRule(int) 1474 * @see #addRule(int, int) 1475 * @see #getRule(int) 1476 */ removeRule(int verb)1477 public void removeRule(int verb) { 1478 addRule(verb, 0); 1479 } 1480 1481 /** 1482 * Returns the layout rule associated with a specific verb. 1483 * 1484 * @param verb one of the verbs defined by {@link RelativeLayout}, such 1485 * as ALIGN_WITH_PARENT_LEFT 1486 * @return the id of another view to use as an anchor, a boolean value 1487 * (represented as {@link RelativeLayout#TRUE} for true 1488 * or 0 for false), or -1 for verbs that don't refer to another 1489 * sibling (for example, ALIGN_WITH_PARENT_BOTTOM) 1490 * @see #addRule(int) 1491 * @see #addRule(int, int) 1492 */ getRule(int verb)1493 public int getRule(int verb) { 1494 return mRules[verb]; 1495 } 1496 hasRelativeRules()1497 private boolean hasRelativeRules() { 1498 return (mInitialRules[START_OF] != 0 || mInitialRules[END_OF] != 0 || 1499 mInitialRules[ALIGN_START] != 0 || mInitialRules[ALIGN_END] != 0 || 1500 mInitialRules[ALIGN_PARENT_START] != 0 || mInitialRules[ALIGN_PARENT_END] != 0); 1501 } 1502 isRelativeRule(int rule)1503 private boolean isRelativeRule(int rule) { 1504 return rule == START_OF || rule == END_OF 1505 || rule == ALIGN_START || rule == ALIGN_END 1506 || rule == ALIGN_PARENT_START || rule == ALIGN_PARENT_END; 1507 } 1508 1509 // The way we are resolving rules depends on the layout direction and if we are pre JB MR1 1510 // or not. 1511 // 1512 // If we are pre JB MR1 (said as "RTL compatibility mode"), "left"/"right" rules are having 1513 // predominance over any "start/end" rules that could have been defined. A special case: 1514 // if no "left"/"right" rule has been defined and "start"/"end" rules are defined then we 1515 // resolve those "start"/"end" rules to "left"/"right" respectively. 1516 // 1517 // If we are JB MR1+, then "start"/"end" rules are having predominance over "left"/"right" 1518 // rules. If no "start"/"end" rule is defined then we use "left"/"right" rules. 1519 // 1520 // In all cases, the result of the resolution should clear the "start"/"end" rules to leave 1521 // only the "left"/"right" rules at the end. resolveRules(int layoutDirection)1522 private void resolveRules(int layoutDirection) { 1523 final boolean isLayoutRtl = (layoutDirection == View.LAYOUT_DIRECTION_RTL); 1524 1525 // Reset to initial state 1526 System.arraycopy(mInitialRules, LEFT_OF, mRules, LEFT_OF, VERB_COUNT); 1527 1528 // Apply rules depending on direction and if we are in RTL compatibility mode 1529 if (mIsRtlCompatibilityMode) { 1530 if (mRules[ALIGN_START] != 0) { 1531 if (mRules[ALIGN_LEFT] == 0) { 1532 // "left" rule is not defined but "start" rule is: use the "start" rule as 1533 // the "left" rule 1534 mRules[ALIGN_LEFT] = mRules[ALIGN_START]; 1535 } 1536 mRules[ALIGN_START] = 0; 1537 } 1538 1539 if (mRules[ALIGN_END] != 0) { 1540 if (mRules[ALIGN_RIGHT] == 0) { 1541 // "right" rule is not defined but "end" rule is: use the "end" rule as the 1542 // "right" rule 1543 mRules[ALIGN_RIGHT] = mRules[ALIGN_END]; 1544 } 1545 mRules[ALIGN_END] = 0; 1546 } 1547 1548 if (mRules[START_OF] != 0) { 1549 if (mRules[LEFT_OF] == 0) { 1550 // "left" rule is not defined but "start" rule is: use the "start" rule as 1551 // the "left" rule 1552 mRules[LEFT_OF] = mRules[START_OF]; 1553 } 1554 mRules[START_OF] = 0; 1555 } 1556 1557 if (mRules[END_OF] != 0) { 1558 if (mRules[RIGHT_OF] == 0) { 1559 // "right" rule is not defined but "end" rule is: use the "end" rule as the 1560 // "right" rule 1561 mRules[RIGHT_OF] = mRules[END_OF]; 1562 } 1563 mRules[END_OF] = 0; 1564 } 1565 1566 if (mRules[ALIGN_PARENT_START] != 0) { 1567 if (mRules[ALIGN_PARENT_LEFT] == 0) { 1568 // "left" rule is not defined but "start" rule is: use the "start" rule as 1569 // the "left" rule 1570 mRules[ALIGN_PARENT_LEFT] = mRules[ALIGN_PARENT_START]; 1571 } 1572 mRules[ALIGN_PARENT_START] = 0; 1573 } 1574 1575 if (mRules[ALIGN_PARENT_END] != 0) { 1576 if (mRules[ALIGN_PARENT_RIGHT] == 0) { 1577 // "right" rule is not defined but "end" rule is: use the "end" rule as the 1578 // "right" rule 1579 mRules[ALIGN_PARENT_RIGHT] = mRules[ALIGN_PARENT_END]; 1580 } 1581 mRules[ALIGN_PARENT_END] = 0; 1582 } 1583 } else { 1584 // JB MR1+ case 1585 if ((mRules[ALIGN_START] != 0 || mRules[ALIGN_END] != 0) && 1586 (mRules[ALIGN_LEFT] != 0 || mRules[ALIGN_RIGHT] != 0)) { 1587 // "start"/"end" rules take precedence over "left"/"right" rules 1588 mRules[ALIGN_LEFT] = 0; 1589 mRules[ALIGN_RIGHT] = 0; 1590 } 1591 if (mRules[ALIGN_START] != 0) { 1592 // "start" rule resolved to "left" or "right" depending on the direction 1593 mRules[isLayoutRtl ? ALIGN_RIGHT : ALIGN_LEFT] = mRules[ALIGN_START]; 1594 mRules[ALIGN_START] = 0; 1595 } 1596 if (mRules[ALIGN_END] != 0) { 1597 // "end" rule resolved to "left" or "right" depending on the direction 1598 mRules[isLayoutRtl ? ALIGN_LEFT : ALIGN_RIGHT] = mRules[ALIGN_END]; 1599 mRules[ALIGN_END] = 0; 1600 } 1601 1602 if ((mRules[START_OF] != 0 || mRules[END_OF] != 0) && 1603 (mRules[LEFT_OF] != 0 || mRules[RIGHT_OF] != 0)) { 1604 // "start"/"end" rules take precedence over "left"/"right" rules 1605 mRules[LEFT_OF] = 0; 1606 mRules[RIGHT_OF] = 0; 1607 } 1608 if (mRules[START_OF] != 0) { 1609 // "start" rule resolved to "left" or "right" depending on the direction 1610 mRules[isLayoutRtl ? RIGHT_OF : LEFT_OF] = mRules[START_OF]; 1611 mRules[START_OF] = 0; 1612 } 1613 if (mRules[END_OF] != 0) { 1614 // "end" rule resolved to "left" or "right" depending on the direction 1615 mRules[isLayoutRtl ? LEFT_OF : RIGHT_OF] = mRules[END_OF]; 1616 mRules[END_OF] = 0; 1617 } 1618 1619 if ((mRules[ALIGN_PARENT_START] != 0 || mRules[ALIGN_PARENT_END] != 0) && 1620 (mRules[ALIGN_PARENT_LEFT] != 0 || mRules[ALIGN_PARENT_RIGHT] != 0)) { 1621 // "start"/"end" rules take precedence over "left"/"right" rules 1622 mRules[ALIGN_PARENT_LEFT] = 0; 1623 mRules[ALIGN_PARENT_RIGHT] = 0; 1624 } 1625 if (mRules[ALIGN_PARENT_START] != 0) { 1626 // "start" rule resolved to "left" or "right" depending on the direction 1627 mRules[isLayoutRtl ? ALIGN_PARENT_RIGHT : ALIGN_PARENT_LEFT] = mRules[ALIGN_PARENT_START]; 1628 mRules[ALIGN_PARENT_START] = 0; 1629 } 1630 if (mRules[ALIGN_PARENT_END] != 0) { 1631 // "end" rule resolved to "left" or "right" depending on the direction 1632 mRules[isLayoutRtl ? ALIGN_PARENT_LEFT : ALIGN_PARENT_RIGHT] = mRules[ALIGN_PARENT_END]; 1633 mRules[ALIGN_PARENT_END] = 0; 1634 } 1635 } 1636 1637 mRulesChanged = false; 1638 mNeedsLayoutResolution = false; 1639 } 1640 1641 /** 1642 * Retrieves a complete list of all supported rules, where the index is the rule 1643 * verb, and the element value is the value specified, or "false" if it was never 1644 * set. If there are relative rules defined (*_START / *_END), they will be resolved 1645 * depending on the layout direction. 1646 * 1647 * @param layoutDirection the direction of the layout. 1648 * Should be either {@link View#LAYOUT_DIRECTION_LTR} 1649 * or {@link View#LAYOUT_DIRECTION_RTL} 1650 * @return the supported rules 1651 * @see #addRule(int, int) 1652 * 1653 * @hide 1654 */ getRules(int layoutDirection)1655 public int[] getRules(int layoutDirection) { 1656 resolveLayoutDirection(layoutDirection); 1657 return mRules; 1658 } 1659 1660 /** 1661 * Retrieves a complete list of all supported rules, where the index is the rule 1662 * verb, and the element value is the value specified, or "false" if it was never 1663 * set. There will be no resolution of relative rules done. 1664 * 1665 * @return the supported rules 1666 * @see #addRule(int, int) 1667 */ getRules()1668 public int[] getRules() { 1669 return mRules; 1670 } 1671 1672 /** 1673 * This will be called by {@link android.view.View#requestLayout()} to 1674 * resolve layout parameters that are relative to the layout direction. 1675 * <p> 1676 * After this method is called, any rules using layout-relative verbs 1677 * (ex. {@link #START_OF}) previously added via {@link #addRule(int)} 1678 * may only be accessed via their resolved absolute verbs (ex. 1679 * {@link #LEFT_OF}). 1680 */ 1681 @Override resolveLayoutDirection(int layoutDirection)1682 public void resolveLayoutDirection(int layoutDirection) { 1683 if (shouldResolveLayoutDirection(layoutDirection)) { 1684 resolveRules(layoutDirection); 1685 } 1686 1687 // This will set the layout direction. 1688 super.resolveLayoutDirection(layoutDirection); 1689 } 1690 shouldResolveLayoutDirection(int layoutDirection)1691 private boolean shouldResolveLayoutDirection(int layoutDirection) { 1692 return (mNeedsLayoutResolution || hasRelativeRules()) 1693 && (mRulesChanged || layoutDirection != getLayoutDirection()); 1694 } 1695 1696 /** @hide */ 1697 @Override encodeProperties(@onNull ViewHierarchyEncoder encoder)1698 protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) { 1699 super.encodeProperties(encoder); 1700 encoder.addProperty("layout:alignWithParent", alignWithParent); 1701 } 1702 } 1703 1704 private static class DependencyGraph { 1705 /** 1706 * List of all views in the graph. 1707 */ 1708 private ArrayList<Node> mNodes = new ArrayList<Node>(); 1709 1710 /** 1711 * List of nodes in the graph. Each node is identified by its 1712 * view id (see View#getId()). 1713 */ 1714 private SparseArray<Node> mKeyNodes = new SparseArray<Node>(); 1715 1716 /** 1717 * Temporary data structure used to build the list of roots 1718 * for this graph. 1719 */ 1720 private ArrayDeque<Node> mRoots = new ArrayDeque<Node>(); 1721 1722 /** 1723 * Clears the graph. 1724 */ clear()1725 void clear() { 1726 final ArrayList<Node> nodes = mNodes; 1727 final int count = nodes.size(); 1728 1729 for (int i = 0; i < count; i++) { 1730 nodes.get(i).release(); 1731 } 1732 nodes.clear(); 1733 1734 mKeyNodes.clear(); 1735 mRoots.clear(); 1736 } 1737 1738 /** 1739 * Adds a view to the graph. 1740 * 1741 * @param view The view to be added as a node to the graph. 1742 */ add(View view)1743 void add(View view) { 1744 final int id = view.getId(); 1745 final Node node = Node.acquire(view); 1746 1747 if (id != View.NO_ID) { 1748 mKeyNodes.put(id, node); 1749 } 1750 1751 mNodes.add(node); 1752 } 1753 1754 /** 1755 * Builds a sorted list of views. The sorting order depends on the dependencies 1756 * between the view. For instance, if view C needs view A to be processed first 1757 * and view A needs view B to be processed first, the dependency graph 1758 * is: B -> A -> C. The sorted array will contain views B, A and C in this order. 1759 * 1760 * @param sorted The sorted list of views. The length of this array must 1761 * be equal to getChildCount(). 1762 * @param rules The list of rules to take into account. 1763 */ getSortedViews(View[] sorted, int... rules)1764 void getSortedViews(View[] sorted, int... rules) { 1765 final ArrayDeque<Node> roots = findRoots(rules); 1766 int index = 0; 1767 1768 Node node; 1769 while ((node = roots.pollLast()) != null) { 1770 final View view = node.view; 1771 final int key = view.getId(); 1772 1773 sorted[index++] = view; 1774 1775 final ArrayMap<Node, DependencyGraph> dependents = node.dependents; 1776 final int count = dependents.size(); 1777 for (int i = 0; i < count; i++) { 1778 final Node dependent = dependents.keyAt(i); 1779 final SparseArray<Node> dependencies = dependent.dependencies; 1780 1781 dependencies.remove(key); 1782 if (dependencies.size() == 0) { 1783 roots.add(dependent); 1784 } 1785 } 1786 } 1787 1788 if (index < sorted.length) { 1789 throw new IllegalStateException("Circular dependencies cannot exist" 1790 + " in RelativeLayout"); 1791 } 1792 } 1793 1794 /** 1795 * Finds the roots of the graph. A root is a node with no dependency and 1796 * with [0..n] dependents. 1797 * 1798 * @param rulesFilter The list of rules to consider when building the 1799 * dependencies 1800 * 1801 * @return A list of node, each being a root of the graph 1802 */ findRoots(int[] rulesFilter)1803 private ArrayDeque<Node> findRoots(int[] rulesFilter) { 1804 final SparseArray<Node> keyNodes = mKeyNodes; 1805 final ArrayList<Node> nodes = mNodes; 1806 final int count = nodes.size(); 1807 1808 // Find roots can be invoked several times, so make sure to clear 1809 // all dependents and dependencies before running the algorithm 1810 for (int i = 0; i < count; i++) { 1811 final Node node = nodes.get(i); 1812 node.dependents.clear(); 1813 node.dependencies.clear(); 1814 } 1815 1816 // Builds up the dependents and dependencies for each node of the graph 1817 for (int i = 0; i < count; i++) { 1818 final Node node = nodes.get(i); 1819 1820 final LayoutParams layoutParams = (LayoutParams) node.view.getLayoutParams(); 1821 final int[] rules = layoutParams.mRules; 1822 final int rulesCount = rulesFilter.length; 1823 1824 // Look only the the rules passed in parameter, this way we build only the 1825 // dependencies for a specific set of rules 1826 for (int j = 0; j < rulesCount; j++) { 1827 final int rule = rules[rulesFilter[j]]; 1828 if (rule > 0) { 1829 // The node this node depends on 1830 final Node dependency = keyNodes.get(rule); 1831 // Skip unknowns and self dependencies 1832 if (dependency == null || dependency == node) { 1833 continue; 1834 } 1835 // Add the current node as a dependent 1836 dependency.dependents.put(node, this); 1837 // Add a dependency to the current node 1838 node.dependencies.put(rule, dependency); 1839 } 1840 } 1841 } 1842 1843 final ArrayDeque<Node> roots = mRoots; 1844 roots.clear(); 1845 1846 // Finds all the roots in the graph: all nodes with no dependencies 1847 for (int i = 0; i < count; i++) { 1848 final Node node = nodes.get(i); 1849 if (node.dependencies.size() == 0) roots.addLast(node); 1850 } 1851 1852 return roots; 1853 } 1854 1855 /** 1856 * A node in the dependency graph. A node is a view, its list of dependencies 1857 * and its list of dependents. 1858 * 1859 * A node with no dependent is considered a root of the graph. 1860 */ 1861 static class Node { 1862 /** 1863 * The view representing this node in the layout. 1864 */ 1865 View view; 1866 1867 /** 1868 * The list of dependents for this node; a dependent is a node 1869 * that needs this node to be processed first. 1870 */ 1871 final ArrayMap<Node, DependencyGraph> dependents = 1872 new ArrayMap<Node, DependencyGraph>(); 1873 1874 /** 1875 * The list of dependencies for this node. 1876 */ 1877 final SparseArray<Node> dependencies = new SparseArray<Node>(); 1878 1879 /* 1880 * START POOL IMPLEMENTATION 1881 */ 1882 // The pool is static, so all nodes instances are shared across 1883 // activities, that's why we give it a rather high limit 1884 private static final int POOL_LIMIT = 100; 1885 private static final SynchronizedPool<Node> sPool = 1886 new SynchronizedPool<Node>(POOL_LIMIT); 1887 acquire(View view)1888 static Node acquire(View view) { 1889 Node node = sPool.acquire(); 1890 if (node == null) { 1891 node = new Node(); 1892 } 1893 node.view = view; 1894 return node; 1895 } 1896 release()1897 void release() { 1898 view = null; 1899 dependents.clear(); 1900 dependencies.clear(); 1901 1902 sPool.release(this); 1903 } 1904 /* 1905 * END POOL IMPLEMENTATION 1906 */ 1907 } 1908 } 1909 } 1910