1 package com.android.deskclock.widget;
2 
3 import android.content.Context;
4 import android.util.AttributeSet;
5 import android.view.View;
6 import android.widget.LinearLayout;
7 import android.widget.TextView;
8 
9 /**
10  * When this layout is in the Horizontal orientation and one and only one child is a TextView with a
11  * non-null android:ellipsize, this layout will reduce android:maxWidth of that TextView to ensure
12  * the siblings are not truncated. This class is useful when that ellipsize-text-view "starts"
13  * before other children of this view group. This layout has no effect if:
14  * <ul>
15  *     <li>the orientation is not horizontal</li>
16  *     <li>any child has weights.</li>
17  *     <li>more than one child has a non-null android:ellipsize.</li>
18  * </ul>
19  *
20  * <p>The purpose of this horizontal-linear-layout is to ensure that when the sum of widths of the
21  * children are greater than this parent, the maximum width of the ellipsize-text-view, is reduced
22  * so that no siblings are truncated.</p>
23  *
24  * <p>For example: Given Text1 has android:ellipsize="end" and Text2 has android:ellipsize="none",
25  * as Text1 and/or Text2 grow in width, both will consume more width until Text2 hits the end
26  * margin, then Text1 will cease to grow and instead shrink to accommodate any further growth in
27  * Text2.</p>
28  * <ul>
29  * <li>|[text1]|[text2]              |</li>
30  * <li>|[text1 text1]|[text2 text2]  |</li>
31  * <li>|[text...]|[text2 text2 text2]|</li>
32  * </ul>
33  */
34 public class EllipsizeLayout extends LinearLayout {
35 
36     @SuppressWarnings("unused")
EllipsizeLayout(Context context)37     public EllipsizeLayout(Context context) {
38         this(context, null);
39     }
40 
EllipsizeLayout(Context context, AttributeSet attrs)41     public EllipsizeLayout(Context context, AttributeSet attrs) {
42         super(context, attrs);
43     }
44 
45     /**
46      * This override only acts when the LinearLayout is in the Horizontal orientation and is in it's
47      * final measurement pass(MeasureSpec.EXACTLY). In this case only, this class
48      * <ul>
49      *     <li>Identifies the one TextView child with the non-null android:ellipsize.</li>
50      *     <li>Re-measures the needed width of all children (by calling measureChildWithMargins with
51      *     the width measure specification to MeasureSpec.UNSPECIFIED.)</li>
52      *     <li>Sums the children's widths.</li>
53      *     <li>Whenever the sum of the children's widths is greater than this parent was allocated,
54      *     the maximum width of the one TextView child with the non-null android:ellipsize is
55      *     reduced.</li>
56      * </ul>
57      *
58      * @param widthMeasureSpec horizontal space requirements as imposed by the parent
59      * @param heightMeasureSpec vertical space requirements as imposed by the parent
60      */
61     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)62     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
63         if (getOrientation() == HORIZONTAL
64                 && (MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY)) {
65             int totalLength = 0;
66             // If any of the constraints of this class are exceeded, outOfSpec becomes true
67             // and the no alterations are made to the ellipsize-text-view.
68             boolean outOfSpec = false;
69             TextView ellipsizeView = null;
70             final int count = getChildCount();
71             final int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
72             final int queryWidthMeasureSpec = MeasureSpec.
73                     makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.UNSPECIFIED);
74 
75             for (int ii = 0; ii < count && !outOfSpec; ++ii) {
76                 final View child = getChildAt(ii);
77                 if (child != null && child.getVisibility() != GONE) {
78                     // Identify the ellipsize view
79                     if (child instanceof TextView) {
80                         final TextView tv = (TextView) child;
81                         if (tv.getEllipsize() != null) {
82                             if (ellipsizeView == null) {
83                                 ellipsizeView = tv;
84                                 // Clear the maximum width on ellipsizeView before measurement
85                                 ellipsizeView.setMaxWidth(Integer.MAX_VALUE);
86                             } else {
87                                 // TODO: support multiple android:ellipsize
88                                 outOfSpec = true;
89                             }
90                         }
91                     }
92                     // Ask the child to measure itself
93                     measureChildWithMargins(child, queryWidthMeasureSpec, 0, heightMeasureSpec, 0);
94 
95                     // Get the layout parameters to check for a weighted width and to add the
96                     // child's margins to the total length.
97                     final LinearLayout.LayoutParams layoutParams =
98                             (LinearLayout.LayoutParams) child.getLayoutParams();
99                     if (layoutParams != null) {
100                         outOfSpec |= (layoutParams.weight > 0f);
101                         totalLength += child.getMeasuredWidth()
102                                 + layoutParams.leftMargin + layoutParams.rightMargin;
103                     } else {
104                         outOfSpec = true;
105                     }
106                 }
107             }
108             // Last constraint test
109             outOfSpec |= (ellipsizeView == null) || (totalLength == 0);
110 
111             if (!outOfSpec && totalLength > parentWidth) {
112                 int maxWidth = ellipsizeView.getMeasuredWidth() - (totalLength - parentWidth);
113                 // TODO: Respect android:minWidth (easy with @TargetApi(16))
114                 final int minWidth = 0;
115                 if (maxWidth < minWidth) {
116                     maxWidth = minWidth;
117                 }
118                 ellipsizeView.setMaxWidth(maxWidth);
119             }
120         }
121 
122         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
123     }
124 }
125