Home | History | Annotate | Download | only in widget
      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")
     37     public EllipsizeLayout(Context context) {
     38         this(context, null);
     39     }
     40 
     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
     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