1 /*
2  * Copyright (C) 2014 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.accessibility.AccessibilityEvent;
27 import android.view.accessibility.AccessibilityNodeInfo;
28 import android.widget.Button;
29 import android.widget.TextView;
30 
31 import androidx.annotation.DrawableRes;
32 import androidx.annotation.NonNull;
33 import androidx.annotation.Nullable;
34 import androidx.annotation.RestrictTo;
35 import androidx.appcompat.R;
36 import androidx.core.view.TintableBackgroundView;
37 import androidx.core.widget.AutoSizeableTextView;
38 import androidx.core.widget.TextViewCompat;
39 
40 /**
41  * A {@link Button} which supports compatible features on older versions of the platform,
42  * including:
43  * <ul>
44  *     <li>Allows dynamic tint of its background via the background tint methods in
45  *     {@link androidx.core.view.ViewCompat}.</li>
46  *     <li>Allows setting of the background tint using {@link R.attr#backgroundTint} and
47  *     {@link R.attr#backgroundTintMode}.</li>
48  * </ul>
49  *
50  * <p>This will automatically be used when you use {@link Button} in your layouts
51  * and the top-level activity / dialog is provided by
52  * <a href="{@docRoot}topic/libraries/support-library/packages.html#v7-appcompat">appcompat</a>.
53  * You should only need to manually use this class when writing custom views.</p>
54  */
55 public class AppCompatButton extends Button implements TintableBackgroundView,
56         AutoSizeableTextView {
57 
58     private final AppCompatBackgroundHelper mBackgroundTintHelper;
59     private final AppCompatTextHelper mTextHelper;
60 
AppCompatButton(Context context)61     public AppCompatButton(Context context) {
62         this(context, null);
63     }
64 
AppCompatButton(Context context, AttributeSet attrs)65     public AppCompatButton(Context context, AttributeSet attrs) {
66         this(context, attrs, R.attr.buttonStyle);
67     }
68 
AppCompatButton(Context context, AttributeSet attrs, int defStyleAttr)69     public AppCompatButton(Context context, AttributeSet attrs, int defStyleAttr) {
70         super(TintContextWrapper.wrap(context), attrs, defStyleAttr);
71 
72         mBackgroundTintHelper = new AppCompatBackgroundHelper(this);
73         mBackgroundTintHelper.loadFromAttributes(attrs, defStyleAttr);
74 
75         mTextHelper = new AppCompatTextHelper(this);
76         mTextHelper.loadFromAttributes(attrs, defStyleAttr);
77         mTextHelper.applyCompoundDrawablesTints();
78     }
79 
80     @Override
setBackgroundResource(@rawableRes int resId)81     public void setBackgroundResource(@DrawableRes int resId) {
82         super.setBackgroundResource(resId);
83         if (mBackgroundTintHelper != null) {
84             mBackgroundTintHelper.onSetBackgroundResource(resId);
85         }
86     }
87 
88     @Override
setBackgroundDrawable(Drawable background)89     public void setBackgroundDrawable(Drawable background) {
90         super.setBackgroundDrawable(background);
91         if (mBackgroundTintHelper != null) {
92             mBackgroundTintHelper.onSetBackgroundDrawable(background);
93         }
94     }
95 
96     /**
97      * This should be accessed via
98      * {@link androidx.core.view.ViewCompat#setBackgroundTintList(android.view.View, ColorStateList)}
99      *
100      * @hide
101      */
102     @RestrictTo(LIBRARY_GROUP)
103     @Override
setSupportBackgroundTintList(@ullable ColorStateList tint)104     public void setSupportBackgroundTintList(@Nullable ColorStateList tint) {
105         if (mBackgroundTintHelper != null) {
106             mBackgroundTintHelper.setSupportBackgroundTintList(tint);
107         }
108     }
109 
110     /**
111      * This should be accessed via
112      * {@link androidx.core.view.ViewCompat#getBackgroundTintList(android.view.View)}
113      *
114      * @hide
115      */
116     @RestrictTo(LIBRARY_GROUP)
117     @Override
118     @Nullable
getSupportBackgroundTintList()119     public ColorStateList getSupportBackgroundTintList() {
120         return mBackgroundTintHelper != null
121                 ? mBackgroundTintHelper.getSupportBackgroundTintList() : null;
122     }
123 
124     /**
125      * This should be accessed via
126      * {@link androidx.core.view.ViewCompat#setBackgroundTintMode(android.view.View, PorterDuff.Mode)}
127      *
128      * @hide
129      */
130     @RestrictTo(LIBRARY_GROUP)
131     @Override
setSupportBackgroundTintMode(@ullable PorterDuff.Mode tintMode)132     public void setSupportBackgroundTintMode(@Nullable PorterDuff.Mode tintMode) {
133         if (mBackgroundTintHelper != null) {
134             mBackgroundTintHelper.setSupportBackgroundTintMode(tintMode);
135         }
136     }
137 
138     /**
139      * This should be accessed via
140      * {@link androidx.core.view.ViewCompat#getBackgroundTintMode(android.view.View)}
141      *
142      * @hide
143      */
144     @RestrictTo(LIBRARY_GROUP)
145     @Override
146     @Nullable
getSupportBackgroundTintMode()147     public PorterDuff.Mode getSupportBackgroundTintMode() {
148         return mBackgroundTintHelper != null
149                 ? mBackgroundTintHelper.getSupportBackgroundTintMode() : null;
150     }
151 
152     @Override
drawableStateChanged()153     protected void drawableStateChanged() {
154         super.drawableStateChanged();
155         if (mBackgroundTintHelper != null) {
156             mBackgroundTintHelper.applySupportBackgroundTint();
157         }
158         if (mTextHelper != null) {
159             mTextHelper.applyCompoundDrawablesTints();
160         }
161     }
162 
163     @Override
setTextAppearance(Context context, int resId)164     public void setTextAppearance(Context context, int resId) {
165         super.setTextAppearance(context, resId);
166         if (mTextHelper != null) {
167             mTextHelper.onSetTextAppearance(context, resId);
168         }
169     }
170 
171     @Override
onInitializeAccessibilityEvent(AccessibilityEvent event)172     public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
173         super.onInitializeAccessibilityEvent(event);
174         event.setClassName(Button.class.getName());
175     }
176 
177     @Override
onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info)178     public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
179         super.onInitializeAccessibilityNodeInfo(info);
180         info.setClassName(Button.class.getName());
181     }
182 
183     @Override
onLayout(boolean changed, int left, int top, int right, int bottom)184     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
185         super.onLayout(changed, left, top, right, bottom);
186         if (mTextHelper != null) {
187             mTextHelper.onLayout(changed, left, top, right, bottom);
188         }
189     }
190 
191     @Override
setTextSize(int unit, float size)192     public void setTextSize(int unit, float size) {
193         if (PLATFORM_SUPPORTS_AUTOSIZE) {
194             super.setTextSize(unit, size);
195         } else {
196             if (mTextHelper != null) {
197                 mTextHelper.setTextSize(unit, size);
198             }
199         }
200     }
201 
202     @Override
onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter)203     protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
204         super.onTextChanged(text, start, lengthBefore, lengthAfter);
205         if (mTextHelper != null && !PLATFORM_SUPPORTS_AUTOSIZE && mTextHelper.isAutoSizeEnabled()) {
206             mTextHelper.autoSizeText();
207         }
208     }
209 
210     /**
211      * @hide
212      */
213     @RestrictTo(LIBRARY_GROUP)
214     @Override
setAutoSizeTextTypeWithDefaults( @extViewCompat.AutoSizeTextType int autoSizeTextType)215     public void setAutoSizeTextTypeWithDefaults(
216             @TextViewCompat.AutoSizeTextType int autoSizeTextType) {
217         if (PLATFORM_SUPPORTS_AUTOSIZE) {
218             super.setAutoSizeTextTypeWithDefaults(autoSizeTextType);
219         } else {
220             if (mTextHelper != null) {
221                 mTextHelper.setAutoSizeTextTypeWithDefaults(autoSizeTextType);
222             }
223         }
224     }
225 
226     /**
227      * @hide
228      */
229     @RestrictTo(LIBRARY_GROUP)
230     @Override
setAutoSizeTextTypeUniformWithConfiguration( int autoSizeMinTextSize, int autoSizeMaxTextSize, int autoSizeStepGranularity, int unit)231     public void setAutoSizeTextTypeUniformWithConfiguration(
232             int autoSizeMinTextSize,
233             int autoSizeMaxTextSize,
234             int autoSizeStepGranularity,
235             int unit) throws IllegalArgumentException {
236         if (PLATFORM_SUPPORTS_AUTOSIZE) {
237             super.setAutoSizeTextTypeUniformWithConfiguration(
238                     autoSizeMinTextSize, autoSizeMaxTextSize, autoSizeStepGranularity, unit);
239         } else {
240             if (mTextHelper != null) {
241                 mTextHelper.setAutoSizeTextTypeUniformWithConfiguration(
242                         autoSizeMinTextSize, autoSizeMaxTextSize, autoSizeStepGranularity, unit);
243             }
244         }
245     }
246 
247     /**
248      * @hide
249      */
250     @RestrictTo(LIBRARY_GROUP)
251     @Override
setAutoSizeTextTypeUniformWithPresetSizes(@onNull int[] presetSizes, int unit)252     public void setAutoSizeTextTypeUniformWithPresetSizes(@NonNull int[] presetSizes, int unit)
253             throws IllegalArgumentException {
254         if (PLATFORM_SUPPORTS_AUTOSIZE) {
255             super.setAutoSizeTextTypeUniformWithPresetSizes(presetSizes, unit);
256         } else {
257             if (mTextHelper != null) {
258                 mTextHelper.setAutoSizeTextTypeUniformWithPresetSizes(presetSizes, unit);
259             }
260         }
261     }
262 
263     /**
264      * @hide
265      */
266     @RestrictTo(LIBRARY_GROUP)
267     @Override
268     @TextViewCompat.AutoSizeTextType
getAutoSizeTextType()269     public int getAutoSizeTextType() {
270         if (PLATFORM_SUPPORTS_AUTOSIZE) {
271             return super.getAutoSizeTextType() == TextView.AUTO_SIZE_TEXT_TYPE_UNIFORM
272                     ? TextViewCompat.AUTO_SIZE_TEXT_TYPE_UNIFORM
273                     : TextViewCompat.AUTO_SIZE_TEXT_TYPE_NONE;
274         } else {
275             if (mTextHelper != null) {
276                 return mTextHelper.getAutoSizeTextType();
277             }
278         }
279         return TextViewCompat.AUTO_SIZE_TEXT_TYPE_NONE;
280     }
281 
282     /**
283      * @hide
284      */
285     @RestrictTo(LIBRARY_GROUP)
286     @Override
getAutoSizeStepGranularity()287     public int getAutoSizeStepGranularity() {
288         if (PLATFORM_SUPPORTS_AUTOSIZE) {
289             return super.getAutoSizeStepGranularity();
290         } else {
291             if (mTextHelper != null) {
292                 return mTextHelper.getAutoSizeStepGranularity();
293             }
294         }
295         return -1;
296     }
297 
298     /**
299      * @hide
300      */
301     @RestrictTo(LIBRARY_GROUP)
302     @Override
getAutoSizeMinTextSize()303     public int getAutoSizeMinTextSize() {
304         if (PLATFORM_SUPPORTS_AUTOSIZE) {
305             return super.getAutoSizeMinTextSize();
306         } else {
307             if (mTextHelper != null) {
308                 return mTextHelper.getAutoSizeMinTextSize();
309             }
310         }
311         return -1;
312     }
313 
314     /**
315      * @hide
316      */
317     @RestrictTo(LIBRARY_GROUP)
318     @Override
getAutoSizeMaxTextSize()319     public int getAutoSizeMaxTextSize() {
320         if (PLATFORM_SUPPORTS_AUTOSIZE) {
321             return super.getAutoSizeMaxTextSize();
322         } else {
323             if (mTextHelper != null) {
324                 return mTextHelper.getAutoSizeMaxTextSize();
325             }
326         }
327         return -1;
328     }
329 
330     /**
331      * @hide
332      */
333     @RestrictTo(LIBRARY_GROUP)
334     @Override
getAutoSizeTextAvailableSizes()335     public int[] getAutoSizeTextAvailableSizes() {
336         if (PLATFORM_SUPPORTS_AUTOSIZE) {
337             return super.getAutoSizeTextAvailableSizes();
338         } else {
339             if (mTextHelper != null) {
340                 return mTextHelper.getAutoSizeTextAvailableSizes();
341             }
342         }
343         return new int[0];
344     }
345 
346     /**
347      * Sets the properties of this field to transform input to ALL CAPS
348      * display. This may use a "small caps" formatting if available.
349      * This setting will be ignored if this field is editable or selectable.
350      *
351      * This call replaces the current transformation method. Disabling this
352      * will not necessarily restore the previous behavior from before this
353      * was enabled.
354      */
setSupportAllCaps(boolean allCaps)355     public void setSupportAllCaps(boolean allCaps) {
356         if (mTextHelper != null) {
357             mTextHelper.setAllCaps(allCaps);
358         }
359     }
360 }
361