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