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