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