• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5  * in compliance with the License. You may obtain a copy of the License at
6  *
7  * http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software distributed under the License
10  * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11  * or implied. See the License for the specific language governing permissions and limitations under
12  * the License.
13  */
14 package android.support.v17.leanback.widget;
15 
16 import android.content.Context;
17 import android.content.res.TypedArray;
18 import android.text.Layout;
19 import android.util.AttributeSet;
20 import android.util.TypedValue;
21 import android.widget.TextView;
22 
23 import android.support.v17.leanback.R;
24 
25 /**
26  * <p>A {@link android.widget.TextView} that adjusts text size automatically in response
27  * to certain trigger conditions, such as text that wraps over multiple lines.</p>
28  * @hide
29  */
30 class ResizingTextView extends TextView {
31 
32     /**
33      * Trigger text resize when text flows into the last line of a multi-line text view.
34      */
35     public static final int TRIGGER_MAX_LINES = 0x01;
36 
37     private int mTriggerConditions; // Union of trigger conditions
38     private int mResizedTextSize;
39     // Note: Maintaining line spacing turned out not to be useful, and will be removed in
40     // the next round of design for this class (b/18736630). For now it simply defaults to false.
41     private boolean mMaintainLineSpacing;
42     private int mResizedPaddingAdjustmentTop;
43     private int mResizedPaddingAdjustmentBottom;
44 
45     private boolean mIsResized = false;
46     // Remember default properties in case we need to restore them
47     private boolean mDefaultsInitialized = false;
48     private int mDefaultTextSize;
49     private float mDefaultLineSpacingExtra;
50     private int mDefaultPaddingTop;
51     private int mDefaultPaddingBottom;
52 
ResizingTextView(Context ctx, AttributeSet attrs, int defStyleAttr, int defStyleRes)53     public ResizingTextView(Context ctx, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
54         super(ctx, attrs, defStyleAttr);
55         TypedArray a = ctx.obtainStyledAttributes(attrs, R.styleable.lbResizingTextView,
56                 defStyleAttr, defStyleRes);
57 
58         try {
59             mTriggerConditions = a.getInt(
60                     R.styleable.lbResizingTextView_resizeTrigger, TRIGGER_MAX_LINES);
61             mResizedTextSize = a.getDimensionPixelSize(
62                     R.styleable.lbResizingTextView_resizedTextSize, -1);
63             mMaintainLineSpacing = a.getBoolean(
64                     R.styleable.lbResizingTextView_maintainLineSpacing, false);
65             mResizedPaddingAdjustmentTop = a.getDimensionPixelOffset(
66                     R.styleable.lbResizingTextView_resizedPaddingAdjustmentTop, 0);
67             mResizedPaddingAdjustmentBottom = a.getDimensionPixelOffset(
68                     R.styleable.lbResizingTextView_resizedPaddingAdjustmentBottom, 0);
69         } finally {
70             a.recycle();
71         }
72     }
73 
ResizingTextView(Context ctx, AttributeSet attrs, int defStyleAttr)74     public ResizingTextView(Context ctx, AttributeSet attrs, int defStyleAttr) {
75         this(ctx, attrs, defStyleAttr, 0);
76     }
77 
ResizingTextView(Context ctx, AttributeSet attrs)78     public ResizingTextView(Context ctx, AttributeSet attrs) {
79         // TODO We should define our own style that inherits from TextViewStyle, to set defaults
80         // for new styleables,  We then pass the appropriate R.attr up the constructor chain here.
81         this(ctx, attrs, android.R.attr.textViewStyle);
82     }
83 
ResizingTextView(Context ctx)84     public ResizingTextView(Context ctx) {
85         this(ctx, null);
86     }
87 
88     /**
89      * @return the trigger conditions used to determine whether resize occurs
90      */
getTriggerConditions()91     public int getTriggerConditions() {
92         return mTriggerConditions;
93     }
94 
95     /**
96      * Set the trigger conditions used to determine whether resize occurs. Pass
97      * a union of trigger condition constants, such as {@link ResizingTextView#TRIGGER_MAX_LINES}.
98      *
99      * @param conditions A union of trigger condition constants
100      */
setTriggerConditions(int conditions)101     public void setTriggerConditions(int conditions) {
102         if (mTriggerConditions != conditions) {
103             mTriggerConditions = conditions;
104             // Always request a layout when trigger conditions change
105             requestLayout();
106         }
107     }
108 
109     /**
110      * @return the resized text size
111      */
getResizedTextSize()112     public int getResizedTextSize() {
113         return mResizedTextSize;
114     }
115 
116     /**
117      * Set the text size for resized text.
118      *
119      * @param size The text size for resized text
120      */
setResizedTextSize(int size)121     public void setResizedTextSize(int size) {
122         if (mResizedTextSize != size) {
123             mResizedTextSize = size;
124             resizeParamsChanged();
125         }
126     }
127 
128     /**
129      * @return whether or not to maintain line spacing when resizing text.
130      * The default is true.
131      */
getMaintainLineSpacing()132     public boolean getMaintainLineSpacing() {
133         return mMaintainLineSpacing;
134     }
135 
136     /**
137      * Set whether or not to maintain line spacing when resizing text.
138      * The default is true.
139      *
140      * @param maintain Whether or not to maintain line spacing
141      */
setMaintainLineSpacing(boolean maintain)142     public void setMaintainLineSpacing(boolean maintain) {
143         if (mMaintainLineSpacing != maintain) {
144             mMaintainLineSpacing = maintain;
145             resizeParamsChanged();
146         }
147     }
148 
149     /**
150      * @return desired adjustment to top padding for resized text
151      */
getResizedPaddingAdjustmentTop()152     public int getResizedPaddingAdjustmentTop() {
153         return mResizedPaddingAdjustmentTop;
154     }
155 
156     /**
157      * Set the desired adjustment to top padding for resized text.
158      *
159      * @param adjustment The adjustment to top padding, in pixels
160      */
setResizedPaddingAdjustmentTop(int adjustment)161     public void setResizedPaddingAdjustmentTop(int adjustment) {
162         if (mResizedPaddingAdjustmentTop != adjustment) {
163             mResizedPaddingAdjustmentTop = adjustment;
164             resizeParamsChanged();
165         }
166     }
167 
168     /**
169      * @return desired adjustment to bottom padding for resized text
170      */
getResizedPaddingAdjustmentBottom()171     public int getResizedPaddingAdjustmentBottom() {
172         return mResizedPaddingAdjustmentBottom;
173     }
174 
175     /**
176      * Set the desired adjustment to bottom padding for resized text.
177      *
178      * @param adjustment The adjustment to bottom padding, in pixels
179      */
setResizedPaddingAdjustmentBottom(int adjustment)180     public void setResizedPaddingAdjustmentBottom(int adjustment) {
181         if (mResizedPaddingAdjustmentBottom != adjustment) {
182             mResizedPaddingAdjustmentBottom = adjustment;
183             resizeParamsChanged();
184         }
185     }
186 
resizeParamsChanged()187     private void resizeParamsChanged() {
188         // If we're not resized, then changing resize parameters doesn't
189         // affect layout, so don't bother requesting.
190         if (mIsResized) {
191             requestLayout();
192         }
193     }
194 
195     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)196     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
197         if (!mDefaultsInitialized) {
198             mDefaultTextSize = (int) getTextSize();
199             mDefaultLineSpacingExtra = getLineSpacingExtra();
200             mDefaultPaddingTop = getPaddingTop();
201             mDefaultPaddingBottom = getPaddingBottom();
202             mDefaultsInitialized = true;
203         }
204 
205         // Always try first to measure with defaults. Otherwise, we may think we can get away
206         // with larger text sizes later when we actually can't.
207         setTextSize(TypedValue.COMPLEX_UNIT_PX, mDefaultTextSize);
208         setLineSpacing(mDefaultLineSpacingExtra, getLineSpacingMultiplier());
209         setPaddingTopAndBottom(mDefaultPaddingTop, mDefaultPaddingBottom);
210 
211         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
212 
213         boolean resizeText = false;
214 
215         final Layout layout = getLayout();
216         if (layout != null) {
217             if ((mTriggerConditions & TRIGGER_MAX_LINES) > 0) {
218                 final int lineCount = layout.getLineCount();
219                 final int maxLines = getMaxLines();
220                 if (maxLines > 1) {
221                     resizeText = lineCount == maxLines;
222                 }
223             }
224         }
225 
226         final int currentSizePx = (int) getTextSize();
227         boolean remeasure = false;
228         if (resizeText) {
229             if (mResizedTextSize != -1 && currentSizePx != mResizedTextSize) {
230                 setTextSize(TypedValue.COMPLEX_UNIT_PX, mResizedTextSize);
231                 remeasure = true;
232             }
233             // Check for other desired adjustments in addition to the text size
234             final float targetLineSpacingExtra = mDefaultLineSpacingExtra +
235                     mDefaultTextSize - mResizedTextSize;
236             if (mMaintainLineSpacing && getLineSpacingExtra() != targetLineSpacingExtra) {
237                 setLineSpacing(targetLineSpacingExtra, getLineSpacingMultiplier());
238                 remeasure = true;
239             }
240             final int paddingTop = mDefaultPaddingTop + mResizedPaddingAdjustmentTop;
241             final int paddingBottom = mDefaultPaddingBottom + mResizedPaddingAdjustmentBottom;
242             if (getPaddingTop() != paddingTop || getPaddingBottom() != paddingBottom) {
243                 setPaddingTopAndBottom(paddingTop, paddingBottom);
244                 remeasure = true;
245             }
246         } else {
247             // Use default size, line spacing, and padding
248             if (mResizedTextSize != -1 && currentSizePx != mDefaultTextSize) {
249                 setTextSize(TypedValue.COMPLEX_UNIT_PX, mDefaultTextSize);
250                 remeasure = true;
251             }
252             if (mMaintainLineSpacing && getLineSpacingExtra() != mDefaultLineSpacingExtra) {
253                 setLineSpacing(mDefaultLineSpacingExtra, getLineSpacingMultiplier());
254                 remeasure = true;
255             }
256             if (getPaddingTop() != mDefaultPaddingTop ||
257                     getPaddingBottom() != mDefaultPaddingBottom) {
258                 setPaddingTopAndBottom(mDefaultPaddingTop, mDefaultPaddingBottom);
259                 remeasure = true;
260             }
261         }
262         mIsResized = resizeText;
263         if (remeasure) {
264             super.onMeasure(widthMeasureSpec, heightMeasureSpec);
265         }
266     }
267 
setPaddingTopAndBottom(int paddingTop, int paddingBottom)268     private void setPaddingTopAndBottom(int paddingTop, int paddingBottom) {
269         if (isPaddingRelative()) {
270             setPaddingRelative(getPaddingStart(), paddingTop, getPaddingEnd(), paddingBottom);
271         } else {
272             setPadding(getPaddingLeft(), paddingTop, getPaddingRight(), paddingBottom);
273         }
274     }
275 }
276