LinearLayoutCompat.java revision 49c78900da0d43140fb602431fb93212bd7f6c70
1/* 2 * Copyright (C) 2014 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.support.v7.widget; 18 19import android.content.Context; 20import android.content.res.TypedArray; 21import android.graphics.Canvas; 22import android.graphics.drawable.Drawable; 23import android.os.Build; 24import android.support.annotation.IntDef; 25import android.support.v4.view.GravityCompat; 26import android.support.v4.view.ViewCompat; 27import android.support.v7.appcompat.R; 28import android.support.v7.internal.widget.ViewUtils; 29import android.util.AttributeSet; 30import android.view.Gravity; 31import android.view.View; 32import android.view.ViewGroup; 33import android.view.accessibility.AccessibilityEvent; 34import android.view.accessibility.AccessibilityNodeInfo; 35 36import java.lang.annotation.Retention; 37import java.lang.annotation.RetentionPolicy; 38 39 40/** 41 * A Layout that arranges its children in a single column or a single row. The direction of 42 * the row can be set by calling {@link #setOrientation(int) setOrientation()}. 43 * You can also specify gravity, which specifies the alignment of all the child elements by 44 * calling {@link #setGravity(int) setGravity()} or specify that specific children 45 * grow to fill up any remaining space in the layout by setting the <em>weight</em> member of 46 * {@link LinearLayoutCompat.LayoutParams LinearLayoutCompat.LayoutParams}. 47 * The default orientation is horizontal. 48 * 49 * <p>See the <a href="{@docRoot}guide/topics/ui/layout/linear.html">Linear Layout</a> 50 * guide.</p> 51 * 52 * <p> 53 * Also see {@link LinearLayoutCompat.LayoutParams android.widget.LinearLayoutCompat.LayoutParams} 54 * for layout attributes </p> 55 * 56 * @attr ref android.R.styleable#LinearLayout_baselineAligned 57 * @attr ref android.R.styleable#LinearLayout_baselineAlignedChildIndex 58 * @attr ref android.R.styleable#LinearLayout_gravity 59 * @attr ref R.styleable#LinearLayout_measureWithLargestChild 60 * @attr ref android.R.styleable#LinearLayout_orientation 61 * @attr ref android.R.styleable#LinearLayout_weightSum 62 */ 63public class LinearLayoutCompat extends ViewGroup { 64 /** @hide */ 65 @IntDef({HORIZONTAL, VERTICAL}) 66 @Retention(RetentionPolicy.SOURCE) 67 public @interface OrientationMode {} 68 69 public static final int HORIZONTAL = 0; 70 public static final int VERTICAL = 1; 71 72 /** @hide */ 73 @IntDef(flag = true, 74 value = { 75 SHOW_DIVIDER_NONE, 76 SHOW_DIVIDER_BEGINNING, 77 SHOW_DIVIDER_MIDDLE, 78 SHOW_DIVIDER_END 79 }) 80 @Retention(RetentionPolicy.SOURCE) 81 public @interface DividerMode {} 82 83 /** 84 * Don't show any dividers. 85 */ 86 public static final int SHOW_DIVIDER_NONE = 0; 87 /** 88 * Show a divider at the beginning of the group. 89 */ 90 public static final int SHOW_DIVIDER_BEGINNING = 1; 91 /** 92 * Show dividers between each item in the group. 93 */ 94 public static final int SHOW_DIVIDER_MIDDLE = 2; 95 /** 96 * Show a divider at the end of the group. 97 */ 98 public static final int SHOW_DIVIDER_END = 4; 99 100 /** 101 * Whether the children of this layout are baseline aligned. Only applicable 102 * if {@link #mOrientation} is horizontal. 103 */ 104 private boolean mBaselineAligned = true; 105 106 /** 107 * If this layout is part of another layout that is baseline aligned, 108 * use the child at this index as the baseline. 109 * 110 * Note: this is orthogonal to {@link #mBaselineAligned}, which is concerned 111 * with whether the children of this layout are baseline aligned. 112 */ 113 private int mBaselineAlignedChildIndex = -1; 114 115 /** 116 * The additional offset to the child's baseline. 117 * We'll calculate the baseline of this layout as we measure vertically; for 118 * horizontal linear layouts, the offset of 0 is appropriate. 119 */ 120 private int mBaselineChildTop = 0; 121 122 private int mOrientation; 123 124 private int mGravity = GravityCompat.START | Gravity.TOP; 125 126 private int mTotalLength; 127 128 private float mWeightSum; 129 130 private boolean mUseLargestChild; 131 132 private int[] mMaxAscent; 133 private int[] mMaxDescent; 134 135 private static final int VERTICAL_GRAVITY_COUNT = 4; 136 137 private static final int INDEX_CENTER_VERTICAL = 0; 138 private static final int INDEX_TOP = 1; 139 private static final int INDEX_BOTTOM = 2; 140 private static final int INDEX_FILL = 3; 141 142 private Drawable mDivider; 143 private int mDividerWidth; 144 private int mDividerHeight; 145 private int mShowDividers; 146 private int mDividerPadding; 147 148 public LinearLayoutCompat(Context context) { 149 this(context, null); 150 } 151 152 public LinearLayoutCompat(Context context, AttributeSet attrs) { 153 this(context, attrs, 0); 154 } 155 156 public LinearLayoutCompat(Context context, AttributeSet attrs, int defStyleAttr) { 157 super(context, attrs, defStyleAttr); 158 159 final TypedArray a = context.obtainStyledAttributes( 160 attrs, R.styleable.LinearLayoutCompat, defStyleAttr, 0); 161 162 int index = a.getInt(R.styleable.LinearLayoutCompat_android_orientation, -1); 163 if (index >= 0) { 164 setOrientation(index); 165 } 166 167 index = a.getInt(R.styleable.LinearLayoutCompat_android_gravity, -1); 168 if (index >= 0) { 169 setGravity(index); 170 } 171 172 boolean baselineAligned = a.getBoolean(R.styleable.LinearLayoutCompat_android_baselineAligned, true); 173 if (!baselineAligned) { 174 setBaselineAligned(baselineAligned); 175 } 176 177 mWeightSum = a.getFloat(R.styleable.LinearLayoutCompat_android_weightSum, -1.0f); 178 179 mBaselineAlignedChildIndex = 180 a.getInt(R.styleable.LinearLayoutCompat_android_baselineAlignedChildIndex, -1); 181 182 mUseLargestChild = a.getBoolean(R.styleable.LinearLayoutCompat_measureWithLargestChild, false); 183 184 setDividerDrawable(a.getDrawable(R.styleable.LinearLayoutCompat_divider)); 185 mShowDividers = a.getInt(R.styleable.LinearLayoutCompat_showDividers, SHOW_DIVIDER_NONE); 186 mDividerPadding = a.getDimensionPixelSize(R.styleable.LinearLayoutCompat_dividerPadding, 0); 187 188 a.recycle(); 189 } 190 191 /** 192 * Set how dividers should be shown between items in this layout 193 * 194 * @param showDividers One or more of {@link #SHOW_DIVIDER_BEGINNING}, 195 * {@link #SHOW_DIVIDER_MIDDLE}, or {@link #SHOW_DIVIDER_END}, 196 * or {@link #SHOW_DIVIDER_NONE} to show no dividers. 197 */ 198 public void setShowDividers(@DividerMode int showDividers) { 199 if (showDividers != mShowDividers) { 200 requestLayout(); 201 } 202 mShowDividers = showDividers; 203 } 204 205 @Override 206 public boolean shouldDelayChildPressedState() { 207 return false; 208 } 209 210 /** 211 * @return A flag set indicating how dividers should be shown around items. 212 * @see #setShowDividers(int) 213 */ 214 @DividerMode 215 public int getShowDividers() { 216 return mShowDividers; 217 } 218 219 /** 220 * @return the divider Drawable that will divide each item. 221 * 222 * @see #setDividerDrawable(Drawable) 223 * 224 * @attr ref android.R.styleable#LinearLayout_divider 225 */ 226 public Drawable getDividerDrawable() { 227 return mDivider; 228 } 229 230 /** 231 * Set a drawable to be used as a divider between items. 232 * 233 * @param divider Drawable that will divide each item. 234 * 235 * @see #setShowDividers(int) 236 * 237 * @attr ref android.R.styleable#LinearLayout_divider 238 */ 239 public void setDividerDrawable(Drawable divider) { 240 if (divider == mDivider) { 241 return; 242 } 243 mDivider = divider; 244 if (divider != null) { 245 mDividerWidth = divider.getIntrinsicWidth(); 246 mDividerHeight = divider.getIntrinsicHeight(); 247 } else { 248 mDividerWidth = 0; 249 mDividerHeight = 0; 250 } 251 setWillNotDraw(divider == null); 252 requestLayout(); 253 } 254 255 /** 256 * Set padding displayed on both ends of dividers. 257 * 258 * @param padding Padding value in pixels that will be applied to each end 259 * 260 * @see #setShowDividers(int) 261 * @see #setDividerDrawable(Drawable) 262 * @see #getDividerPadding() 263 */ 264 public void setDividerPadding(int padding) { 265 mDividerPadding = padding; 266 } 267 268 /** 269 * Get the padding size used to inset dividers in pixels 270 * 271 * @see #setShowDividers(int) 272 * @see #setDividerDrawable(Drawable) 273 * @see #setDividerPadding(int) 274 */ 275 public int getDividerPadding() { 276 return mDividerPadding; 277 } 278 279 /** 280 * Get the width of the current divider drawable. 281 * 282 * @hide Used internally by framework. 283 */ 284 public int getDividerWidth() { 285 return mDividerWidth; 286 } 287 288 @Override 289 protected void onDraw(Canvas canvas) { 290 if (mDivider == null) { 291 return; 292 } 293 294 if (mOrientation == VERTICAL) { 295 drawDividersVertical(canvas); 296 } else { 297 drawDividersHorizontal(canvas); 298 } 299 } 300 301 void drawDividersVertical(Canvas canvas) { 302 final int count = getVirtualChildCount(); 303 for (int i = 0; i < count; i++) { 304 final View child = getVirtualChildAt(i); 305 306 if (child != null && child.getVisibility() != GONE) { 307 if (hasDividerBeforeChildAt(i)) { 308 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 309 final int top = child.getTop() - lp.topMargin - mDividerHeight; 310 drawHorizontalDivider(canvas, top); 311 } 312 } 313 } 314 315 if (hasDividerBeforeChildAt(count)) { 316 final View child = getVirtualChildAt(count - 1); 317 int bottom = 0; 318 if (child == null) { 319 bottom = getHeight() - getPaddingBottom() - mDividerHeight; 320 } else { 321 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 322 bottom = child.getBottom() + lp.bottomMargin; 323 } 324 drawHorizontalDivider(canvas, bottom); 325 } 326 } 327 328 void drawDividersHorizontal(Canvas canvas) { 329 final int count = getVirtualChildCount(); 330 final boolean isLayoutRtl = ViewUtils.isLayoutRtl(this); 331 for (int i = 0; i < count; i++) { 332 final View child = getVirtualChildAt(i); 333 334 if (child != null && child.getVisibility() != GONE) { 335 if (hasDividerBeforeChildAt(i)) { 336 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 337 final int position; 338 if (isLayoutRtl) { 339 position = child.getRight() + lp.rightMargin; 340 } else { 341 position = child.getLeft() - lp.leftMargin - mDividerWidth; 342 } 343 drawVerticalDivider(canvas, position); 344 } 345 } 346 } 347 348 if (hasDividerBeforeChildAt(count)) { 349 final View child = getVirtualChildAt(count - 1); 350 int position; 351 if (child == null) { 352 if (isLayoutRtl) { 353 position = getPaddingLeft(); 354 } else { 355 position = getWidth() - getPaddingRight() - mDividerWidth; 356 } 357 } else { 358 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 359 if (isLayoutRtl) { 360 position = child.getLeft() - lp.leftMargin - mDividerWidth; 361 } else { 362 position = child.getRight() + lp.rightMargin; 363 } 364 } 365 drawVerticalDivider(canvas, position); 366 } 367 } 368 369 void drawHorizontalDivider(Canvas canvas, int top) { 370 mDivider.setBounds(getPaddingLeft() + mDividerPadding, top, 371 getWidth() - getPaddingRight() - mDividerPadding, top + mDividerHeight); 372 mDivider.draw(canvas); 373 } 374 375 void drawVerticalDivider(Canvas canvas, int left) { 376 mDivider.setBounds(left, getPaddingTop() + mDividerPadding, 377 left + mDividerWidth, getHeight() - getPaddingBottom() - mDividerPadding); 378 mDivider.draw(canvas); 379 } 380 381 /** 382 * <p>Indicates whether widgets contained within this layout are aligned 383 * on their baseline or not.</p> 384 * 385 * @return true when widgets are baseline-aligned, false otherwise 386 */ 387 public boolean isBaselineAligned() { 388 return mBaselineAligned; 389 } 390 391 /** 392 * <p>Defines whether widgets contained in this layout are 393 * baseline-aligned or not.</p> 394 * 395 * @param baselineAligned true to align widgets on their baseline, 396 * false otherwise 397 * 398 * @attr ref android.R.styleable#LinearLayout_baselineAligned 399 */ 400 public void setBaselineAligned(boolean baselineAligned) { 401 mBaselineAligned = baselineAligned; 402 } 403 404 /** 405 * When true, all children with a weight will be considered having 406 * the minimum size of the largest child. If false, all children are 407 * measured normally. 408 * 409 * @return True to measure children with a weight using the minimum 410 * size of the largest child, false otherwise. 411 * 412 * @attr ref android.R.styleable#LinearLayout_measureWithLargestChild 413 */ 414 public boolean isMeasureWithLargestChildEnabled() { 415 return mUseLargestChild; 416 } 417 418 /** 419 * When set to true, all children with a weight will be considered having 420 * the minimum size of the largest child. If false, all children are 421 * measured normally. 422 * 423 * Disabled by default. 424 * 425 * @param enabled True to measure children with a weight using the 426 * minimum size of the largest child, false otherwise. 427 * 428 * @attr ref android.R.styleable#LinearLayout_measureWithLargestChild 429 */ 430 public void setMeasureWithLargestChildEnabled(boolean enabled) { 431 mUseLargestChild = enabled; 432 } 433 434 @Override 435 public int getBaseline() { 436 if (mBaselineAlignedChildIndex < 0) { 437 return super.getBaseline(); 438 } 439 440 if (getChildCount() <= mBaselineAlignedChildIndex) { 441 throw new RuntimeException("mBaselineAlignedChildIndex of LinearLayout " 442 + "set to an index that is out of bounds."); 443 } 444 445 final View child = getChildAt(mBaselineAlignedChildIndex); 446 final int childBaseline = child.getBaseline(); 447 448 if (childBaseline == -1) { 449 if (mBaselineAlignedChildIndex == 0) { 450 // this is just the default case, safe to return -1 451 return -1; 452 } 453 // the user picked an index that points to something that doesn't 454 // know how to calculate its baseline. 455 throw new RuntimeException("mBaselineAlignedChildIndex of LinearLayout " 456 + "points to a View that doesn't know how to get its baseline."); 457 } 458 459 // TODO: This should try to take into account the virtual offsets 460 // (See getNextLocationOffset and getLocationOffset) 461 // We should add to childTop: 462 // sum([getNextLocationOffset(getChildAt(i)) / i < mBaselineAlignedChildIndex]) 463 // and also add: 464 // getLocationOffset(child) 465 int childTop = mBaselineChildTop; 466 467 if (mOrientation == VERTICAL) { 468 final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; 469 if (majorGravity != Gravity.TOP) { 470 switch (majorGravity) { 471 case Gravity.BOTTOM: 472 childTop = getBottom() - getTop() - getPaddingBottom() - mTotalLength; 473 break; 474 475 case Gravity.CENTER_VERTICAL: 476 childTop += ((getBottom() - getTop() - getPaddingTop() - getPaddingBottom()) - 477 mTotalLength) / 2; 478 break; 479 } 480 } 481 } 482 483 LinearLayoutCompat.LayoutParams lp = (LinearLayoutCompat.LayoutParams) child.getLayoutParams(); 484 return childTop + lp.topMargin + childBaseline; 485 } 486 487 /** 488 * @return The index of the child that will be used if this layout is 489 * part of a larger layout that is baseline aligned, or -1 if none has 490 * been set. 491 */ 492 public int getBaselineAlignedChildIndex() { 493 return mBaselineAlignedChildIndex; 494 } 495 496 /** 497 * @param i The index of the child that will be used if this layout is 498 * part of a larger layout that is baseline aligned. 499 * 500 * @attr ref android.R.styleable#LinearLayout_baselineAlignedChildIndex 501 */ 502 public void setBaselineAlignedChildIndex(int i) { 503 if ((i < 0) || (i >= getChildCount())) { 504 throw new IllegalArgumentException("base aligned child index out " 505 + "of range (0, " + getChildCount() + ")"); 506 } 507 mBaselineAlignedChildIndex = i; 508 } 509 510 /** 511 * <p>Returns the view at the specified index. This method can be overriden 512 * to take into account virtual children. Refer to 513 * {@link android.widget.TableLayout} and {@link android.widget.TableRow} 514 * for an example.</p> 515 * 516 * @param index the child's index 517 * @return the child at the specified index 518 */ 519 View getVirtualChildAt(int index) { 520 return getChildAt(index); 521 } 522 523 /** 524 * <p>Returns the virtual number of children. This number might be different 525 * than the actual number of children if the layout can hold virtual 526 * children. Refer to 527 * {@link android.widget.TableLayout} and {@link android.widget.TableRow} 528 * for an example.</p> 529 * 530 * @return the virtual number of children 531 */ 532 int getVirtualChildCount() { 533 return getChildCount(); 534 } 535 536 /** 537 * Returns the desired weights sum. 538 * 539 * @return A number greater than 0.0f if the weight sum is defined, or 540 * a number lower than or equals to 0.0f if not weight sum is 541 * to be used. 542 */ 543 public float getWeightSum() { 544 return mWeightSum; 545 } 546 547 /** 548 * Defines the desired weights sum. If unspecified the weights sum is computed 549 * at layout time by adding the layout_weight of each child. 550 * 551 * This can be used for instance to give a single child 50% of the total 552 * available space by giving it a layout_weight of 0.5 and setting the 553 * weightSum to 1.0. 554 * 555 * @param weightSum a number greater than 0.0f, or a number lower than or equals 556 * to 0.0f if the weight sum should be computed from the children's 557 * layout_weight 558 */ 559 public void setWeightSum(float weightSum) { 560 mWeightSum = Math.max(0.0f, weightSum); 561 } 562 563 @Override 564 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 565 if (mOrientation == VERTICAL) { 566 measureVertical(widthMeasureSpec, heightMeasureSpec); 567 } else { 568 measureHorizontal(widthMeasureSpec, heightMeasureSpec); 569 } 570 } 571 572 /** 573 * Determines where to position dividers between children. 574 * 575 * @param childIndex Index of child to check for preceding divider 576 * @return true if there should be a divider before the child at childIndex 577 * @hide Pending API consideration. Currently only used internally by the system. 578 */ 579 protected boolean hasDividerBeforeChildAt(int childIndex) { 580 if (childIndex == 0) { 581 return (mShowDividers & SHOW_DIVIDER_BEGINNING) != 0; 582 } else if (childIndex == getChildCount()) { 583 return (mShowDividers & SHOW_DIVIDER_END) != 0; 584 } else if ((mShowDividers & SHOW_DIVIDER_MIDDLE) != 0) { 585 boolean hasVisibleViewBefore = false; 586 for (int i = childIndex - 1; i >= 0; i--) { 587 if (getChildAt(i).getVisibility() != GONE) { 588 hasVisibleViewBefore = true; 589 break; 590 } 591 } 592 return hasVisibleViewBefore; 593 } 594 return false; 595 } 596 597 /** 598 * Measures the children when the orientation of this LinearLayout is set 599 * to {@link #VERTICAL}. 600 * 601 * @param widthMeasureSpec Horizontal space requirements as imposed by the parent. 602 * @param heightMeasureSpec Vertical space requirements as imposed by the parent. 603 * 604 * @see #getOrientation() 605 * @see #setOrientation(int) 606 * @see #onMeasure(int, int) 607 */ 608 void measureVertical(int widthMeasureSpec, int heightMeasureSpec) { 609 mTotalLength = 0; 610 int maxWidth = 0; 611 int childState = 0; 612 int alternativeMaxWidth = 0; 613 int weightedMaxWidth = 0; 614 boolean allFillParent = true; 615 float totalWeight = 0; 616 617 final int count = getVirtualChildCount(); 618 619 final int widthMode = MeasureSpec.getMode(widthMeasureSpec); 620 final int heightMode = MeasureSpec.getMode(heightMeasureSpec); 621 622 boolean matchWidth = false; 623 boolean skippedMeasure = false; 624 625 final int baselineChildIndex = mBaselineAlignedChildIndex; 626 final boolean useLargestChild = mUseLargestChild; 627 628 int largestChildHeight = Integer.MIN_VALUE; 629 630 // See how tall everyone is. Also remember max width. 631 for (int i = 0; i < count; ++i) { 632 final View child = getVirtualChildAt(i); 633 634 if (child == null) { 635 mTotalLength += measureNullChild(i); 636 continue; 637 } 638 639 if (child.getVisibility() == View.GONE) { 640 i += getChildrenSkipCount(child, i); 641 continue; 642 } 643 644 if (hasDividerBeforeChildAt(i)) { 645 mTotalLength += mDividerHeight; 646 } 647 648 LinearLayoutCompat.LayoutParams lp = (LinearLayoutCompat.LayoutParams) child.getLayoutParams(); 649 650 totalWeight += lp.weight; 651 652 if (heightMode == MeasureSpec.EXACTLY && lp.height == 0 && lp.weight > 0) { 653 // Optimization: don't bother measuring children who are going to use 654 // leftover space. These views will get measured again down below if 655 // there is any leftover space. 656 final int totalLength = mTotalLength; 657 mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin); 658 skippedMeasure = true; 659 } else { 660 int oldHeight = Integer.MIN_VALUE; 661 662 if (lp.height == 0 && lp.weight > 0) { 663 // heightMode is either UNSPECIFIED or AT_MOST, and this 664 // child wanted to stretch to fill available space. 665 // Translate that to WRAP_CONTENT so that it does not end up 666 // with a height of 0 667 oldHeight = 0; 668 lp.height = LayoutParams.WRAP_CONTENT; 669 } 670 671 // Determine how big this child would like to be. If this or 672 // previous children have given a weight, then we allow it to 673 // use all available space (and we will shrink things later 674 // if needed). 675 measureChildBeforeLayout( 676 child, i, widthMeasureSpec, 0, heightMeasureSpec, 677 totalWeight == 0 ? mTotalLength : 0); 678 679 if (oldHeight != Integer.MIN_VALUE) { 680 lp.height = oldHeight; 681 } 682 683 final int childHeight = child.getMeasuredHeight(); 684 final int totalLength = mTotalLength; 685 mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin + 686 lp.bottomMargin + getNextLocationOffset(child)); 687 688 if (useLargestChild) { 689 largestChildHeight = Math.max(childHeight, largestChildHeight); 690 } 691 } 692 693 /** 694 * If applicable, compute the additional offset to the child's baseline 695 * we'll need later when asked {@link #getBaseline}. 696 */ 697 if ((baselineChildIndex >= 0) && (baselineChildIndex == i + 1)) { 698 mBaselineChildTop = mTotalLength; 699 } 700 701 // if we are trying to use a child index for our baseline, the above 702 // book keeping only works if there are no children above it with 703 // weight. fail fast to aid the developer. 704 if (i < baselineChildIndex && lp.weight > 0) { 705 throw new RuntimeException("A child of LinearLayout with index " 706 + "less than mBaselineAlignedChildIndex has weight > 0, which " 707 + "won't work. Either remove the weight, or don't set " 708 + "mBaselineAlignedChildIndex."); 709 } 710 711 boolean matchWidthLocally = false; 712 if (widthMode != MeasureSpec.EXACTLY && lp.width == LayoutParams.MATCH_PARENT) { 713 // The width of the linear layout will scale, and at least one 714 // child said it wanted to match our width. Set a flag 715 // indicating that we need to remeasure at least that view when 716 // we know our width. 717 matchWidth = true; 718 matchWidthLocally = true; 719 } 720 721 final int margin = lp.leftMargin + lp.rightMargin; 722 final int measuredWidth = child.getMeasuredWidth() + margin; 723 maxWidth = Math.max(maxWidth, measuredWidth); 724 childState = ViewUtils.combineMeasuredStates(childState, 725 ViewCompat.getMeasuredState(child)); 726 727 allFillParent = allFillParent && lp.width == LayoutParams.MATCH_PARENT; 728 if (lp.weight > 0) { 729 /* 730 * Widths of weighted Views are bogus if we end up 731 * remeasuring, so keep them separate. 732 */ 733 weightedMaxWidth = Math.max(weightedMaxWidth, 734 matchWidthLocally ? margin : measuredWidth); 735 } else { 736 alternativeMaxWidth = Math.max(alternativeMaxWidth, 737 matchWidthLocally ? margin : measuredWidth); 738 } 739 740 i += getChildrenSkipCount(child, i); 741 } 742 743 if (mTotalLength > 0 && hasDividerBeforeChildAt(count)) { 744 mTotalLength += mDividerHeight; 745 } 746 747 if (useLargestChild && 748 (heightMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.UNSPECIFIED)) { 749 mTotalLength = 0; 750 751 for (int i = 0; i < count; ++i) { 752 final View child = getVirtualChildAt(i); 753 754 if (child == null) { 755 mTotalLength += measureNullChild(i); 756 continue; 757 } 758 759 if (child.getVisibility() == GONE) { 760 i += getChildrenSkipCount(child, i); 761 continue; 762 } 763 764 final LinearLayoutCompat.LayoutParams lp = (LinearLayoutCompat.LayoutParams) 765 child.getLayoutParams(); 766 // Account for negative margins 767 final int totalLength = mTotalLength; 768 mTotalLength = Math.max(totalLength, totalLength + largestChildHeight + 769 lp.topMargin + lp.bottomMargin + getNextLocationOffset(child)); 770 } 771 } 772 773 // Add in our padding 774 mTotalLength += getPaddingTop() + getPaddingBottom(); 775 776 int heightSize = mTotalLength; 777 778 // Check against our minimum height 779 heightSize = Math.max(heightSize, getSuggestedMinimumHeight()); 780 781 // Reconcile our calculated size with the heightMeasureSpec 782 int heightSizeAndState = ViewCompat.resolveSizeAndState(heightSize, heightMeasureSpec, 0); 783 heightSize = heightSizeAndState & ViewCompat.MEASURED_SIZE_MASK; 784 785 // Either expand children with weight to take up available space or 786 // shrink them if they extend beyond our current bounds. If we skipped 787 // measurement on any children, we need to measure them now. 788 int delta = heightSize - mTotalLength; 789 if (skippedMeasure || delta != 0 && totalWeight > 0.0f) { 790 float weightSum = mWeightSum > 0.0f ? mWeightSum : totalWeight; 791 792 mTotalLength = 0; 793 794 for (int i = 0; i < count; ++i) { 795 final View child = getVirtualChildAt(i); 796 797 if (child.getVisibility() == View.GONE) { 798 continue; 799 } 800 801 LinearLayoutCompat.LayoutParams lp = (LinearLayoutCompat.LayoutParams) child.getLayoutParams(); 802 803 float childExtra = lp.weight; 804 if (childExtra > 0) { 805 // Child said it could absorb extra space -- give him his share 806 int share = (int) (childExtra * delta / weightSum); 807 weightSum -= childExtra; 808 delta -= share; 809 810 final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, 811 getPaddingLeft() + getPaddingRight() + 812 lp.leftMargin + lp.rightMargin, lp.width); 813 814 // TODO: Use a field like lp.isMeasured to figure out if this 815 // child has been previously measured 816 if ((lp.height != 0) || (heightMode != MeasureSpec.EXACTLY)) { 817 // child was measured once already above... 818 // base new measurement on stored values 819 int childHeight = child.getMeasuredHeight() + share; 820 if (childHeight < 0) { 821 childHeight = 0; 822 } 823 824 child.measure(childWidthMeasureSpec, 825 MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY)); 826 } else { 827 // child was skipped in the loop above. 828 // Measure for this first time here 829 child.measure(childWidthMeasureSpec, 830 MeasureSpec.makeMeasureSpec(share > 0 ? share : 0, 831 MeasureSpec.EXACTLY)); 832 } 833 834 // Child may now not fit in vertical dimension. 835 childState = ViewUtils.combineMeasuredStates(childState, 836 ViewCompat.getMeasuredState(child) & (ViewCompat.MEASURED_STATE_MASK 837 >> ViewCompat.MEASURED_HEIGHT_STATE_SHIFT)); 838 } 839 840 final int margin = lp.leftMargin + lp.rightMargin; 841 final int measuredWidth = child.getMeasuredWidth() + margin; 842 maxWidth = Math.max(maxWidth, measuredWidth); 843 844 boolean matchWidthLocally = widthMode != MeasureSpec.EXACTLY && 845 lp.width == LayoutParams.MATCH_PARENT; 846 847 alternativeMaxWidth = Math.max(alternativeMaxWidth, 848 matchWidthLocally ? margin : measuredWidth); 849 850 allFillParent = allFillParent && lp.width == LayoutParams.MATCH_PARENT; 851 852 final int totalLength = mTotalLength; 853 mTotalLength = Math.max(totalLength, totalLength + child.getMeasuredHeight() + 854 lp.topMargin + lp.bottomMargin + getNextLocationOffset(child)); 855 } 856 857 // Add in our padding 858 mTotalLength += getPaddingTop() + getPaddingBottom(); 859 // TODO: Should we recompute the heightSpec based on the new total length? 860 } else { 861 alternativeMaxWidth = Math.max(alternativeMaxWidth, 862 weightedMaxWidth); 863 864 865 // We have no limit, so make all weighted views as tall as the largest child. 866 // Children will have already been measured once. 867 if (useLargestChild && heightMode != MeasureSpec.EXACTLY) { 868 for (int i = 0; i < count; i++) { 869 final View child = getVirtualChildAt(i); 870 871 if (child == null || child.getVisibility() == View.GONE) { 872 continue; 873 } 874 875 final LinearLayoutCompat.LayoutParams lp = 876 (LinearLayoutCompat.LayoutParams) child.getLayoutParams(); 877 878 float childExtra = lp.weight; 879 if (childExtra > 0) { 880 child.measure( 881 MeasureSpec.makeMeasureSpec(child.getMeasuredWidth(), 882 MeasureSpec.EXACTLY), 883 MeasureSpec.makeMeasureSpec(largestChildHeight, 884 MeasureSpec.EXACTLY)); 885 } 886 } 887 } 888 } 889 890 if (!allFillParent && widthMode != MeasureSpec.EXACTLY) { 891 maxWidth = alternativeMaxWidth; 892 } 893 894 maxWidth += getPaddingLeft() + getPaddingRight(); 895 896 // Check against our minimum width 897 maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth()); 898 899 setMeasuredDimension(ViewCompat.resolveSizeAndState(maxWidth, widthMeasureSpec, childState), 900 heightSizeAndState); 901 902 if (matchWidth) { 903 forceUniformWidth(count, heightMeasureSpec); 904 } 905 } 906 907 private void forceUniformWidth(int count, int heightMeasureSpec) { 908 // Pretend that the linear layout has an exact size. 909 int uniformMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth(), 910 MeasureSpec.EXACTLY); 911 for (int i = 0; i< count; ++i) { 912 final View child = getVirtualChildAt(i); 913 if (child.getVisibility() != GONE) { 914 LinearLayoutCompat.LayoutParams lp = ((LinearLayoutCompat.LayoutParams)child.getLayoutParams()); 915 916 if (lp.width == LayoutParams.MATCH_PARENT) { 917 // Temporarily force children to reuse their old measured height 918 // FIXME: this may not be right for something like wrapping text? 919 int oldHeight = lp.height; 920 lp.height = child.getMeasuredHeight(); 921 922 // Remeasue with new dimensions 923 measureChildWithMargins(child, uniformMeasureSpec, 0, heightMeasureSpec, 0); 924 lp.height = oldHeight; 925 } 926 } 927 } 928 } 929 930 /** 931 * Measures the children when the orientation of this LinearLayout is set 932 * to {@link #HORIZONTAL}. 933 * 934 * @param widthMeasureSpec Horizontal space requirements as imposed by the parent. 935 * @param heightMeasureSpec Vertical space requirements as imposed by the parent. 936 * 937 * @see #getOrientation() 938 * @see #setOrientation(int) 939 * @see #onMeasure(int, int) 940 */ 941 void measureHorizontal(int widthMeasureSpec, int heightMeasureSpec) { 942 mTotalLength = 0; 943 int maxHeight = 0; 944 int childState = 0; 945 int alternativeMaxHeight = 0; 946 int weightedMaxHeight = 0; 947 boolean allFillParent = true; 948 float totalWeight = 0; 949 950 final int count = getVirtualChildCount(); 951 952 final int widthMode = MeasureSpec.getMode(widthMeasureSpec); 953 final int heightMode = MeasureSpec.getMode(heightMeasureSpec); 954 955 boolean matchHeight = false; 956 boolean skippedMeasure = false; 957 958 if (mMaxAscent == null || mMaxDescent == null) { 959 mMaxAscent = new int[VERTICAL_GRAVITY_COUNT]; 960 mMaxDescent = new int[VERTICAL_GRAVITY_COUNT]; 961 } 962 963 final int[] maxAscent = mMaxAscent; 964 final int[] maxDescent = mMaxDescent; 965 966 maxAscent[0] = maxAscent[1] = maxAscent[2] = maxAscent[3] = -1; 967 maxDescent[0] = maxDescent[1] = maxDescent[2] = maxDescent[3] = -1; 968 969 final boolean baselineAligned = mBaselineAligned; 970 final boolean useLargestChild = mUseLargestChild; 971 972 final boolean isExactly = widthMode == MeasureSpec.EXACTLY; 973 974 int largestChildWidth = Integer.MIN_VALUE; 975 976 // See how wide everyone is. Also remember max height. 977 for (int i = 0; i < count; ++i) { 978 final View child = getVirtualChildAt(i); 979 980 if (child == null) { 981 mTotalLength += measureNullChild(i); 982 continue; 983 } 984 985 if (child.getVisibility() == GONE) { 986 i += getChildrenSkipCount(child, i); 987 continue; 988 } 989 990 if (hasDividerBeforeChildAt(i)) { 991 mTotalLength += mDividerWidth; 992 } 993 994 final LinearLayoutCompat.LayoutParams lp = (LinearLayoutCompat.LayoutParams) 995 child.getLayoutParams(); 996 997 totalWeight += lp.weight; 998 999 if (widthMode == MeasureSpec.EXACTLY && lp.width == 0 && lp.weight > 0) { 1000 // Optimization: don't bother measuring children who are going to use 1001 // leftover space. These views will get measured again down below if 1002 // there is any leftover space. 1003 if (isExactly) { 1004 mTotalLength += lp.leftMargin + lp.rightMargin; 1005 } else { 1006 final int totalLength = mTotalLength; 1007 mTotalLength = Math.max(totalLength, totalLength + 1008 lp.leftMargin + lp.rightMargin); 1009 } 1010 1011 // Baseline alignment requires to measure widgets to obtain the 1012 // baseline offset (in particular for TextViews). The following 1013 // defeats the optimization mentioned above. Allow the child to 1014 // use as much space as it wants because we can shrink things 1015 // later (and re-measure). 1016 if (baselineAligned) { 1017 final int freeSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); 1018 child.measure(freeSpec, freeSpec); 1019 } else { 1020 skippedMeasure = true; 1021 } 1022 } else { 1023 int oldWidth = Integer.MIN_VALUE; 1024 1025 if (lp.width == 0 && lp.weight > 0) { 1026 // widthMode is either UNSPECIFIED or AT_MOST, and this 1027 // child 1028 // wanted to stretch to fill available space. Translate that to 1029 // WRAP_CONTENT so that it does not end up with a width of 0 1030 oldWidth = 0; 1031 lp.width = LayoutParams.WRAP_CONTENT; 1032 } 1033 1034 // Determine how big this child would like to be. If this or 1035 // previous children have given a weight, then we allow it to 1036 // use all available space (and we will shrink things later 1037 // if needed). 1038 measureChildBeforeLayout(child, i, widthMeasureSpec, 1039 totalWeight == 0 ? mTotalLength : 0, 1040 heightMeasureSpec, 0); 1041 1042 if (oldWidth != Integer.MIN_VALUE) { 1043 lp.width = oldWidth; 1044 } 1045 1046 final int childWidth = child.getMeasuredWidth(); 1047 if (isExactly) { 1048 mTotalLength += childWidth + lp.leftMargin + lp.rightMargin + 1049 getNextLocationOffset(child); 1050 } else { 1051 final int totalLength = mTotalLength; 1052 mTotalLength = Math.max(totalLength, totalLength + childWidth + lp.leftMargin + 1053 lp.rightMargin + getNextLocationOffset(child)); 1054 } 1055 1056 if (useLargestChild) { 1057 largestChildWidth = Math.max(childWidth, largestChildWidth); 1058 } 1059 } 1060 1061 boolean matchHeightLocally = false; 1062 if (heightMode != MeasureSpec.EXACTLY && lp.height == LayoutParams.MATCH_PARENT) { 1063 // The height of the linear layout will scale, and at least one 1064 // child said it wanted to match our height. Set a flag indicating that 1065 // we need to remeasure at least that view when we know our height. 1066 matchHeight = true; 1067 matchHeightLocally = true; 1068 } 1069 1070 final int margin = lp.topMargin + lp.bottomMargin; 1071 final int childHeight = child.getMeasuredHeight() + margin; 1072 childState = ViewUtils.combineMeasuredStates(childState, 1073 ViewCompat.getMeasuredState(child)); 1074 1075 if (baselineAligned) { 1076 final int childBaseline = child.getBaseline(); 1077 if (childBaseline != -1) { 1078 // Translates the child's vertical gravity into an index 1079 // in the range 0..VERTICAL_GRAVITY_COUNT 1080 final int gravity = (lp.gravity < 0 ? mGravity : lp.gravity) 1081 & Gravity.VERTICAL_GRAVITY_MASK; 1082 final int index = ((gravity >> Gravity.AXIS_Y_SHIFT) 1083 & ~Gravity.AXIS_SPECIFIED) >> 1; 1084 1085 maxAscent[index] = Math.max(maxAscent[index], childBaseline); 1086 maxDescent[index] = Math.max(maxDescent[index], childHeight - childBaseline); 1087 } 1088 } 1089 1090 maxHeight = Math.max(maxHeight, childHeight); 1091 1092 allFillParent = allFillParent && lp.height == LayoutParams.MATCH_PARENT; 1093 if (lp.weight > 0) { 1094 /* 1095 * Heights of weighted Views are bogus if we end up 1096 * remeasuring, so keep them separate. 1097 */ 1098 weightedMaxHeight = Math.max(weightedMaxHeight, 1099 matchHeightLocally ? margin : childHeight); 1100 } else { 1101 alternativeMaxHeight = Math.max(alternativeMaxHeight, 1102 matchHeightLocally ? margin : childHeight); 1103 } 1104 1105 i += getChildrenSkipCount(child, i); 1106 } 1107 1108 if (mTotalLength > 0 && hasDividerBeforeChildAt(count)) { 1109 mTotalLength += mDividerWidth; 1110 } 1111 1112 // Check mMaxAscent[INDEX_TOP] first because it maps to Gravity.TOP, 1113 // the most common case 1114 if (maxAscent[INDEX_TOP] != -1 || 1115 maxAscent[INDEX_CENTER_VERTICAL] != -1 || 1116 maxAscent[INDEX_BOTTOM] != -1 || 1117 maxAscent[INDEX_FILL] != -1) { 1118 final int ascent = Math.max(maxAscent[INDEX_FILL], 1119 Math.max(maxAscent[INDEX_CENTER_VERTICAL], 1120 Math.max(maxAscent[INDEX_TOP], maxAscent[INDEX_BOTTOM]))); 1121 final int descent = Math.max(maxDescent[INDEX_FILL], 1122 Math.max(maxDescent[INDEX_CENTER_VERTICAL], 1123 Math.max(maxDescent[INDEX_TOP], maxDescent[INDEX_BOTTOM]))); 1124 maxHeight = Math.max(maxHeight, ascent + descent); 1125 } 1126 1127 if (useLargestChild && 1128 (widthMode == MeasureSpec.AT_MOST || widthMode == MeasureSpec.UNSPECIFIED)) { 1129 mTotalLength = 0; 1130 1131 for (int i = 0; i < count; ++i) { 1132 final View child = getVirtualChildAt(i); 1133 1134 if (child == null) { 1135 mTotalLength += measureNullChild(i); 1136 continue; 1137 } 1138 1139 if (child.getVisibility() == GONE) { 1140 i += getChildrenSkipCount(child, i); 1141 continue; 1142 } 1143 1144 final LinearLayoutCompat.LayoutParams lp = (LinearLayoutCompat.LayoutParams) 1145 child.getLayoutParams(); 1146 if (isExactly) { 1147 mTotalLength += largestChildWidth + lp.leftMargin + lp.rightMargin + 1148 getNextLocationOffset(child); 1149 } else { 1150 final int totalLength = mTotalLength; 1151 mTotalLength = Math.max(totalLength, totalLength + largestChildWidth + 1152 lp.leftMargin + lp.rightMargin + getNextLocationOffset(child)); 1153 } 1154 } 1155 } 1156 1157 // Add in our padding 1158 mTotalLength += getPaddingLeft() + getPaddingRight(); 1159 1160 int widthSize = mTotalLength; 1161 1162 // Check against our minimum width 1163 widthSize = Math.max(widthSize, getSuggestedMinimumWidth()); 1164 1165 // Reconcile our calculated size with the widthMeasureSpec 1166 int widthSizeAndState = ViewCompat.resolveSizeAndState(widthSize, widthMeasureSpec, 0); 1167 widthSize = widthSizeAndState & ViewCompat.MEASURED_SIZE_MASK; 1168 1169 // Either expand children with weight to take up available space or 1170 // shrink them if they extend beyond our current bounds. If we skipped 1171 // measurement on any children, we need to measure them now. 1172 int delta = widthSize - mTotalLength; 1173 if (skippedMeasure || delta != 0 && totalWeight > 0.0f) { 1174 float weightSum = mWeightSum > 0.0f ? mWeightSum : totalWeight; 1175 1176 maxAscent[0] = maxAscent[1] = maxAscent[2] = maxAscent[3] = -1; 1177 maxDescent[0] = maxDescent[1] = maxDescent[2] = maxDescent[3] = -1; 1178 maxHeight = -1; 1179 1180 mTotalLength = 0; 1181 1182 for (int i = 0; i < count; ++i) { 1183 final View child = getVirtualChildAt(i); 1184 1185 if (child == null || child.getVisibility() == View.GONE) { 1186 continue; 1187 } 1188 1189 final LinearLayoutCompat.LayoutParams lp = 1190 (LinearLayoutCompat.LayoutParams) child.getLayoutParams(); 1191 1192 float childExtra = lp.weight; 1193 if (childExtra > 0) { 1194 // Child said it could absorb extra space -- give him his share 1195 int share = (int) (childExtra * delta / weightSum); 1196 weightSum -= childExtra; 1197 delta -= share; 1198 1199 final int childHeightMeasureSpec = getChildMeasureSpec( 1200 heightMeasureSpec, 1201 getPaddingTop() + getPaddingBottom() + lp.topMargin + lp.bottomMargin, 1202 lp.height); 1203 1204 // TODO: Use a field like lp.isMeasured to figure out if this 1205 // child has been previously measured 1206 if ((lp.width != 0) || (widthMode != MeasureSpec.EXACTLY)) { 1207 // child was measured once already above ... base new measurement 1208 // on stored values 1209 int childWidth = child.getMeasuredWidth() + share; 1210 if (childWidth < 0) { 1211 childWidth = 0; 1212 } 1213 1214 child.measure( 1215 MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY), 1216 childHeightMeasureSpec); 1217 } else { 1218 // child was skipped in the loop above. Measure for this first time here 1219 child.measure(MeasureSpec.makeMeasureSpec( 1220 share > 0 ? share : 0, MeasureSpec.EXACTLY), 1221 childHeightMeasureSpec); 1222 } 1223 1224 // Child may now not fit in horizontal dimension. 1225 childState = ViewUtils.combineMeasuredStates(childState, 1226 ViewCompat.getMeasuredState(child) & ViewCompat.MEASURED_STATE_MASK); 1227 } 1228 1229 if (isExactly) { 1230 mTotalLength += child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin + 1231 getNextLocationOffset(child); 1232 } else { 1233 final int totalLength = mTotalLength; 1234 mTotalLength = Math.max(totalLength, totalLength + child.getMeasuredWidth() + 1235 lp.leftMargin + lp.rightMargin + getNextLocationOffset(child)); 1236 } 1237 1238 boolean matchHeightLocally = heightMode != MeasureSpec.EXACTLY && 1239 lp.height == LayoutParams.MATCH_PARENT; 1240 1241 final int margin = lp.topMargin + lp .bottomMargin; 1242 int childHeight = child.getMeasuredHeight() + margin; 1243 maxHeight = Math.max(maxHeight, childHeight); 1244 alternativeMaxHeight = Math.max(alternativeMaxHeight, 1245 matchHeightLocally ? margin : childHeight); 1246 1247 allFillParent = allFillParent && lp.height == LayoutParams.MATCH_PARENT; 1248 1249 if (baselineAligned) { 1250 final int childBaseline = child.getBaseline(); 1251 if (childBaseline != -1) { 1252 // Translates the child's vertical gravity into an index in the range 0..2 1253 final int gravity = (lp.gravity < 0 ? mGravity : lp.gravity) 1254 & Gravity.VERTICAL_GRAVITY_MASK; 1255 final int index = ((gravity >> Gravity.AXIS_Y_SHIFT) 1256 & ~Gravity.AXIS_SPECIFIED) >> 1; 1257 1258 maxAscent[index] = Math.max(maxAscent[index], childBaseline); 1259 maxDescent[index] = Math.max(maxDescent[index], 1260 childHeight - childBaseline); 1261 } 1262 } 1263 } 1264 1265 // Add in our padding 1266 mTotalLength += getPaddingLeft() + getPaddingRight(); 1267 // TODO: Should we update widthSize with the new total length? 1268 1269 // Check mMaxAscent[INDEX_TOP] first because it maps to Gravity.TOP, 1270 // the most common case 1271 if (maxAscent[INDEX_TOP] != -1 || 1272 maxAscent[INDEX_CENTER_VERTICAL] != -1 || 1273 maxAscent[INDEX_BOTTOM] != -1 || 1274 maxAscent[INDEX_FILL] != -1) { 1275 final int ascent = Math.max(maxAscent[INDEX_FILL], 1276 Math.max(maxAscent[INDEX_CENTER_VERTICAL], 1277 Math.max(maxAscent[INDEX_TOP], maxAscent[INDEX_BOTTOM]))); 1278 final int descent = Math.max(maxDescent[INDEX_FILL], 1279 Math.max(maxDescent[INDEX_CENTER_VERTICAL], 1280 Math.max(maxDescent[INDEX_TOP], maxDescent[INDEX_BOTTOM]))); 1281 maxHeight = Math.max(maxHeight, ascent + descent); 1282 } 1283 } else { 1284 alternativeMaxHeight = Math.max(alternativeMaxHeight, weightedMaxHeight); 1285 1286 // We have no limit, so make all weighted views as wide as the largest child. 1287 // Children will have already been measured once. 1288 if (useLargestChild && widthMode != MeasureSpec.EXACTLY) { 1289 for (int i = 0; i < count; i++) { 1290 final View child = getVirtualChildAt(i); 1291 1292 if (child == null || child.getVisibility() == View.GONE) { 1293 continue; 1294 } 1295 1296 final LinearLayoutCompat.LayoutParams lp = 1297 (LinearLayoutCompat.LayoutParams) child.getLayoutParams(); 1298 1299 float childExtra = lp.weight; 1300 if (childExtra > 0) { 1301 child.measure( 1302 MeasureSpec.makeMeasureSpec(largestChildWidth, MeasureSpec.EXACTLY), 1303 MeasureSpec.makeMeasureSpec(child.getMeasuredHeight(), 1304 MeasureSpec.EXACTLY)); 1305 } 1306 } 1307 } 1308 } 1309 1310 if (!allFillParent && heightMode != MeasureSpec.EXACTLY) { 1311 maxHeight = alternativeMaxHeight; 1312 } 1313 1314 maxHeight += getPaddingTop() + getPaddingBottom(); 1315 1316 // Check against our minimum height 1317 maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight()); 1318 1319 setMeasuredDimension(widthSizeAndState | (childState&ViewCompat.MEASURED_STATE_MASK), 1320 ViewCompat.resolveSizeAndState(maxHeight, heightMeasureSpec, 1321 (childState<<ViewCompat.MEASURED_HEIGHT_STATE_SHIFT))); 1322 1323 if (matchHeight) { 1324 forceUniformHeight(count, widthMeasureSpec); 1325 } 1326 } 1327 1328 private void forceUniformHeight(int count, int widthMeasureSpec) { 1329 // Pretend that the linear layout has an exact size. This is the measured height of 1330 // ourselves. The measured height should be the max height of the children, changed 1331 // to accommodate the heightMeasureSpec from the parent 1332 int uniformMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight(), 1333 MeasureSpec.EXACTLY); 1334 for (int i = 0; i < count; ++i) { 1335 final View child = getVirtualChildAt(i); 1336 if (child.getVisibility() != GONE) { 1337 LinearLayoutCompat.LayoutParams lp = (LinearLayoutCompat.LayoutParams) child.getLayoutParams(); 1338 1339 if (lp.height == LayoutParams.MATCH_PARENT) { 1340 // Temporarily force children to reuse their old measured width 1341 // FIXME: this may not be right for something like wrapping text? 1342 int oldWidth = lp.width; 1343 lp.width = child.getMeasuredWidth(); 1344 1345 // Remeasure with new dimensions 1346 measureChildWithMargins(child, widthMeasureSpec, 0, uniformMeasureSpec, 0); 1347 lp.width = oldWidth; 1348 } 1349 } 1350 } 1351 } 1352 1353 /** 1354 * <p>Returns the number of children to skip after measuring/laying out 1355 * the specified child.</p> 1356 * 1357 * @param child the child after which we want to skip children 1358 * @param index the index of the child after which we want to skip children 1359 * @return the number of children to skip, 0 by default 1360 */ 1361 int getChildrenSkipCount(View child, int index) { 1362 return 0; 1363 } 1364 1365 /** 1366 * <p>Returns the size (width or height) that should be occupied by a null 1367 * child.</p> 1368 * 1369 * @param childIndex the index of the null child 1370 * @return the width or height of the child depending on the orientation 1371 */ 1372 int measureNullChild(int childIndex) { 1373 return 0; 1374 } 1375 1376 /** 1377 * <p>Measure the child according to the parent's measure specs. This 1378 * method should be overriden by subclasses to force the sizing of 1379 * children. This method is called by {@link #measureVertical(int, int)} and 1380 * {@link #measureHorizontal(int, int)}.</p> 1381 * 1382 * @param child the child to measure 1383 * @param childIndex the index of the child in this view 1384 * @param widthMeasureSpec horizontal space requirements as imposed by the parent 1385 * @param totalWidth extra space that has been used up by the parent horizontally 1386 * @param heightMeasureSpec vertical space requirements as imposed by the parent 1387 * @param totalHeight extra space that has been used up by the parent vertically 1388 */ 1389 void measureChildBeforeLayout(View child, int childIndex, 1390 int widthMeasureSpec, int totalWidth, int heightMeasureSpec, 1391 int totalHeight) { 1392 measureChildWithMargins(child, widthMeasureSpec, totalWidth, 1393 heightMeasureSpec, totalHeight); 1394 } 1395 1396 /** 1397 * <p>Return the location offset of the specified child. This can be used 1398 * by subclasses to change the location of a given widget.</p> 1399 * 1400 * @param child the child for which to obtain the location offset 1401 * @return the location offset in pixels 1402 */ 1403 int getLocationOffset(View child) { 1404 return 0; 1405 } 1406 1407 /** 1408 * <p>Return the size offset of the next sibling of the specified child. 1409 * This can be used by subclasses to change the location of the widget 1410 * following <code>child</code>.</p> 1411 * 1412 * @param child the child whose next sibling will be moved 1413 * @return the location offset of the next child in pixels 1414 */ 1415 int getNextLocationOffset(View child) { 1416 return 0; 1417 } 1418 1419 @Override 1420 protected void onLayout(boolean changed, int l, int t, int r, int b) { 1421 if (mOrientation == VERTICAL) { 1422 layoutVertical(l, t, r, b); 1423 } else { 1424 layoutHorizontal(l, t, r, b); 1425 } 1426 } 1427 1428 /** 1429 * Position the children during a layout pass if the orientation of this 1430 * LinearLayout is set to {@link #VERTICAL}. 1431 * 1432 * @see #getOrientation() 1433 * @see #setOrientation(int) 1434 * @see #onLayout(boolean, int, int, int, int) 1435 * @param left 1436 * @param top 1437 * @param right 1438 * @param bottom 1439 */ 1440 void layoutVertical(int left, int top, int right, int bottom) { 1441 final int paddingLeft = getPaddingLeft(); 1442 1443 int childTop; 1444 int childLeft; 1445 1446 // Where right end of child should go 1447 final int width = right - left; 1448 int childRight = width - getPaddingRight(); 1449 1450 // Space available for child 1451 int childSpace = width - paddingLeft - getPaddingRight(); 1452 1453 final int count = getVirtualChildCount(); 1454 1455 final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; 1456 final int minorGravity = mGravity & GravityCompat.RELATIVE_HORIZONTAL_GRAVITY_MASK; 1457 1458 switch (majorGravity) { 1459 case Gravity.BOTTOM: 1460 // mTotalLength contains the padding already 1461 childTop = getPaddingTop() + bottom - top - mTotalLength; 1462 break; 1463 1464 // mTotalLength contains the padding already 1465 case Gravity.CENTER_VERTICAL: 1466 childTop = getPaddingTop() + (bottom - top - mTotalLength) / 2; 1467 break; 1468 1469 case Gravity.TOP: 1470 default: 1471 childTop = getPaddingTop(); 1472 break; 1473 } 1474 1475 for (int i = 0; i < count; i++) { 1476 final View child = getVirtualChildAt(i); 1477 if (child == null) { 1478 childTop += measureNullChild(i); 1479 } else if (child.getVisibility() != GONE) { 1480 final int childWidth = child.getMeasuredWidth(); 1481 final int childHeight = child.getMeasuredHeight(); 1482 1483 final LinearLayoutCompat.LayoutParams lp = 1484 (LinearLayoutCompat.LayoutParams) child.getLayoutParams(); 1485 1486 int gravity = lp.gravity; 1487 if (gravity < 0) { 1488 gravity = minorGravity; 1489 } 1490 final int layoutDirection = ViewCompat.getLayoutDirection(this); 1491 final int absoluteGravity = GravityCompat.getAbsoluteGravity(gravity, 1492 layoutDirection); 1493 switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { 1494 case Gravity.CENTER_HORIZONTAL: 1495 childLeft = paddingLeft + ((childSpace - childWidth) / 2) 1496 + lp.leftMargin - lp.rightMargin; 1497 break; 1498 1499 case Gravity.RIGHT: 1500 childLeft = childRight - childWidth - lp.rightMargin; 1501 break; 1502 1503 case Gravity.LEFT: 1504 default: 1505 childLeft = paddingLeft + lp.leftMargin; 1506 break; 1507 } 1508 1509 if (hasDividerBeforeChildAt(i)) { 1510 childTop += mDividerHeight; 1511 } 1512 1513 childTop += lp.topMargin; 1514 setChildFrame(child, childLeft, childTop + getLocationOffset(child), 1515 childWidth, childHeight); 1516 childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child); 1517 1518 i += getChildrenSkipCount(child, i); 1519 } 1520 } 1521 } 1522 1523 /** 1524 * Position the children during a layout pass if the orientation of this 1525 * LinearLayout is set to {@link #HORIZONTAL}. 1526 * 1527 * @see #getOrientation() 1528 * @see #setOrientation(int) 1529 * @see #onLayout(boolean, int, int, int, int) 1530 * @param left 1531 * @param top 1532 * @param right 1533 * @param bottom 1534 */ 1535 void layoutHorizontal(int left, int top, int right, int bottom) { 1536 final boolean isLayoutRtl = ViewUtils.isLayoutRtl(this); 1537 final int paddingTop = getPaddingTop(); 1538 1539 int childTop; 1540 int childLeft; 1541 1542 // Where bottom of child should go 1543 final int height = bottom - top; 1544 int childBottom = height - getPaddingBottom(); 1545 1546 // Space available for child 1547 int childSpace = height - paddingTop - getPaddingBottom(); 1548 1549 final int count = getVirtualChildCount(); 1550 1551 final int majorGravity = mGravity & GravityCompat.RELATIVE_HORIZONTAL_GRAVITY_MASK; 1552 final int minorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; 1553 1554 final boolean baselineAligned = mBaselineAligned; 1555 1556 final int[] maxAscent = mMaxAscent; 1557 final int[] maxDescent = mMaxDescent; 1558 1559 final int layoutDirection = ViewCompat.getLayoutDirection(this); 1560 switch (GravityCompat.getAbsoluteGravity(majorGravity, layoutDirection)) { 1561 case Gravity.RIGHT: 1562 // mTotalLength contains the padding already 1563 childLeft = getPaddingLeft() + right - left - mTotalLength; 1564 break; 1565 1566 case Gravity.CENTER_HORIZONTAL: 1567 // mTotalLength contains the padding already 1568 childLeft = getPaddingLeft() + (right - left - mTotalLength) / 2; 1569 break; 1570 1571 case Gravity.LEFT: 1572 default: 1573 childLeft = getPaddingLeft(); 1574 break; 1575 } 1576 1577 int start = 0; 1578 int dir = 1; 1579 //In case of RTL, start drawing from the last child. 1580 if (isLayoutRtl) { 1581 start = count - 1; 1582 dir = -1; 1583 } 1584 1585 for (int i = 0; i < count; i++) { 1586 int childIndex = start + dir * i; 1587 final View child = getVirtualChildAt(childIndex); 1588 1589 if (child == null) { 1590 childLeft += measureNullChild(childIndex); 1591 } else if (child.getVisibility() != GONE) { 1592 final int childWidth = child.getMeasuredWidth(); 1593 final int childHeight = child.getMeasuredHeight(); 1594 int childBaseline = -1; 1595 1596 final LinearLayoutCompat.LayoutParams lp = 1597 (LinearLayoutCompat.LayoutParams) child.getLayoutParams(); 1598 1599 if (baselineAligned && lp.height != LayoutParams.MATCH_PARENT) { 1600 childBaseline = child.getBaseline(); 1601 } 1602 1603 int gravity = lp.gravity; 1604 if (gravity < 0) { 1605 gravity = minorGravity; 1606 } 1607 1608 switch (gravity & Gravity.VERTICAL_GRAVITY_MASK) { 1609 case Gravity.TOP: 1610 childTop = paddingTop + lp.topMargin; 1611 if (childBaseline != -1) { 1612 childTop += maxAscent[INDEX_TOP] - childBaseline; 1613 } 1614 break; 1615 1616 case Gravity.CENTER_VERTICAL: 1617 // Removed support for baseline alignment when layout_gravity or 1618 // gravity == center_vertical. See bug #1038483. 1619 // Keep the code around if we need to re-enable this feature 1620 // if (childBaseline != -1) { 1621 // // Align baselines vertically only if the child is smaller than us 1622 // if (childSpace - childHeight > 0) { 1623 // childTop = paddingTop + (childSpace / 2) - childBaseline; 1624 // } else { 1625 // childTop = paddingTop + (childSpace - childHeight) / 2; 1626 // } 1627 // } else { 1628 childTop = paddingTop + ((childSpace - childHeight) / 2) 1629 + lp.topMargin - lp.bottomMargin; 1630 break; 1631 1632 case Gravity.BOTTOM: 1633 childTop = childBottom - childHeight - lp.bottomMargin; 1634 if (childBaseline != -1) { 1635 int descent = child.getMeasuredHeight() - childBaseline; 1636 childTop -= (maxDescent[INDEX_BOTTOM] - descent); 1637 } 1638 break; 1639 default: 1640 childTop = paddingTop; 1641 break; 1642 } 1643 1644 if (hasDividerBeforeChildAt(childIndex)) { 1645 childLeft += mDividerWidth; 1646 } 1647 1648 childLeft += lp.leftMargin; 1649 setChildFrame(child, childLeft + getLocationOffset(child), childTop, 1650 childWidth, childHeight); 1651 childLeft += childWidth + lp.rightMargin + 1652 getNextLocationOffset(child); 1653 1654 i += getChildrenSkipCount(child, childIndex); 1655 } 1656 } 1657 } 1658 1659 private void setChildFrame(View child, int left, int top, int width, int height) { 1660 child.layout(left, top, left + width, top + height); 1661 } 1662 1663 /** 1664 * Should the layout be a column or a row. 1665 * @param orientation Pass {@link #HORIZONTAL} or {@link #VERTICAL}. Default 1666 * value is {@link #HORIZONTAL}. 1667 * 1668 * @attr ref android.R.styleable#LinearLayout_orientation 1669 */ 1670 public void setOrientation(@OrientationMode int orientation) { 1671 if (mOrientation != orientation) { 1672 mOrientation = orientation; 1673 requestLayout(); 1674 } 1675 } 1676 1677 /** 1678 * Returns the current orientation. 1679 * 1680 * @return either {@link #HORIZONTAL} or {@link #VERTICAL} 1681 */ 1682 @OrientationMode 1683 public int getOrientation() { 1684 return mOrientation; 1685 } 1686 1687 /** 1688 * Describes how the child views are positioned. Defaults to GRAVITY_TOP. If 1689 * this layout has a VERTICAL orientation, this controls where all the child 1690 * views are placed if there is extra vertical space. If this layout has a 1691 * HORIZONTAL orientation, this controls the alignment of the children. 1692 * 1693 * @param gravity See {@link android.view.Gravity} 1694 * 1695 * @attr ref android.R.styleable#LinearLayout_gravity 1696 */ 1697 public void setGravity(int gravity) { 1698 if (mGravity != gravity) { 1699 if ((gravity & GravityCompat.RELATIVE_HORIZONTAL_GRAVITY_MASK) == 0) { 1700 gravity |= GravityCompat.START; 1701 } 1702 1703 if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) { 1704 gravity |= Gravity.TOP; 1705 } 1706 1707 mGravity = gravity; 1708 requestLayout(); 1709 } 1710 } 1711 1712 public void setHorizontalGravity(int horizontalGravity) { 1713 final int gravity = horizontalGravity & GravityCompat.RELATIVE_HORIZONTAL_GRAVITY_MASK; 1714 if ((mGravity & GravityCompat.RELATIVE_HORIZONTAL_GRAVITY_MASK) != gravity) { 1715 mGravity = (mGravity & ~GravityCompat.RELATIVE_HORIZONTAL_GRAVITY_MASK) | gravity; 1716 requestLayout(); 1717 } 1718 } 1719 1720 public void setVerticalGravity(int verticalGravity) { 1721 final int gravity = verticalGravity & Gravity.VERTICAL_GRAVITY_MASK; 1722 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != gravity) { 1723 mGravity = (mGravity & ~Gravity.VERTICAL_GRAVITY_MASK) | gravity; 1724 requestLayout(); 1725 } 1726 } 1727 1728 @Override 1729 public LayoutParams generateLayoutParams(AttributeSet attrs) { 1730 return new LinearLayoutCompat.LayoutParams(getContext(), attrs); 1731 } 1732 1733 /** 1734 * Returns a set of layout parameters with a width of 1735 * {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT} 1736 * and a height of {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} 1737 * when the layout's orientation is {@link #VERTICAL}. When the orientation is 1738 * {@link #HORIZONTAL}, the width is set to {@link LayoutParams#WRAP_CONTENT} 1739 * and the height to {@link LayoutParams#WRAP_CONTENT}. 1740 */ 1741 @Override 1742 protected LayoutParams generateDefaultLayoutParams() { 1743 if (mOrientation == HORIZONTAL) { 1744 return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); 1745 } else if (mOrientation == VERTICAL) { 1746 return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); 1747 } 1748 return null; 1749 } 1750 1751 @Override 1752 protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { 1753 return new LayoutParams(p); 1754 } 1755 1756 1757 // Override to allow type-checking of LayoutParams. 1758 @Override 1759 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { 1760 return p instanceof LinearLayoutCompat.LayoutParams; 1761 } 1762 1763 public void onInitializeAccessibilityEvent(AccessibilityEvent event) { 1764 if (Build.VERSION.SDK_INT >= 14) { 1765 super.onInitializeAccessibilityEvent(event); 1766 event.setClassName(LinearLayoutCompat.class.getName()); 1767 } 1768 } 1769 1770 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { 1771 if (Build.VERSION.SDK_INT >= 14) { 1772 super.onInitializeAccessibilityNodeInfo(info); 1773 info.setClassName(LinearLayoutCompat.class.getName()); 1774 } 1775 } 1776 1777 /** 1778 * Per-child layout information associated with ViewLinearLayout. 1779 * 1780 * @attr ref android.R.styleable#LinearLayout_Layout_layout_weight 1781 * @attr ref android.R.styleable#LinearLayout_Layout_layout_gravity 1782 */ 1783 public static class LayoutParams extends ViewGroup.MarginLayoutParams { 1784 /** 1785 * Indicates how much of the extra space in the LinearLayout will be 1786 * allocated to the view associated with these LayoutParams. Specify 1787 * 0 if the view should not be stretched. Otherwise the extra pixels 1788 * will be pro-rated among all views whose weight is greater than 0. 1789 */ 1790 public float weight; 1791 1792 /** 1793 * Gravity for the view associated with these LayoutParams. 1794 * 1795 * @see android.view.Gravity 1796 */ 1797 public int gravity = -1; 1798 1799 /** 1800 * {@inheritDoc} 1801 */ 1802 public LayoutParams(Context c, AttributeSet attrs) { 1803 super(c, attrs); 1804 TypedArray a = 1805 c.obtainStyledAttributes(attrs, R.styleable.LinearLayoutCompat_Layout); 1806 1807 weight = a.getFloat(R.styleable.LinearLayoutCompat_Layout_android_layout_weight, 0); 1808 gravity = a.getInt(R.styleable.LinearLayoutCompat_Layout_android_layout_gravity, -1); 1809 1810 a.recycle(); 1811 } 1812 1813 /** 1814 * {@inheritDoc} 1815 */ 1816 public LayoutParams(int width, int height) { 1817 super(width, height); 1818 weight = 0; 1819 } 1820 1821 /** 1822 * Creates a new set of layout parameters with the specified width, height 1823 * and weight. 1824 * 1825 * @param width the width, either {@link #MATCH_PARENT}, 1826 * {@link #WRAP_CONTENT} or a fixed size in pixels 1827 * @param height the height, either {@link #MATCH_PARENT}, 1828 * {@link #WRAP_CONTENT} or a fixed size in pixels 1829 * @param weight the weight 1830 */ 1831 public LayoutParams(int width, int height, float weight) { 1832 super(width, height); 1833 this.weight = weight; 1834 } 1835 1836 /** 1837 * {@inheritDoc} 1838 */ 1839 public LayoutParams(ViewGroup.LayoutParams p) { 1840 super(p); 1841 } 1842 1843 /** 1844 * {@inheritDoc} 1845 */ 1846 public LayoutParams(ViewGroup.MarginLayoutParams source) { 1847 super(source); 1848 } 1849 1850 /** 1851 * Copy constructor. Clones the width, height, margin values, weight, 1852 * and gravity of the source. 1853 * 1854 * @param source The layout params to copy from. 1855 */ 1856 public LayoutParams(LayoutParams source) { 1857 super(source); 1858 1859 this.weight = source.weight; 1860 this.gravity = source.gravity; 1861 } 1862 } 1863}