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