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