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