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