ResizingTextView.java revision 7fa663ffa812ebc8cb2bb492eecc40485b145eba
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 */ 14package android.support.v17.leanback.widget; 15 16import android.content.Context; 17import android.content.res.TypedArray; 18import android.text.Layout; 19import android.util.AttributeSet; 20import android.util.TypedValue; 21import android.widget.TextView; 22 23import 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 */ 30class 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 private boolean mMaintainLineSpacing; 40 private int mResizedPaddingAdjustmentTop; 41 private int mResizedPaddingAdjustmentBottom; 42 43 private boolean mIsResized = false; 44 // Remember default properties in case we need to restore them 45 private boolean mDefaultsInitialized = false; 46 private int mDefaultTextSize; 47 private float mDefaultLineSpacingExtra; 48 private int mDefaultPaddingTop; 49 private int mDefaultPaddingBottom; 50 51 public ResizingTextView(Context ctx, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 52 super(ctx, attrs, defStyleAttr); 53 TypedArray a = ctx.obtainStyledAttributes(attrs, R.styleable.lbResizingTextView, 54 defStyleAttr, defStyleRes); 55 56 try { 57 mTriggerConditions = a.getInt( 58 R.styleable.lbResizingTextView_resizeTrigger, TRIGGER_MAX_LINES); 59 mResizedTextSize = a.getDimensionPixelSize( 60 R.styleable.lbResizingTextView_resizedTextSize, -1); 61 mMaintainLineSpacing = a.getBoolean( 62 R.styleable.lbResizingTextView_maintainLineSpacing, true); 63 mResizedPaddingAdjustmentTop = a.getDimensionPixelOffset( 64 R.styleable.lbResizingTextView_resizedPaddingAdjustmentTop, 0); 65 mResizedPaddingAdjustmentBottom = a.getDimensionPixelOffset( 66 R.styleable.lbResizingTextView_resizedPaddingAdjustmentBottom, 0); 67 } finally { 68 a.recycle(); 69 } 70 } 71 72 public ResizingTextView(Context ctx, AttributeSet attrs, int defStyleAttr) { 73 this(ctx, attrs, defStyleAttr, 0); 74 } 75 76 public ResizingTextView(Context ctx, AttributeSet attrs) { 77 this(ctx, attrs, 0); 78 } 79 80 public ResizingTextView(Context ctx) { 81 this(ctx, null); 82 } 83 84 /** 85 * @return the trigger conditions used to determine whether resize occurs 86 */ 87 public int getTriggerConditions() { 88 return mTriggerConditions; 89 } 90 91 /** 92 * Set the trigger conditions used to determine whether resize occurs. Pass 93 * a union of trigger condition constants, such as {@link ResizingTextView#TRIGGER_MAX_LINES}. 94 * 95 * @param conditions A union of trigger condition constants 96 */ 97 public void setTriggerConditions(int conditions) { 98 if (mTriggerConditions != conditions) { 99 mTriggerConditions = conditions; 100 // Always request a layout when trigger conditions change 101 requestLayout(); 102 } 103 } 104 105 /** 106 * @return the resized text size 107 */ 108 public int getResizedTextSize() { 109 return mResizedTextSize; 110 } 111 112 /** 113 * Set the text size for resized text. 114 * 115 * @param size The text size for resized text 116 */ 117 public void setResizedTextSize(int size) { 118 if (mResizedTextSize != size) { 119 mResizedTextSize = size; 120 resizeParamsChanged(); 121 } 122 } 123 124 /** 125 * @return whether or not to maintain line spacing when resizing text. 126 * The default is true. 127 */ 128 public boolean getMaintainLineSpacing() { 129 return mMaintainLineSpacing; 130 } 131 132 /** 133 * Set whether or not to maintain line spacing when resizing text. 134 * The default is true. 135 * 136 * @param maintain Whether or not to maintain line spacing 137 */ 138 public void setMaintainLineSpacing(boolean maintain) { 139 if (mMaintainLineSpacing != maintain) { 140 mMaintainLineSpacing = maintain; 141 resizeParamsChanged(); 142 } 143 } 144 145 /** 146 * @return desired adjustment to top padding for resized text 147 */ 148 public int getResizedPaddingAdjustmentTop() { 149 return mResizedPaddingAdjustmentTop; 150 } 151 152 /** 153 * Set the desired adjustment to top padding for resized text. 154 * 155 * @param adjustment The adjustment to top padding, in pixels 156 */ 157 public void setResizedPaddingAdjustmentTop(int adjustment) { 158 if (mResizedPaddingAdjustmentTop != adjustment) { 159 mResizedPaddingAdjustmentTop = adjustment; 160 resizeParamsChanged(); 161 } 162 } 163 164 /** 165 * @return desired adjustment to bottom padding for resized text 166 */ 167 public int getResizedPaddingAdjustmentBottom() { 168 return mResizedPaddingAdjustmentBottom; 169 } 170 171 /** 172 * Set the desired adjustment to bottom padding for resized text. 173 * 174 * @param adjustment The adjustment to bottom padding, in pixels 175 */ 176 public void setResizedPaddingAdjustmentBottom(int adjustment) { 177 if (mResizedPaddingAdjustmentBottom != adjustment) { 178 mResizedPaddingAdjustmentBottom = adjustment; 179 resizeParamsChanged(); 180 } 181 } 182 183 private void resizeParamsChanged() { 184 // If we're not resized, then changing resize parameters doesn't 185 // affect layout, so don't bother requesting. 186 if (mIsResized) { 187 requestLayout(); 188 } 189 } 190 191 @Override 192 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 193 if (!mDefaultsInitialized) { 194 mDefaultTextSize = (int) getTextSize(); 195 mDefaultLineSpacingExtra = getLineSpacingExtra(); 196 mDefaultPaddingTop = getPaddingTop(); 197 mDefaultPaddingBottom = getPaddingBottom(); 198 mDefaultsInitialized = true; 199 } 200 201 // Always try first to measure with defaults. Otherwise, we may think we can get away 202 // with larger text sizes later when we actually can't. 203 setTextSize(TypedValue.COMPLEX_UNIT_PX, mDefaultTextSize); 204 setLineSpacing(mDefaultLineSpacingExtra, getLineSpacingMultiplier()); 205 setPaddingTopAndBottom(mDefaultPaddingTop, mDefaultPaddingBottom); 206 207 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 208 209 boolean resizeText = false; 210 211 final Layout layout = getLayout(); 212 if (layout != null) { 213 if ((mTriggerConditions & TRIGGER_MAX_LINES) > 0) { 214 final int lineCount = layout.getLineCount(); 215 final int maxLines = getMaxLines(); 216 if (maxLines > 1) { 217 resizeText = lineCount == maxLines; 218 } 219 } 220 } 221 222 final int currentSizePx = (int) getTextSize(); 223 boolean remeasure = false; 224 if (resizeText) { 225 if (mResizedTextSize != -1 && currentSizePx != mResizedTextSize) { 226 setTextSize(TypedValue.COMPLEX_UNIT_PX, mResizedTextSize); 227 remeasure = true; 228 } 229 // Check for other desired adjustments in addition to the text size 230 final float targetLineSpacingExtra = mDefaultLineSpacingExtra + 231 mDefaultTextSize - mResizedTextSize; 232 if (mMaintainLineSpacing && getLineSpacingExtra() != targetLineSpacingExtra) { 233 setLineSpacing(targetLineSpacingExtra, getLineSpacingMultiplier()); 234 remeasure = true; 235 } 236 final int paddingTop = mDefaultPaddingTop + mResizedPaddingAdjustmentTop; 237 final int paddingBottom = mDefaultPaddingBottom + mResizedPaddingAdjustmentBottom; 238 if (getPaddingTop() != paddingTop || getPaddingBottom() != paddingBottom) { 239 setPaddingTopAndBottom(paddingTop, paddingBottom); 240 remeasure = true; 241 } 242 } else { 243 // Use default size, line spacing, and padding 244 if (mResizedTextSize != -1 && currentSizePx != mDefaultTextSize) { 245 setTextSize(TypedValue.COMPLEX_UNIT_PX, mDefaultTextSize); 246 remeasure = true; 247 } 248 if (mMaintainLineSpacing && getLineSpacingExtra() != mDefaultLineSpacingExtra) { 249 setLineSpacing(mDefaultLineSpacingExtra, getLineSpacingMultiplier()); 250 remeasure = true; 251 } 252 if (getPaddingTop() != mDefaultPaddingTop || 253 getPaddingBottom() != mDefaultPaddingBottom) { 254 setPaddingTopAndBottom(mDefaultPaddingTop, mDefaultPaddingBottom); 255 remeasure = true; 256 } 257 } 258 mIsResized = resizeText; 259 if (remeasure) { 260 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 261 } 262 } 263 264 private void setPaddingTopAndBottom(int paddingTop, int paddingBottom) { 265 if (isPaddingRelative()) { 266 setPaddingRelative(getPaddingStart(), paddingTop, getPaddingEnd(), paddingBottom); 267 } else { 268 setPadding(getPaddingLeft(), paddingTop, getPaddingRight(), paddingBottom); 269 } 270 } 271} 272