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_PREFIX;
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.text.InputFilter;
26 import android.util.AttributeSet;
27 import android.view.View;
28 import android.widget.RadioButton;
29 import android.widget.TextView;
30 
31 import androidx.annotation.DrawableRes;
32 import androidx.annotation.RestrictTo;
33 import androidx.appcompat.R;
34 import androidx.appcompat.content.res.AppCompatResources;
35 import androidx.core.view.TintableBackgroundView;
36 import androidx.core.view.ViewCompat;
37 import androidx.core.widget.TintableCompoundButton;
38 import androidx.core.widget.TintableCompoundDrawablesView;
39 import androidx.resourceinspection.annotation.AppCompatShadowedAttributes;
40 
41 import org.jspecify.annotations.NonNull;
42 import org.jspecify.annotations.Nullable;
43 
44 /**
45  * A {@link RadioButton} which supports compatible features on older versions of the platform,
46  * including:
47  * <ul>
48  *     <li>Allows dynamic tint of its background via the background tint methods in
49  *     {@link androidx.core.widget.CompoundButtonCompat}.</li>
50  *     <li>Allows setting of the background tint using {@link R.attr#buttonTint} and
51  *     {@link R.attr#buttonTintMode}.</li>
52  * </ul>
53  *
54  * <p>This will automatically be used when you use {@link RadioButton} in your layouts
55  * and the top-level activity / dialog is provided by
56  * <a href="{@docRoot}topic/libraries/support-library/packages.html#v7-appcompat">appcompat</a>.
57  * You should only need to manually use this class when writing custom views.</p>
58  */
59 @AppCompatShadowedAttributes
60 public class AppCompatRadioButton extends RadioButton implements TintableCompoundButton,
61         TintableBackgroundView, EmojiCompatConfigurationView, TintableCompoundDrawablesView {
62 
63     private final AppCompatCompoundButtonHelper mCompoundButtonHelper;
64     private final AppCompatBackgroundHelper mBackgroundTintHelper;
65     private final AppCompatTextHelper mTextHelper;
66     private AppCompatEmojiTextHelper mAppCompatEmojiTextHelper;
67 
AppCompatRadioButton(Context context)68     public AppCompatRadioButton(Context context) {
69         this(context, null);
70     }
71 
AppCompatRadioButton(Context context, @Nullable AttributeSet attrs)72     public AppCompatRadioButton(Context context, @Nullable AttributeSet attrs) {
73         this(context, attrs, R.attr.radioButtonStyle);
74     }
75 
AppCompatRadioButton(Context context, @Nullable AttributeSet attrs, int defStyleAttr)76     public AppCompatRadioButton(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
77         super(TintContextWrapper.wrap(context), attrs, defStyleAttr);
78 
79         ThemeUtils.checkAppCompatTheme(this, getContext());
80 
81         mCompoundButtonHelper = new AppCompatCompoundButtonHelper(this);
82         mCompoundButtonHelper.loadFromAttributes(attrs, defStyleAttr);
83 
84         mBackgroundTintHelper = new AppCompatBackgroundHelper(this);
85         mBackgroundTintHelper.loadFromAttributes(attrs, defStyleAttr);
86 
87         mTextHelper = new AppCompatTextHelper(this);
88         mTextHelper.loadFromAttributes(attrs, defStyleAttr);
89         AppCompatEmojiTextHelper emojiTextViewHelper = getEmojiTextViewHelper();
90         emojiTextViewHelper.loadFromAttributes(attrs, defStyleAttr);
91     }
92 
93     /**
94      * This may be called from super constructors.
95      */
getEmojiTextViewHelper()96     private @NonNull AppCompatEmojiTextHelper getEmojiTextViewHelper() {
97         if (mAppCompatEmojiTextHelper == null) {
98             mAppCompatEmojiTextHelper = new AppCompatEmojiTextHelper(this);
99         }
100         return mAppCompatEmojiTextHelper;
101     }
102 
103     @Override
setButtonDrawable(Drawable buttonDrawable)104     public void setButtonDrawable(Drawable buttonDrawable) {
105         super.setButtonDrawable(buttonDrawable);
106         if (mCompoundButtonHelper != null) {
107             mCompoundButtonHelper.onSetButtonDrawable();
108         }
109     }
110 
111     @Override
setButtonDrawable(@rawableRes int resId)112     public void setButtonDrawable(@DrawableRes int resId) {
113         setButtonDrawable(AppCompatResources.getDrawable(getContext(), resId));
114     }
115 
116     /**
117      * This should be accessed from {@link androidx.core.widget.CompoundButtonCompat}
118      */
119     @RestrictTo(LIBRARY_GROUP_PREFIX)
120     @Override
setSupportButtonTintList(@ullable ColorStateList tint)121     public void setSupportButtonTintList(@Nullable ColorStateList tint) {
122         if (mCompoundButtonHelper != null) {
123             mCompoundButtonHelper.setSupportButtonTintList(tint);
124         }
125     }
126 
127     /**
128      * This should be accessed from {@link androidx.core.widget.CompoundButtonCompat}
129      */
130     @RestrictTo(LIBRARY_GROUP_PREFIX)
131     @Override
getSupportButtonTintList()132     public @Nullable ColorStateList getSupportButtonTintList() {
133         return mCompoundButtonHelper != null
134                 ? mCompoundButtonHelper.getSupportButtonTintList()
135                 : null;
136     }
137 
138     /**
139      * This should be accessed from {@link androidx.core.widget.CompoundButtonCompat}
140      */
141     @RestrictTo(LIBRARY_GROUP_PREFIX)
142     @Override
setSupportButtonTintMode(PorterDuff.@ullable Mode tintMode)143     public void setSupportButtonTintMode(PorterDuff.@Nullable Mode tintMode) {
144         if (mCompoundButtonHelper != null) {
145             mCompoundButtonHelper.setSupportButtonTintMode(tintMode);
146         }
147     }
148 
149     /**
150      * This should be accessed from {@link androidx.core.widget.CompoundButtonCompat}
151      */
152     @RestrictTo(LIBRARY_GROUP_PREFIX)
153     @Override
getSupportButtonTintMode()154     public PorterDuff.@Nullable Mode getSupportButtonTintMode() {
155         return mCompoundButtonHelper != null
156                 ? mCompoundButtonHelper.getSupportButtonTintMode()
157                 : null;
158     }
159 
160     /**
161      * This should be accessed via
162      * {@link ViewCompat#setBackgroundTintList(View, ColorStateList)}
163      *
164      */
165     @RestrictTo(LIBRARY_GROUP_PREFIX)
166     @Override
setSupportBackgroundTintList(@ullable ColorStateList tint)167     public void setSupportBackgroundTintList(@Nullable ColorStateList tint) {
168         if (mBackgroundTintHelper != null) {
169             mBackgroundTintHelper.setSupportBackgroundTintList(tint);
170         }
171     }
172 
173     /**
174      * This should be accessed via
175      * {@link androidx.core.view.ViewCompat#getBackgroundTintList(android.view.View)}
176      *
177      */
178     @RestrictTo(LIBRARY_GROUP_PREFIX)
179     @Override
getSupportBackgroundTintList()180     public @Nullable ColorStateList getSupportBackgroundTintList() {
181         return mBackgroundTintHelper != null
182                 ? mBackgroundTintHelper.getSupportBackgroundTintList() : null;
183     }
184 
185     /**
186      * This should be accessed via
187      * {@link ViewCompat#setBackgroundTintMode(View, PorterDuff.Mode)}
188      *
189      */
190     @RestrictTo(LIBRARY_GROUP_PREFIX)
191     @Override
setSupportBackgroundTintMode(PorterDuff.@ullable Mode tintMode)192     public void setSupportBackgroundTintMode(PorterDuff.@Nullable Mode tintMode) {
193         if (mBackgroundTintHelper != null) {
194             mBackgroundTintHelper.setSupportBackgroundTintMode(tintMode);
195         }
196     }
197 
198     /**
199      * This should be accessed via
200      * {@link androidx.core.view.ViewCompat#getBackgroundTintMode(android.view.View)}
201      *
202      */
203     @RestrictTo(LIBRARY_GROUP_PREFIX)
204     @Override
getSupportBackgroundTintMode()205     public PorterDuff.@Nullable Mode getSupportBackgroundTintMode() {
206         return mBackgroundTintHelper != null
207                 ? mBackgroundTintHelper.getSupportBackgroundTintMode() : null;
208     }
209 
210     @Override
setBackgroundDrawable(@ullable Drawable background)211     public void setBackgroundDrawable(@Nullable Drawable background) {
212         super.setBackgroundDrawable(background);
213         if (mBackgroundTintHelper != null) {
214             mBackgroundTintHelper.onSetBackgroundDrawable(background);
215         }
216     }
217 
218     @Override
setBackgroundResource(@rawableRes int resId)219     public void setBackgroundResource(@DrawableRes int resId) {
220         super.setBackgroundResource(resId);
221         if (mBackgroundTintHelper != null) {
222             mBackgroundTintHelper.onSetBackgroundResource(resId);
223         }
224     }
225 
226     @Override
drawableStateChanged()227     protected void drawableStateChanged() {
228         super.drawableStateChanged();
229         if (mBackgroundTintHelper != null) {
230             mBackgroundTintHelper.applySupportBackgroundTint();
231         }
232         if (mTextHelper != null) {
233             mTextHelper.applyCompoundDrawablesTints();
234         }
235     }
236 
237     @Override
setFilters(@uppressWarnings"ArrayReturn") InputFilter @onNull [] filters)238     public void setFilters(@SuppressWarnings("ArrayReturn") InputFilter @NonNull [] filters) {
239         super.setFilters(getEmojiTextViewHelper().getFilters(filters));
240     }
241 
242     @Override
setAllCaps(boolean allCaps)243     public void setAllCaps(boolean allCaps) {
244         super.setAllCaps(allCaps);
245         getEmojiTextViewHelper().setAllCaps(allCaps);
246     }
247 
248     @Override
setEmojiCompatEnabled(boolean enabled)249     public void setEmojiCompatEnabled(boolean enabled) {
250         getEmojiTextViewHelper().setEnabled(enabled);
251     }
252 
253     @Override
isEmojiCompatEnabled()254     public boolean isEmojiCompatEnabled() {
255         return getEmojiTextViewHelper().isEnabled();
256     }
257 
258     @Override
setCompoundDrawables(@ullable Drawable left, @Nullable Drawable top, @Nullable Drawable right, @Nullable Drawable bottom)259     public void setCompoundDrawables(@Nullable Drawable left, @Nullable Drawable top,
260             @Nullable Drawable right, @Nullable Drawable bottom) {
261         super.setCompoundDrawables(left, top, right, bottom);
262         if (mTextHelper != null) {
263             mTextHelper.onSetCompoundDrawables();
264         }
265     }
266 
267     @Override
setCompoundDrawablesRelative(@ullable Drawable start, @Nullable Drawable top, @Nullable Drawable end, @Nullable Drawable bottom)268     public void setCompoundDrawablesRelative(@Nullable Drawable start, @Nullable Drawable top,
269             @Nullable Drawable end, @Nullable Drawable bottom) {
270         super.setCompoundDrawablesRelative(start, top, end, bottom);
271         if (mTextHelper != null) {
272             mTextHelper.onSetCompoundDrawables();
273         }
274     }
275 
276     /**
277      * This should be accessed via
278      * {@link androidx.core.widget.TextViewCompat#getCompoundDrawableTintList(TextView)}
279      *
280      * @return the tint applied to the compound drawables
281      * @attr ref androidx.appcompat.R.styleable#AppCompatTextView_drawableTint
282      * @see #setSupportCompoundDrawablesTintList(ColorStateList)
283      *
284      */
285     @Override
286     @RestrictTo(LIBRARY_GROUP_PREFIX)
getSupportCompoundDrawablesTintList()287     public @Nullable ColorStateList getSupportCompoundDrawablesTintList() {
288         return mTextHelper.getCompoundDrawableTintList();
289     }
290 
291     /**
292      * This should be accessed via {@link
293      * androidx.core.widget.TextViewCompat#setCompoundDrawableTintList(TextView, ColorStateList)}
294      *
295      * Applies a tint to the compound drawables. Does not modify the current tint mode, which is
296      * {@link PorterDuff.Mode#SRC_IN} by default.
297      * <p>
298      * Subsequent calls to {@link #setCompoundDrawables(Drawable, Drawable, Drawable, Drawable)} and
299      * related methods will automatically mutate the drawables and apply the specified tint and tint
300      * mode using {@link Drawable#setTintList(ColorStateList)}.
301      *
302      * @param tintList the tint to apply, may be {@code null} to clear tint
303      * @attr ref androidx.appcompat.R.styleable#AppCompatTextView_drawableTint
304      * @see #getSupportCompoundDrawablesTintList()
305      *
306      */
307     @Override
308     @RestrictTo(LIBRARY_GROUP_PREFIX)
setSupportCompoundDrawablesTintList(@ullable ColorStateList tintList)309     public void setSupportCompoundDrawablesTintList(@Nullable ColorStateList tintList) {
310         mTextHelper.setCompoundDrawableTintList(tintList);
311         mTextHelper.applyCompoundDrawablesTints();
312     }
313 
314     /**
315      * This should be accessed via
316      * {@link androidx.core.widget.TextViewCompat#getCompoundDrawableTintMode(TextView)}
317      *
318      * Returns the blending mode used to apply the tint to the compound drawables, if specified.
319      *
320      * @return the blending mode used to apply the tint to the compound drawables
321      * @attr ref androidx.appcompat.R.styleable#AppCompatTextView_drawableTintMode
322      * @see #setSupportCompoundDrawablesTintMode(PorterDuff.Mode)
323      *
324      */
325     @Override
326     @RestrictTo(LIBRARY_GROUP_PREFIX)
getSupportCompoundDrawablesTintMode()327     public PorterDuff.@Nullable Mode getSupportCompoundDrawablesTintMode() {
328         return mTextHelper.getCompoundDrawableTintMode();
329     }
330 
331     /**
332      * This should be accessed via {@link
333      * androidx.core.widget.TextViewCompat#setCompoundDrawableTintMode(TextView, PorterDuff.Mode)}
334      *
335      * Specifies the blending mode used to apply the tint specified by
336      * {@link #setSupportCompoundDrawablesTintList(ColorStateList)} to the compound drawables. The
337      * default mode is {@link PorterDuff.Mode#SRC_IN}.
338      *
339      * @param tintMode the blending mode used to apply the tint, may be {@code null} to clear tint
340      * @attr ref androidx.appcompat.R.styleable#AppCompatTextView_drawableTintMode
341      * @see #setSupportCompoundDrawablesTintList(ColorStateList)
342      *
343      */
344     @Override
345     @RestrictTo(LIBRARY_GROUP_PREFIX)
setSupportCompoundDrawablesTintMode(PorterDuff.@ullable Mode tintMode)346     public void setSupportCompoundDrawablesTintMode(PorterDuff.@Nullable Mode tintMode) {
347         mTextHelper.setCompoundDrawableTintMode(tintMode);
348         mTextHelper.applyCompoundDrawablesTints();
349     }
350 }
351