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