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