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