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 17package android.support.v4.widget; 18 19import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP; 20 21import android.graphics.drawable.Drawable; 22import android.os.Build; 23import android.support.annotation.DrawableRes; 24import android.support.annotation.IntDef; 25import android.support.annotation.NonNull; 26import android.support.annotation.Nullable; 27import android.support.annotation.RequiresApi; 28import android.support.annotation.RestrictTo; 29import android.support.annotation.StyleRes; 30import android.support.v4.os.BuildCompat; 31import android.util.Log; 32import android.util.TypedValue; 33import android.view.View; 34import android.widget.TextView; 35 36import java.lang.annotation.Retention; 37import java.lang.annotation.RetentionPolicy; 38import java.lang.reflect.Field; 39 40/** 41 * Helper for accessing features in {@link TextView}. 42 */ 43public final class TextViewCompat { 44 45 /** 46 * The TextView does not auto-size text (default). 47 */ 48 public static final int AUTO_SIZE_TEXT_TYPE_NONE = TextView.AUTO_SIZE_TEXT_TYPE_NONE; 49 50 /** 51 * The TextView scales text size both horizontally and vertically to fit within the 52 * container. 53 */ 54 public static final int AUTO_SIZE_TEXT_TYPE_UNIFORM = TextView.AUTO_SIZE_TEXT_TYPE_UNIFORM; 55 56 /** @hide */ 57 @RestrictTo(LIBRARY_GROUP) 58 @IntDef({AUTO_SIZE_TEXT_TYPE_NONE, AUTO_SIZE_TEXT_TYPE_UNIFORM}) 59 @Retention(RetentionPolicy.SOURCE) 60 public @interface AutoSizeTextType {} 61 62 // Hide constructor 63 private TextViewCompat() {} 64 65 static class TextViewCompatBaseImpl { 66 private static final String LOG_TAG = "TextViewCompatBase"; 67 private static final int LINES = 1; 68 69 private static Field sMaximumField; 70 private static boolean sMaximumFieldFetched; 71 private static Field sMaxModeField; 72 private static boolean sMaxModeFieldFetched; 73 74 private static Field sMinimumField; 75 private static boolean sMinimumFieldFetched; 76 private static Field sMinModeField; 77 private static boolean sMinModeFieldFetched; 78 79 public void setCompoundDrawablesRelative(@NonNull TextView textView, 80 @Nullable Drawable start, @Nullable Drawable top, @Nullable Drawable end, 81 @Nullable Drawable bottom) { 82 textView.setCompoundDrawables(start, top, end, bottom); 83 } 84 85 public void setCompoundDrawablesRelativeWithIntrinsicBounds(@NonNull TextView textView, 86 @Nullable Drawable start, @Nullable Drawable top, @Nullable Drawable end, 87 @Nullable Drawable bottom) { 88 textView.setCompoundDrawablesWithIntrinsicBounds(start, top, end, bottom); 89 } 90 91 public void setCompoundDrawablesRelativeWithIntrinsicBounds(@NonNull TextView textView, 92 @DrawableRes int start, @DrawableRes int top, @DrawableRes int end, 93 @DrawableRes int bottom) { 94 textView.setCompoundDrawablesWithIntrinsicBounds(start, top, end, bottom); 95 } 96 97 private static Field retrieveField(String fieldName) { 98 Field field = null; 99 try { 100 field = TextView.class.getDeclaredField(fieldName); 101 field.setAccessible(true); 102 } catch (NoSuchFieldException e) { 103 Log.e(LOG_TAG, "Could not retrieve " + fieldName + " field."); 104 } 105 return field; 106 } 107 108 private static int retrieveIntFromField(Field field, TextView textView) { 109 try { 110 return field.getInt(textView); 111 } catch (IllegalAccessException e) { 112 Log.d(LOG_TAG, "Could not retrieve value of " + field.getName() + " field."); 113 } 114 return -1; 115 } 116 117 public int getMaxLines(TextView textView) { 118 if (!sMaxModeFieldFetched) { 119 sMaxModeField = retrieveField("mMaxMode"); 120 sMaxModeFieldFetched = true; 121 } 122 if (sMaxModeField != null && retrieveIntFromField(sMaxModeField, textView) == LINES) { 123 // If the max mode is using lines, we can grab the maximum value 124 if (!sMaximumFieldFetched) { 125 sMaximumField = retrieveField("mMaximum"); 126 sMaximumFieldFetched = true; 127 } 128 if (sMaximumField != null) { 129 return retrieveIntFromField(sMaximumField, textView); 130 } 131 } 132 return -1; 133 } 134 135 public int getMinLines(TextView textView) { 136 if (!sMinModeFieldFetched) { 137 sMinModeField = retrieveField("mMinMode"); 138 sMinModeFieldFetched = true; 139 } 140 if (sMinModeField != null && retrieveIntFromField(sMinModeField, textView) == LINES) { 141 // If the min mode is using lines, we can grab the maximum value 142 if (!sMinimumFieldFetched) { 143 sMinimumField = retrieveField("mMinimum"); 144 sMinimumFieldFetched = true; 145 } 146 if (sMinimumField != null) { 147 return retrieveIntFromField(sMinimumField, textView); 148 } 149 } 150 return -1; 151 } 152 153 @SuppressWarnings("deprecation") 154 public void setTextAppearance(TextView textView, @StyleRes int resId) { 155 textView.setTextAppearance(textView.getContext(), resId); 156 } 157 158 public Drawable[] getCompoundDrawablesRelative(@NonNull TextView textView) { 159 return textView.getCompoundDrawables(); 160 } 161 162 public void setAutoSizeTextTypeWithDefaults(TextView textView, int autoSizeTextType) { 163 if (textView instanceof AutoSizeableTextView) { 164 ((AutoSizeableTextView) textView).setAutoSizeTextTypeWithDefaults(autoSizeTextType); 165 } 166 } 167 168 public void setAutoSizeTextTypeUniformWithConfiguration( 169 TextView textView, 170 int autoSizeMinTextSize, 171 int autoSizeMaxTextSize, 172 int autoSizeStepGranularity, 173 int unit) throws IllegalArgumentException { 174 if (textView instanceof AutoSizeableTextView) { 175 ((AutoSizeableTextView) textView).setAutoSizeTextTypeUniformWithConfiguration( 176 autoSizeMinTextSize, autoSizeMaxTextSize, autoSizeStepGranularity, unit); 177 } 178 } 179 180 public void setAutoSizeTextTypeUniformWithPresetSizes(TextView textView, 181 @NonNull int[] presetSizes, int unit) throws IllegalArgumentException { 182 if (textView instanceof AutoSizeableTextView) { 183 ((AutoSizeableTextView) textView).setAutoSizeTextTypeUniformWithPresetSizes( 184 presetSizes, unit); 185 } 186 } 187 188 public int getAutoSizeTextType(TextView textView) { 189 if (textView instanceof AutoSizeableTextView) { 190 return ((AutoSizeableTextView) textView).getAutoSizeTextType(); 191 } 192 return AUTO_SIZE_TEXT_TYPE_NONE; 193 } 194 195 public int getAutoSizeStepGranularity(TextView textView) { 196 if (textView instanceof AutoSizeableTextView) { 197 return ((AutoSizeableTextView) textView).getAutoSizeStepGranularity(); 198 } 199 return -1; 200 } 201 202 public int getAutoSizeMinTextSize(TextView textView) { 203 if (textView instanceof AutoSizeableTextView) { 204 return ((AutoSizeableTextView) textView).getAutoSizeMinTextSize(); 205 } 206 return -1; 207 } 208 209 public int getAutoSizeMaxTextSize(TextView textView) { 210 if (textView instanceof AutoSizeableTextView) { 211 return ((AutoSizeableTextView) textView).getAutoSizeMaxTextSize(); 212 } 213 return -1; 214 } 215 216 public int[] getAutoSizeTextAvailableSizes(TextView textView) { 217 if (textView instanceof AutoSizeableTextView) { 218 return ((AutoSizeableTextView) textView).getAutoSizeTextAvailableSizes(); 219 } 220 return new int[0]; 221 } 222 } 223 224 @RequiresApi(16) 225 static class TextViewCompatApi16Impl extends TextViewCompatBaseImpl { 226 @Override 227 public int getMaxLines(TextView textView) { 228 return textView.getMaxLines(); 229 } 230 231 @Override 232 public int getMinLines(TextView textView) { 233 return textView.getMinLines(); 234 } 235 } 236 237 @RequiresApi(17) 238 static class TextViewCompatApi17Impl extends TextViewCompatApi16Impl { 239 @Override 240 public void setCompoundDrawablesRelative(@NonNull TextView textView, 241 @Nullable Drawable start, @Nullable Drawable top, @Nullable Drawable end, 242 @Nullable Drawable bottom) { 243 boolean rtl = textView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL; 244 textView.setCompoundDrawables(rtl ? end : start, top, rtl ? start : end, bottom); 245 } 246 247 @Override 248 public void setCompoundDrawablesRelativeWithIntrinsicBounds(@NonNull TextView textView, 249 @Nullable Drawable start, @Nullable Drawable top, @Nullable Drawable end, 250 @Nullable Drawable bottom) { 251 boolean rtl = textView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL; 252 textView.setCompoundDrawablesWithIntrinsicBounds(rtl ? end : start, top, 253 rtl ? start : end, bottom); 254 } 255 256 @Override 257 public void setCompoundDrawablesRelativeWithIntrinsicBounds(@NonNull TextView textView, 258 @DrawableRes int start, @DrawableRes int top, @DrawableRes int end, 259 @DrawableRes int bottom) { 260 boolean rtl = textView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL; 261 textView.setCompoundDrawablesWithIntrinsicBounds(rtl ? end : start, top, 262 rtl ? start : end, bottom); 263 } 264 265 @Override 266 public Drawable[] getCompoundDrawablesRelative(@NonNull TextView textView) { 267 final boolean rtl = textView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL; 268 final Drawable[] compounds = textView.getCompoundDrawables(); 269 if (rtl) { 270 // If we're on RTL, we need to invert the horizontal result like above 271 final Drawable start = compounds[2]; 272 final Drawable end = compounds[0]; 273 compounds[0] = start; 274 compounds[2] = end; 275 } 276 return compounds; 277 } 278 } 279 280 @RequiresApi(18) 281 static class TextViewCompatApi18Impl extends TextViewCompatApi17Impl { 282 @Override 283 public void setCompoundDrawablesRelative(@NonNull TextView textView, 284 @Nullable Drawable start, @Nullable Drawable top, @Nullable Drawable end, 285 @Nullable Drawable bottom) { 286 textView.setCompoundDrawablesRelative(start, top, end, bottom); 287 } 288 289 @Override 290 public void setCompoundDrawablesRelativeWithIntrinsicBounds(@NonNull TextView textView, 291 @Nullable Drawable start, @Nullable Drawable top, @Nullable Drawable end, 292 @Nullable Drawable bottom) { 293 textView.setCompoundDrawablesRelativeWithIntrinsicBounds(start, top, end, bottom); 294 } 295 296 @Override 297 public void setCompoundDrawablesRelativeWithIntrinsicBounds(@NonNull TextView textView, 298 @DrawableRes int start, @DrawableRes int top, @DrawableRes int end, 299 @DrawableRes int bottom) { 300 textView.setCompoundDrawablesRelativeWithIntrinsicBounds(start, top, end, bottom); 301 } 302 303 @Override 304 public Drawable[] getCompoundDrawablesRelative(@NonNull TextView textView) { 305 return textView.getCompoundDrawablesRelative(); 306 } 307 } 308 309 @RequiresApi(23) 310 static class TextViewCompatApi23Impl extends TextViewCompatApi18Impl { 311 @Override 312 public void setTextAppearance(@NonNull TextView textView, @StyleRes int resId) { 313 textView.setTextAppearance(resId); 314 } 315 } 316 317 @RequiresApi(27) 318 static class TextViewCompatApi27Impl extends TextViewCompatApi23Impl { 319 @Override 320 public void setAutoSizeTextTypeWithDefaults(TextView textView, int autoSizeTextType) { 321 textView.setAutoSizeTextTypeWithDefaults(autoSizeTextType); 322 } 323 324 @Override 325 public void setAutoSizeTextTypeUniformWithConfiguration( 326 TextView textView, 327 int autoSizeMinTextSize, 328 int autoSizeMaxTextSize, 329 int autoSizeStepGranularity, 330 int unit) throws IllegalArgumentException { 331 textView.setAutoSizeTextTypeUniformWithConfiguration( 332 autoSizeMinTextSize, autoSizeMaxTextSize, autoSizeStepGranularity, unit); 333 } 334 335 @Override 336 public void setAutoSizeTextTypeUniformWithPresetSizes(TextView textView, 337 @NonNull int[] presetSizes, int unit) throws IllegalArgumentException { 338 textView.setAutoSizeTextTypeUniformWithPresetSizes(presetSizes, unit); 339 } 340 341 @Override 342 public int getAutoSizeTextType(TextView textView) { 343 return textView.getAutoSizeTextType(); 344 } 345 346 @Override 347 public int getAutoSizeStepGranularity(TextView textView) { 348 return textView.getAutoSizeStepGranularity(); 349 } 350 351 @Override 352 public int getAutoSizeMinTextSize(TextView textView) { 353 return textView.getAutoSizeMinTextSize(); 354 } 355 356 @Override 357 public int getAutoSizeMaxTextSize(TextView textView) { 358 return textView.getAutoSizeMaxTextSize(); 359 } 360 361 @Override 362 public int[] getAutoSizeTextAvailableSizes(TextView textView) { 363 return textView.getAutoSizeTextAvailableSizes(); 364 } 365 } 366 367 static final TextViewCompatBaseImpl IMPL; 368 369 static { 370 if (BuildCompat.isAtLeastOMR1()) { 371 IMPL = new TextViewCompatApi27Impl(); 372 } else if (Build.VERSION.SDK_INT >= 23) { 373 IMPL = new TextViewCompatApi23Impl(); 374 } else if (Build.VERSION.SDK_INT >= 18) { 375 IMPL = new TextViewCompatApi18Impl(); 376 } else if (Build.VERSION.SDK_INT >= 17) { 377 IMPL = new TextViewCompatApi17Impl(); 378 } else if (Build.VERSION.SDK_INT >= 16) { 379 IMPL = new TextViewCompatApi16Impl(); 380 } else { 381 IMPL = new TextViewCompatBaseImpl(); 382 } 383 } 384 385 /** 386 * Sets the Drawables (if any) to appear to the start of, above, to the end 387 * of, and below the text. Use {@code null} if you do not want a Drawable 388 * there. The Drawables must already have had {@link Drawable#setBounds} 389 * called. 390 * <p/> 391 * Calling this method will overwrite any Drawables previously set using 392 * {@link TextView#setCompoundDrawables} or related methods. 393 * 394 * @param textView The TextView against which to invoke the method. 395 * @attr name android:drawableStart 396 * @attr name android:drawableTop 397 * @attr name android:drawableEnd 398 * @attr name android:drawableBottom 399 */ 400 public static void setCompoundDrawablesRelative(@NonNull TextView textView, 401 @Nullable Drawable start, @Nullable Drawable top, @Nullable Drawable end, 402 @Nullable Drawable bottom) { 403 IMPL.setCompoundDrawablesRelative(textView, start, top, end, bottom); 404 } 405 406 /** 407 * Sets the Drawables (if any) to appear to the start of, above, to the end 408 * of, and below the text. Use {@code null} if you do not want a Drawable 409 * there. The Drawables' bounds will be set to their intrinsic bounds. 410 * <p/> 411 * Calling this method will overwrite any Drawables previously set using 412 * {@link TextView#setCompoundDrawables} or related methods. 413 * 414 * @param textView The TextView against which to invoke the method. 415 * @attr name android:drawableStart 416 * @attr name android:drawableTop 417 * @attr name android:drawableEnd 418 * @attr name android:drawableBottom 419 */ 420 public static void setCompoundDrawablesRelativeWithIntrinsicBounds(@NonNull TextView textView, 421 @Nullable Drawable start, @Nullable Drawable top, @Nullable Drawable end, 422 @Nullable Drawable bottom) { 423 IMPL.setCompoundDrawablesRelativeWithIntrinsicBounds(textView, start, top, end, bottom); 424 } 425 426 /** 427 * Sets the Drawables (if any) to appear to the start of, above, to the end 428 * of, and below the text. Use 0 if you do not want a Drawable there. The 429 * Drawables' bounds will be set to their intrinsic bounds. 430 * <p/> 431 * Calling this method will overwrite any Drawables previously set using 432 * {@link TextView#setCompoundDrawables} or related methods. 433 * 434 * @param textView The TextView against which to invoke the method. 435 * @param start Resource identifier of the start Drawable. 436 * @param top Resource identifier of the top Drawable. 437 * @param end Resource identifier of the end Drawable. 438 * @param bottom Resource identifier of the bottom Drawable. 439 * @attr name android:drawableStart 440 * @attr name android:drawableTop 441 * @attr name android:drawableEnd 442 * @attr name android:drawableBottom 443 */ 444 public static void setCompoundDrawablesRelativeWithIntrinsicBounds(@NonNull TextView textView, 445 @DrawableRes int start, @DrawableRes int top, @DrawableRes int end, 446 @DrawableRes int bottom) { 447 IMPL.setCompoundDrawablesRelativeWithIntrinsicBounds(textView, start, top, end, bottom); 448 } 449 450 /** 451 * Returns the maximum number of lines displayed in the given TextView, or -1 if the maximum 452 * height was set in pixels instead. 453 */ 454 public static int getMaxLines(@NonNull TextView textView) { 455 return IMPL.getMaxLines(textView); 456 } 457 458 /** 459 * Returns the minimum number of lines displayed in the given TextView, or -1 if the minimum 460 * height was set in pixels instead. 461 */ 462 public static int getMinLines(@NonNull TextView textView) { 463 return IMPL.getMinLines(textView); 464 } 465 466 /** 467 * Sets the text appearance from the specified style resource. 468 * <p> 469 * Use a framework-defined {@code TextAppearance} style like 470 * {@link android.R.style#TextAppearance_Material_Body1 @android:style/TextAppearance.Material.Body1}. 471 * 472 * @param textView The TextView against which to invoke the method. 473 * @param resId The resource identifier of the style to apply. 474 */ 475 public static void setTextAppearance(@NonNull TextView textView, @StyleRes int resId) { 476 IMPL.setTextAppearance(textView, resId); 477 } 478 479 /** 480 * Returns drawables for the start, top, end, and bottom borders from the given text view. 481 */ 482 public static Drawable[] getCompoundDrawablesRelative(@NonNull TextView textView) { 483 return IMPL.getCompoundDrawablesRelative(textView); 484 } 485 486 /** 487 * Specify whether this widget should automatically scale the text to try to perfectly fit 488 * within the layout bounds by using the default auto-size configuration. 489 * 490 * @param autoSizeTextType the type of auto-size. Must be one of 491 * {@link TextViewCompat#AUTO_SIZE_TEXT_TYPE_NONE} or 492 * {@link TextViewCompat#AUTO_SIZE_TEXT_TYPE_UNIFORM} 493 * 494 * @attr name android:autoSizeTextType 495 */ 496 public static void setAutoSizeTextTypeWithDefaults(TextView textView, int autoSizeTextType) { 497 IMPL.setAutoSizeTextTypeWithDefaults(textView, autoSizeTextType); 498 } 499 500 /** 501 * Specify whether this widget should automatically scale the text to try to perfectly fit 502 * within the layout bounds. If all the configuration params are valid the type of auto-size is 503 * set to {@link TextViewCompat#AUTO_SIZE_TEXT_TYPE_UNIFORM}. 504 * 505 * @param autoSizeMinTextSize the minimum text size available for auto-size 506 * @param autoSizeMaxTextSize the maximum text size available for auto-size 507 * @param autoSizeStepGranularity the auto-size step granularity. It is used in conjunction with 508 * the minimum and maximum text size in order to build the set of 509 * text sizes the system uses to choose from when auto-sizing 510 * @param unit the desired dimension unit for all sizes above. See {@link TypedValue} for the 511 * possible dimension units 512 * 513 * @throws IllegalArgumentException if any of the configuration params are invalid. 514 * 515 * @attr name android:autoSizeTextType 516 * @attr name android:autoSizeTextType 517 * @attr name android:autoSizeMinTextSize 518 * @attr name android:autoSizeMaxTextSize 519 * @attr name android:autoSizeStepGranularity 520 */ 521 public static void setAutoSizeTextTypeUniformWithConfiguration( 522 TextView textView, 523 int autoSizeMinTextSize, 524 int autoSizeMaxTextSize, 525 int autoSizeStepGranularity, 526 int unit) throws IllegalArgumentException { 527 IMPL.setAutoSizeTextTypeUniformWithConfiguration(textView, autoSizeMinTextSize, 528 autoSizeMaxTextSize, autoSizeStepGranularity, unit); 529 } 530 531 /** 532 * Specify whether this widget should automatically scale the text to try to perfectly fit 533 * within the layout bounds. If at least one value from the <code>presetSizes</code> is valid 534 * then the type of auto-size is set to {@link TextViewCompat#AUTO_SIZE_TEXT_TYPE_UNIFORM}. 535 * 536 * @param presetSizes an {@code int} array of sizes in pixels 537 * @param unit the desired dimension unit for the preset sizes above. See {@link TypedValue} for 538 * the possible dimension units 539 * 540 * @throws IllegalArgumentException if all of the <code>presetSizes</code> are invalid. 541 *_ 542 * @attr name android:autoSizeTextType 543 * @attr name android:autoSizePresetSizes 544 */ 545 public static void setAutoSizeTextTypeUniformWithPresetSizes(TextView textView, 546 @NonNull int[] presetSizes, int unit) throws IllegalArgumentException { 547 IMPL.setAutoSizeTextTypeUniformWithPresetSizes(textView, presetSizes, unit); 548 } 549 550 /** 551 * Returns the type of auto-size set for this widget. 552 * 553 * @return an {@code int} corresponding to one of the auto-size types: 554 * {@link TextViewCompat#AUTO_SIZE_TEXT_TYPE_NONE} or 555 * {@link TextViewCompat#AUTO_SIZE_TEXT_TYPE_UNIFORM} 556 * 557 * @attr name android:autoSizeTextType 558 */ 559 public static int getAutoSizeTextType(TextView textView) { 560 return IMPL.getAutoSizeTextType(textView); 561 } 562 563 /** 564 * @return the current auto-size step granularity in pixels. 565 * 566 * @attr name android:autoSizeStepGranularity 567 */ 568 public static int getAutoSizeStepGranularity(TextView textView) { 569 return IMPL.getAutoSizeStepGranularity(textView); 570 } 571 572 /** 573 * @return the current auto-size minimum text size in pixels (the default is 12sp). Note that 574 * if auto-size has not been configured this function returns {@code -1}. 575 * 576 * @attr name android:autoSizeMinTextSize 577 */ 578 public static int getAutoSizeMinTextSize(TextView textView) { 579 return IMPL.getAutoSizeMinTextSize(textView); 580 } 581 582 /** 583 * @return the current auto-size maximum text size in pixels (the default is 112sp). Note that 584 * if auto-size has not been configured this function returns {@code -1}. 585 * 586 * @attr name android:autoSizeMaxTextSize 587 */ 588 public static int getAutoSizeMaxTextSize(TextView textView) { 589 return IMPL.getAutoSizeMaxTextSize(textView); 590 } 591 592 /** 593 * @return the current auto-size {@code int} sizes array (in pixels). 594 * 595 * @attr name android:autoSizePresetSizes 596 */ 597 public static int[] getAutoSizeTextAvailableSizes(TextView textView) { 598 return IMPL.getAutoSizeTextAvailableSizes(textView); 599 } 600} 601