1 /* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package androidx.appcompat.widget; 18 19 import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP; 20 21 import android.content.Context; 22 import android.content.res.ColorStateList; 23 import android.graphics.PorterDuff; 24 import android.graphics.drawable.Drawable; 25 import android.util.AttributeSet; 26 import android.view.inputmethod.EditorInfo; 27 import android.view.inputmethod.InputConnection; 28 import android.widget.TextView; 29 30 import androidx.annotation.DrawableRes; 31 import androidx.annotation.IntRange; 32 import androidx.annotation.NonNull; 33 import androidx.annotation.Nullable; 34 import androidx.annotation.Px; 35 import androidx.annotation.RestrictTo; 36 import androidx.appcompat.R; 37 import androidx.core.os.BuildCompat; 38 import androidx.core.view.TintableBackgroundView; 39 import androidx.core.widget.AutoSizeableTextView; 40 import androidx.core.widget.TextViewCompat; 41 42 /** 43 * A {@link TextView} which supports compatible features on older versions of the platform, 44 * including: 45 * <ul> 46 * <li>Allows dynamic tint of its background via the background tint methods in 47 * {@link androidx.core.view.ViewCompat}.</li> 48 * <li>Allows setting of the background tint using {@link R.attr#backgroundTint} and 49 * {@link R.attr#backgroundTintMode}.</li> 50 * <li>Supports auto-sizing via {@link androidx.core.widget.TextViewCompat} by allowing 51 * to instruct a {@link TextView} to let the size of the text expand or contract automatically 52 * to fill its layout based on the TextView's characteristics and boundaries. The 53 * style attributes associated with auto-sizing are {@link R.attr#autoSizeTextType}, 54 * {@link R.attr#autoSizeMinTextSize}, {@link R.attr#autoSizeMaxTextSize}, 55 * {@link R.attr#autoSizeStepGranularity} and {@link R.attr#autoSizePresetSizes}, all of 56 * which work back to 57 * {@link android.os.Build.VERSION_CODES#ICE_CREAM_SANDWICH Ice Cream Sandwich}.</li> 58 * </ul> 59 * 60 * <p>This will automatically be used when you use {@link TextView} in your layouts 61 * and the top-level activity / dialog is provided by 62 * <a href="{@docRoot}topic/libraries/support-library/packages.html#v7-appcompat">appcompat</a>. 63 * You should only need to manually use this class when writing custom views.</p> 64 */ 65 public class AppCompatTextView extends TextView implements TintableBackgroundView, 66 AutoSizeableTextView { 67 68 private final AppCompatBackgroundHelper mBackgroundTintHelper; 69 private final AppCompatTextHelper mTextHelper; 70 AppCompatTextView(Context context)71 public AppCompatTextView(Context context) { 72 this(context, null); 73 } 74 AppCompatTextView(Context context, AttributeSet attrs)75 public AppCompatTextView(Context context, AttributeSet attrs) { 76 this(context, attrs, android.R.attr.textViewStyle); 77 } 78 AppCompatTextView(Context context, AttributeSet attrs, int defStyleAttr)79 public AppCompatTextView(Context context, AttributeSet attrs, int defStyleAttr) { 80 super(TintContextWrapper.wrap(context), attrs, defStyleAttr); 81 82 mBackgroundTintHelper = new AppCompatBackgroundHelper(this); 83 mBackgroundTintHelper.loadFromAttributes(attrs, defStyleAttr); 84 85 mTextHelper = new AppCompatTextHelper(this); 86 mTextHelper.loadFromAttributes(attrs, defStyleAttr); 87 mTextHelper.applyCompoundDrawablesTints(); 88 } 89 90 @Override setBackgroundResource(@rawableRes int resId)91 public void setBackgroundResource(@DrawableRes int resId) { 92 super.setBackgroundResource(resId); 93 if (mBackgroundTintHelper != null) { 94 mBackgroundTintHelper.onSetBackgroundResource(resId); 95 } 96 } 97 98 @Override setBackgroundDrawable(Drawable background)99 public void setBackgroundDrawable(Drawable background) { 100 super.setBackgroundDrawable(background); 101 if (mBackgroundTintHelper != null) { 102 mBackgroundTintHelper.onSetBackgroundDrawable(background); 103 } 104 } 105 106 /** 107 * This should be accessed via 108 * {@link androidx.core.view.ViewCompat#setBackgroundTintList(android.view.View, ColorStateList)} 109 * 110 * @hide 111 */ 112 @RestrictTo(LIBRARY_GROUP) 113 @Override setSupportBackgroundTintList(@ullable ColorStateList tint)114 public void setSupportBackgroundTintList(@Nullable ColorStateList tint) { 115 if (mBackgroundTintHelper != null) { 116 mBackgroundTintHelper.setSupportBackgroundTintList(tint); 117 } 118 } 119 120 /** 121 * This should be accessed via 122 * {@link androidx.core.view.ViewCompat#getBackgroundTintList(android.view.View)} 123 * 124 * @hide 125 */ 126 @RestrictTo(LIBRARY_GROUP) 127 @Override 128 @Nullable getSupportBackgroundTintList()129 public ColorStateList getSupportBackgroundTintList() { 130 return mBackgroundTintHelper != null 131 ? mBackgroundTintHelper.getSupportBackgroundTintList() : null; 132 } 133 134 /** 135 * This should be accessed via 136 * {@link androidx.core.view.ViewCompat#setBackgroundTintMode(android.view.View, PorterDuff.Mode)} 137 * 138 * @hide 139 */ 140 @RestrictTo(LIBRARY_GROUP) 141 @Override setSupportBackgroundTintMode(@ullable PorterDuff.Mode tintMode)142 public void setSupportBackgroundTintMode(@Nullable PorterDuff.Mode tintMode) { 143 if (mBackgroundTintHelper != null) { 144 mBackgroundTintHelper.setSupportBackgroundTintMode(tintMode); 145 } 146 } 147 148 /** 149 * This should be accessed via 150 * {@link androidx.core.view.ViewCompat#getBackgroundTintMode(android.view.View)} 151 * 152 * @hide 153 */ 154 @RestrictTo(LIBRARY_GROUP) 155 @Override 156 @Nullable getSupportBackgroundTintMode()157 public PorterDuff.Mode getSupportBackgroundTintMode() { 158 return mBackgroundTintHelper != null 159 ? mBackgroundTintHelper.getSupportBackgroundTintMode() : null; 160 } 161 162 @Override setTextAppearance(Context context, int resId)163 public void setTextAppearance(Context context, int resId) { 164 super.setTextAppearance(context, resId); 165 if (mTextHelper != null) { 166 mTextHelper.onSetTextAppearance(context, resId); 167 } 168 } 169 170 @Override drawableStateChanged()171 protected void drawableStateChanged() { 172 super.drawableStateChanged(); 173 if (mBackgroundTintHelper != null) { 174 mBackgroundTintHelper.applySupportBackgroundTint(); 175 } 176 if (mTextHelper != null) { 177 mTextHelper.applyCompoundDrawablesTints(); 178 } 179 } 180 181 @Override onLayout(boolean changed, int left, int top, int right, int bottom)182 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 183 super.onLayout(changed, left, top, right, bottom); 184 if (mTextHelper != null) { 185 mTextHelper.onLayout(changed, left, top, right, bottom); 186 } 187 } 188 189 @Override setTextSize(int unit, float size)190 public void setTextSize(int unit, float size) { 191 if (PLATFORM_SUPPORTS_AUTOSIZE) { 192 super.setTextSize(unit, size); 193 } else { 194 if (mTextHelper != null) { 195 mTextHelper.setTextSize(unit, size); 196 } 197 } 198 } 199 200 @Override onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter)201 protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) { 202 super.onTextChanged(text, start, lengthBefore, lengthAfter); 203 if (mTextHelper != null && !PLATFORM_SUPPORTS_AUTOSIZE && mTextHelper.isAutoSizeEnabled()) { 204 mTextHelper.autoSizeText(); 205 } 206 } 207 208 /** 209 * This should be accessed via 210 * {@link androidx.core.widget.TextViewCompat#setAutoSizeTextTypeWithDefaults( 211 * TextView, int)} 212 * 213 * @hide 214 */ 215 @RestrictTo(LIBRARY_GROUP) 216 @Override setAutoSizeTextTypeWithDefaults( @extViewCompat.AutoSizeTextType int autoSizeTextType)217 public void setAutoSizeTextTypeWithDefaults( 218 @TextViewCompat.AutoSizeTextType int autoSizeTextType) { 219 if (PLATFORM_SUPPORTS_AUTOSIZE) { 220 super.setAutoSizeTextTypeWithDefaults(autoSizeTextType); 221 } else { 222 if (mTextHelper != null) { 223 mTextHelper.setAutoSizeTextTypeWithDefaults(autoSizeTextType); 224 } 225 } 226 } 227 228 /** 229 * This should be accessed via 230 * {@link androidx.core.widget.TextViewCompat#setAutoSizeTextTypeUniformWithConfiguration( 231 * TextView, int, int, int, int)} 232 * 233 * @hide 234 */ 235 @RestrictTo(LIBRARY_GROUP) 236 @Override setAutoSizeTextTypeUniformWithConfiguration( int autoSizeMinTextSize, int autoSizeMaxTextSize, int autoSizeStepGranularity, int unit)237 public void setAutoSizeTextTypeUniformWithConfiguration( 238 int autoSizeMinTextSize, 239 int autoSizeMaxTextSize, 240 int autoSizeStepGranularity, 241 int unit) throws IllegalArgumentException { 242 if (PLATFORM_SUPPORTS_AUTOSIZE) { 243 super.setAutoSizeTextTypeUniformWithConfiguration( 244 autoSizeMinTextSize, autoSizeMaxTextSize, autoSizeStepGranularity, unit); 245 } else { 246 if (mTextHelper != null) { 247 mTextHelper.setAutoSizeTextTypeUniformWithConfiguration( 248 autoSizeMinTextSize, autoSizeMaxTextSize, autoSizeStepGranularity, unit); 249 } 250 } 251 } 252 253 /** 254 * This should be accessed via 255 * {@link androidx.core.widget.TextViewCompat#setAutoSizeTextTypeUniformWithPresetSizes( 256 * TextView, int[], int)} 257 * 258 * @hide 259 */ 260 @RestrictTo(LIBRARY_GROUP) 261 @Override setAutoSizeTextTypeUniformWithPresetSizes(@onNull int[] presetSizes, int unit)262 public void setAutoSizeTextTypeUniformWithPresetSizes(@NonNull int[] presetSizes, int unit) 263 throws IllegalArgumentException { 264 if (PLATFORM_SUPPORTS_AUTOSIZE) { 265 super.setAutoSizeTextTypeUniformWithPresetSizes(presetSizes, unit); 266 } else { 267 if (mTextHelper != null) { 268 mTextHelper.setAutoSizeTextTypeUniformWithPresetSizes(presetSizes, unit); 269 } 270 } 271 } 272 273 /** 274 * This should be accessed via 275 * {@link androidx.core.widget.TextViewCompat#getAutoSizeTextType(TextView)} 276 * 277 * @hide 278 */ 279 @RestrictTo(LIBRARY_GROUP) 280 @Override 281 @TextViewCompat.AutoSizeTextType getAutoSizeTextType()282 public int getAutoSizeTextType() { 283 if (PLATFORM_SUPPORTS_AUTOSIZE) { 284 return super.getAutoSizeTextType() == TextView.AUTO_SIZE_TEXT_TYPE_UNIFORM 285 ? TextViewCompat.AUTO_SIZE_TEXT_TYPE_UNIFORM 286 : TextViewCompat.AUTO_SIZE_TEXT_TYPE_NONE; 287 } else { 288 if (mTextHelper != null) { 289 return mTextHelper.getAutoSizeTextType(); 290 } 291 } 292 return TextViewCompat.AUTO_SIZE_TEXT_TYPE_NONE; 293 } 294 295 /** 296 * This should be accessed via 297 * {@link androidx.core.widget.TextViewCompat#getAutoSizeStepGranularity(TextView)} 298 * 299 * @hide 300 */ 301 @RestrictTo(LIBRARY_GROUP) 302 @Override getAutoSizeStepGranularity()303 public int getAutoSizeStepGranularity() { 304 if (PLATFORM_SUPPORTS_AUTOSIZE) { 305 return super.getAutoSizeStepGranularity(); 306 } else { 307 if (mTextHelper != null) { 308 return mTextHelper.getAutoSizeStepGranularity(); 309 } 310 } 311 return -1; 312 } 313 314 /** 315 * This should be accessed via 316 * {@link androidx.core.widget.TextViewCompat#getAutoSizeMinTextSize(TextView)} 317 * 318 * @hide 319 */ 320 @RestrictTo(LIBRARY_GROUP) 321 @Override getAutoSizeMinTextSize()322 public int getAutoSizeMinTextSize() { 323 if (PLATFORM_SUPPORTS_AUTOSIZE) { 324 return super.getAutoSizeMinTextSize(); 325 } else { 326 if (mTextHelper != null) { 327 return mTextHelper.getAutoSizeMinTextSize(); 328 } 329 } 330 return -1; 331 } 332 333 /** 334 * This should be accessed via 335 * {@link androidx.core.widget.TextViewCompat#getAutoSizeMaxTextSize(TextView)} 336 * 337 * @hide 338 */ 339 @RestrictTo(LIBRARY_GROUP) 340 @Override getAutoSizeMaxTextSize()341 public int getAutoSizeMaxTextSize() { 342 if (PLATFORM_SUPPORTS_AUTOSIZE) { 343 return super.getAutoSizeMaxTextSize(); 344 } else { 345 if (mTextHelper != null) { 346 return mTextHelper.getAutoSizeMaxTextSize(); 347 } 348 } 349 return -1; 350 } 351 352 /** 353 * This should be accessed via 354 * {@link androidx.core.widget.TextViewCompat#getAutoSizeTextAvailableSizes(TextView)} 355 * 356 * @hide 357 */ 358 @RestrictTo(LIBRARY_GROUP) 359 @Override getAutoSizeTextAvailableSizes()360 public int[] getAutoSizeTextAvailableSizes() { 361 if (PLATFORM_SUPPORTS_AUTOSIZE) { 362 return super.getAutoSizeTextAvailableSizes(); 363 } else { 364 if (mTextHelper != null) { 365 return mTextHelper.getAutoSizeTextAvailableSizes(); 366 } 367 } 368 return new int[0]; 369 } 370 371 @Override onCreateInputConnection(EditorInfo outAttrs)372 public InputConnection onCreateInputConnection(EditorInfo outAttrs) { 373 return AppCompatHintHelper.onCreateInputConnection(super.onCreateInputConnection(outAttrs), 374 outAttrs, this); 375 } 376 377 @Override setFirstBaselineToTopHeight(@x @ntRangefrom = 0) int firstBaselineToTopHeight)378 public void setFirstBaselineToTopHeight(@Px @IntRange(from = 0) int firstBaselineToTopHeight) { 379 if (BuildCompat.isAtLeastP()) { 380 super.setFirstBaselineToTopHeight(firstBaselineToTopHeight); 381 } else { 382 TextViewCompat.setFirstBaselineToTopHeight(this, firstBaselineToTopHeight); 383 } 384 } 385 386 @Override setLastBaselineToBottomHeight( @x @ntRangefrom = 0) int lastBaselineToBottomHeight)387 public void setLastBaselineToBottomHeight( 388 @Px @IntRange(from = 0) int lastBaselineToBottomHeight) { 389 if (BuildCompat.isAtLeastP()) { 390 super.setLastBaselineToBottomHeight(lastBaselineToBottomHeight); 391 } else { 392 TextViewCompat.setLastBaselineToBottomHeight(this, 393 lastBaselineToBottomHeight); 394 } 395 } 396 397 @Override getFirstBaselineToTopHeight()398 public int getFirstBaselineToTopHeight() { 399 return TextViewCompat.getFirstBaselineToTopHeight(this); 400 } 401 402 @Override getLastBaselineToBottomHeight()403 public int getLastBaselineToBottomHeight() { 404 return TextViewCompat.getLastBaselineToBottomHeight(this); 405 } 406 407 @Override setLineHeight(@x @ntRangefrom = 0) int lineHeight)408 public void setLineHeight(@Px @IntRange(from = 0) int lineHeight) { 409 TextViewCompat.setLineHeight(this, lineHeight); 410 } 411 } 412