1 /*
2  * Copyright 2018 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.widget.TextView;
28 import android.widget.ToggleButton;
29 
30 import androidx.annotation.DrawableRes;
31 import androidx.annotation.RestrictTo;
32 import androidx.core.view.TintableBackgroundView;
33 import androidx.core.view.ViewCompat;
34 import androidx.core.widget.TintableCompoundDrawablesView;
35 import androidx.resourceinspection.annotation.AppCompatShadowedAttributes;
36 
37 import org.jspecify.annotations.NonNull;
38 import org.jspecify.annotations.Nullable;
39 
40 /**
41  * A {@link ToggleButton} 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
47  *     {@link androidx.appcompat.R.attr#backgroundTint} and
48  *     {@link androidx.appcompat.R.attr#backgroundTintMode}.</li>
49  *     <li>Allows setting of the font family using {@link android.R.attr#fontFamily}</li>
50  * </ul>
51  *
52  * <p>This will automatically be used when you use {@link ToggleButton} in your layouts.
53  * You should only need to manually use this class when writing custom views.</p>
54  */
55 @AppCompatShadowedAttributes
56 public class AppCompatToggleButton extends ToggleButton implements TintableBackgroundView,
57         EmojiCompatConfigurationView, TintableCompoundDrawablesView {
58 
59     private final AppCompatBackgroundHelper mBackgroundTintHelper;
60     private final AppCompatTextHelper mTextHelper;
61     private AppCompatEmojiTextHelper mAppCompatEmojiTextHelper;
62 
AppCompatToggleButton(@onNull Context context)63     public AppCompatToggleButton(@NonNull Context context) {
64         this(context, null);
65     }
66 
AppCompatToggleButton(@onNull Context context, @Nullable AttributeSet attrs)67     public AppCompatToggleButton(@NonNull Context context, @Nullable AttributeSet attrs) {
68         this(context, attrs, android.R.attr.buttonStyleToggle);
69     }
70 
AppCompatToggleButton( @onNull Context context, @Nullable AttributeSet attrs, int defStyleAttr)71     public AppCompatToggleButton(
72             @NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
73         super(context, attrs, defStyleAttr);
74 
75         ThemeUtils.checkAppCompatTheme(this, getContext());
76 
77         mBackgroundTintHelper = new AppCompatBackgroundHelper(this);
78         mBackgroundTintHelper.loadFromAttributes(attrs, defStyleAttr);
79 
80         mTextHelper = new AppCompatTextHelper(this);
81         mTextHelper.loadFromAttributes(attrs, defStyleAttr);
82 
83         AppCompatEmojiTextHelper emojiTextViewHelper = getEmojiTextViewHelper();
84         emojiTextViewHelper.loadFromAttributes(attrs, defStyleAttr);
85     }
86 
87     @Override
setBackgroundResource(@rawableRes int resId)88     public void setBackgroundResource(@DrawableRes int resId) {
89         super.setBackgroundResource(resId);
90         if (mBackgroundTintHelper != null) {
91             mBackgroundTintHelper.onSetBackgroundResource(resId);
92         }
93     }
94 
95     @Override
setBackgroundDrawable(@ullable Drawable background)96     public void setBackgroundDrawable(@Nullable Drawable background) {
97         super.setBackgroundDrawable(background);
98         if (mBackgroundTintHelper != null) {
99             mBackgroundTintHelper.onSetBackgroundDrawable(background);
100         }
101     }
102 
103     /**
104      * This should be accessed via
105      * {@link ViewCompat#setBackgroundTintList(android.view.View, ColorStateList)}
106      *
107      */
108     @RestrictTo(LIBRARY_GROUP_PREFIX)
109     @Override
setSupportBackgroundTintList(@ullable ColorStateList tint)110     public void setSupportBackgroundTintList(@Nullable ColorStateList tint) {
111         if (mBackgroundTintHelper != null) {
112             mBackgroundTintHelper.setSupportBackgroundTintList(tint);
113         }
114     }
115 
116     /**
117      * This should be accessed via
118      * {@link ViewCompat#getBackgroundTintList(android.view.View)}
119      *
120      */
121     @RestrictTo(LIBRARY_GROUP_PREFIX)
122     @Override
getSupportBackgroundTintList()123     public @Nullable ColorStateList getSupportBackgroundTintList() {
124         return mBackgroundTintHelper != null
125                 ? mBackgroundTintHelper.getSupportBackgroundTintList() : null;
126     }
127 
128     /**
129      * This should be accessed via
130      * {@link ViewCompat#setBackgroundTintMode(android.view.View, PorterDuff.Mode)}
131      *
132      */
133     @RestrictTo(LIBRARY_GROUP_PREFIX)
134     @Override
setSupportBackgroundTintMode(PorterDuff.@ullable Mode tintMode)135     public void setSupportBackgroundTintMode(PorterDuff.@Nullable Mode tintMode) {
136         if (mBackgroundTintHelper != null) {
137             mBackgroundTintHelper.setSupportBackgroundTintMode(tintMode);
138         }
139     }
140 
141     /**
142      * This should be accessed via
143      * {@link ViewCompat#getBackgroundTintMode(android.view.View)}
144      *
145      */
146     @RestrictTo(LIBRARY_GROUP_PREFIX)
147     @Override
getSupportBackgroundTintMode()148     public PorterDuff.@Nullable Mode getSupportBackgroundTintMode() {
149         return mBackgroundTintHelper != null
150                 ? mBackgroundTintHelper.getSupportBackgroundTintMode() : null;
151     }
152 
153     @Override
drawableStateChanged()154     protected void drawableStateChanged() {
155         super.drawableStateChanged();
156         if (mBackgroundTintHelper != null) {
157             mBackgroundTintHelper.applySupportBackgroundTint();
158         }
159         if (mTextHelper != null) {
160             mTextHelper.applyCompoundDrawablesTints();
161         }
162     }
163 
164     @Override
setFilters(@uppressWarnings"ArrayReturn") InputFilter @onNull [] filters)165     public void setFilters(@SuppressWarnings("ArrayReturn") InputFilter @NonNull [] filters) {
166         super.setFilters(getEmojiTextViewHelper().getFilters(filters));
167     }
168 
169 
170     /**
171      * This may be called from super constructors.
172      */
getEmojiTextViewHelper()173     private @NonNull AppCompatEmojiTextHelper getEmojiTextViewHelper() {
174         //noinspection ConstantConditions
175         if (mAppCompatEmojiTextHelper == null) {
176             mAppCompatEmojiTextHelper = new AppCompatEmojiTextHelper(this);
177         }
178         return mAppCompatEmojiTextHelper;
179     }
180 
181     @Override
setAllCaps(boolean allCaps)182     public void setAllCaps(boolean allCaps) {
183         super.setAllCaps(allCaps);
184         getEmojiTextViewHelper().setAllCaps(allCaps);
185     }
186 
187     @Override
setEmojiCompatEnabled(boolean enabled)188     public void setEmojiCompatEnabled(boolean enabled) {
189         getEmojiTextViewHelper().setEnabled(enabled);
190     }
191 
192     @Override
isEmojiCompatEnabled()193     public boolean isEmojiCompatEnabled() {
194         return getEmojiTextViewHelper().isEnabled();
195     }
196 
197     @Override
setCompoundDrawables(@ullable Drawable left, @Nullable Drawable top, @Nullable Drawable right, @Nullable Drawable bottom)198     public void setCompoundDrawables(@Nullable Drawable left, @Nullable Drawable top,
199             @Nullable Drawable right, @Nullable Drawable bottom) {
200         super.setCompoundDrawables(left, top, right, bottom);
201         if (mTextHelper != null) {
202             mTextHelper.onSetCompoundDrawables();
203         }
204     }
205 
206     @Override
setCompoundDrawablesRelative(@ullable Drawable start, @Nullable Drawable top, @Nullable Drawable end, @Nullable Drawable bottom)207     public void setCompoundDrawablesRelative(@Nullable Drawable start, @Nullable Drawable top,
208             @Nullable Drawable end, @Nullable Drawable bottom) {
209         super.setCompoundDrawablesRelative(start, top, end, bottom);
210         if (mTextHelper != null) {
211             mTextHelper.onSetCompoundDrawables();
212         }
213     }
214 
215     /**
216      * This should be accessed via
217      * {@link androidx.core.widget.TextViewCompat#getCompoundDrawableTintList(TextView)}
218      *
219      * @return the tint applied to the compound drawables
220      * @attr ref androidx.appcompat.R.styleable#AppCompatTextView_drawableTint
221      * @see #setSupportCompoundDrawablesTintList(ColorStateList)
222      *
223      */
224     @Override
225     @RestrictTo(LIBRARY_GROUP_PREFIX)
getSupportCompoundDrawablesTintList()226     public @Nullable ColorStateList getSupportCompoundDrawablesTintList() {
227         return mTextHelper.getCompoundDrawableTintList();
228     }
229 
230     /**
231      * This should be accessed via {@link
232      * androidx.core.widget.TextViewCompat#setCompoundDrawableTintList(TextView, ColorStateList)}
233      *
234      * Applies a tint to the compound drawables. Does not modify the current tint mode, which is
235      * {@link PorterDuff.Mode#SRC_IN} by default.
236      * <p>
237      * Subsequent calls to {@link #setCompoundDrawables(Drawable, Drawable, Drawable, Drawable)} and
238      * related methods will automatically mutate the drawables and apply the specified tint and tint
239      * mode using {@link Drawable#setTintList(ColorStateList)}.
240      *
241      * @param tintList the tint to apply, may be {@code null} to clear tint
242      * @attr ref androidx.appcompat.R.styleable#AppCompatTextView_drawableTint
243      * @see #getSupportCompoundDrawablesTintList()
244      *
245      */
246     @Override
247     @RestrictTo(LIBRARY_GROUP_PREFIX)
setSupportCompoundDrawablesTintList(@ullable ColorStateList tintList)248     public void setSupportCompoundDrawablesTintList(@Nullable ColorStateList tintList) {
249         mTextHelper.setCompoundDrawableTintList(tintList);
250         mTextHelper.applyCompoundDrawablesTints();
251     }
252 
253     /**
254      * This should be accessed via
255      * {@link androidx.core.widget.TextViewCompat#getCompoundDrawableTintMode(TextView)}
256      *
257      * Returns the blending mode used to apply the tint to the compound drawables, if specified.
258      *
259      * @return the blending mode used to apply the tint to the compound drawables
260      * @attr ref androidx.appcompat.R.styleable#AppCompatTextView_drawableTintMode
261      * @see #setSupportCompoundDrawablesTintMode(PorterDuff.Mode)
262      *
263      */
264     @Override
265     @RestrictTo(LIBRARY_GROUP_PREFIX)
getSupportCompoundDrawablesTintMode()266     public PorterDuff.@Nullable Mode getSupportCompoundDrawablesTintMode() {
267         return mTextHelper.getCompoundDrawableTintMode();
268     }
269 
270     /**
271      * This should be accessed via {@link
272      * androidx.core.widget.TextViewCompat#setCompoundDrawableTintMode(TextView, PorterDuff.Mode)}
273      *
274      * Specifies the blending mode used to apply the tint specified by
275      * {@link #setSupportCompoundDrawablesTintList(ColorStateList)} to the compound drawables. The
276      * default mode is {@link PorterDuff.Mode#SRC_IN}.
277      *
278      * @param tintMode the blending mode used to apply the tint, may be {@code null} to clear tint
279      * @attr ref androidx.appcompat.R.styleable#AppCompatTextView_drawableTintMode
280      * @see #setSupportCompoundDrawablesTintList(ColorStateList)
281      *
282      */
283     @Override
284     @RestrictTo(LIBRARY_GROUP_PREFIX)
setSupportCompoundDrawablesTintMode(PorterDuff.@ullable Mode tintMode)285     public void setSupportCompoundDrawablesTintMode(PorterDuff.@Nullable Mode tintMode) {
286         mTextHelper.setCompoundDrawableTintMode(tintMode);
287         mTextHelper.applyCompoundDrawablesTints();
288     }
289 }
290