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