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