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