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