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