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