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