Home | History | Annotate | Download | only in widget
      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 
     71     public AppCompatTextView(Context context) {
     72         this(context, null);
     73     }
     74 
     75     public AppCompatTextView(Context context, AttributeSet attrs) {
     76         this(context, attrs, android.R.attr.textViewStyle);
     77     }
     78 
     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
     91     public void setBackgroundResource(@DrawableRes int resId) {
     92         super.setBackgroundResource(resId);
     93         if (mBackgroundTintHelper != null) {
     94             mBackgroundTintHelper.onSetBackgroundResource(resId);
     95         }
     96     }
     97 
     98     @Override
     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
    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
    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
    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
    157     public PorterDuff.Mode getSupportBackgroundTintMode() {
    158         return mBackgroundTintHelper != null
    159                 ? mBackgroundTintHelper.getSupportBackgroundTintMode() : null;
    160     }
    161 
    162     @Override
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    372     public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
    373         return AppCompatHintHelper.onCreateInputConnection(super.onCreateInputConnection(outAttrs),
    374                 outAttrs, this);
    375     }
    376 
    377     @Override
    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
    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
    398     public int getFirstBaselineToTopHeight() {
    399         return TextViewCompat.getFirstBaselineToTopHeight(this);
    400     }
    401 
    402     @Override
    403     public int getLastBaselineToBottomHeight() {
    404         return TextViewCompat.getLastBaselineToBottomHeight(this);
    405     }
    406 
    407     @Override
    408     public void setLineHeight(@Px @IntRange(from = 0) int lineHeight) {
    409         TextViewCompat.setLineHeight(this, lineHeight);
    410     }
    411 }
    412