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