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