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