1 /* 2 * Copyright (C) 2015 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 android.content.res.ColorStateList; 20 import android.graphics.PorterDuff; 21 import android.graphics.drawable.Drawable; 22 import android.os.Build; 23 import android.util.AttributeSet; 24 import android.view.View; 25 26 import androidx.appcompat.R; 27 import androidx.core.view.ViewCompat; 28 29 import org.jspecify.annotations.NonNull; 30 import org.jspecify.annotations.Nullable; 31 32 class AppCompatBackgroundHelper { 33 34 private final @NonNull View mView; 35 private final AppCompatDrawableManager mDrawableManager; 36 37 private int mBackgroundResId = -1; 38 39 private TintInfo mInternalBackgroundTint; 40 private TintInfo mBackgroundTint; 41 private TintInfo mTmpInfo; 42 AppCompatBackgroundHelper(@onNull View view)43 AppCompatBackgroundHelper(@NonNull View view) { 44 mView = view; 45 mDrawableManager = AppCompatDrawableManager.get(); 46 } 47 loadFromAttributes(@ullable AttributeSet attrs, int defStyleAttr)48 void loadFromAttributes(@Nullable AttributeSet attrs, int defStyleAttr) { 49 TintTypedArray a = TintTypedArray.obtainStyledAttributes(mView.getContext(), attrs, 50 R.styleable.ViewBackgroundHelper, defStyleAttr, 0); 51 ViewCompat.saveAttributeDataForStyleable(mView, mView.getContext(), 52 R.styleable.ViewBackgroundHelper, attrs, a.getWrappedTypeArray(), 53 defStyleAttr, 0); 54 try { 55 if (a.hasValue(R.styleable.ViewBackgroundHelper_android_background)) { 56 mBackgroundResId = a.getResourceId( 57 R.styleable.ViewBackgroundHelper_android_background, -1); 58 ColorStateList tint = mDrawableManager 59 .getTintList(mView.getContext(), mBackgroundResId); 60 if (tint != null) { 61 setInternalBackgroundTint(tint); 62 } 63 } 64 if (a.hasValue(R.styleable.ViewBackgroundHelper_backgroundTint)) { 65 ViewCompat.setBackgroundTintList(mView, 66 a.getColorStateList(R.styleable.ViewBackgroundHelper_backgroundTint)); 67 } 68 if (a.hasValue(R.styleable.ViewBackgroundHelper_backgroundTintMode)) { 69 ViewCompat.setBackgroundTintMode(mView, 70 DrawableUtils.parseTintMode( 71 a.getInt(R.styleable.ViewBackgroundHelper_backgroundTintMode, -1), 72 null)); 73 } 74 } finally { 75 a.recycle(); 76 } 77 } 78 onSetBackgroundResource(int resId)79 void onSetBackgroundResource(int resId) { 80 mBackgroundResId = resId; 81 // Update the default background tint 82 setInternalBackgroundTint(mDrawableManager != null 83 ? mDrawableManager.getTintList(mView.getContext(), resId) 84 : null); 85 applySupportBackgroundTint(); 86 } 87 onSetBackgroundDrawable(Drawable background)88 void onSetBackgroundDrawable(Drawable background) { 89 mBackgroundResId = -1; 90 // We don't know that this drawable is, so we need to clear the default background tint 91 setInternalBackgroundTint(null); 92 applySupportBackgroundTint(); 93 } 94 setSupportBackgroundTintList(ColorStateList tint)95 void setSupportBackgroundTintList(ColorStateList tint) { 96 if (mBackgroundTint == null) { 97 mBackgroundTint = new TintInfo(); 98 } 99 mBackgroundTint.mTintList = tint; 100 mBackgroundTint.mHasTintList = true; 101 applySupportBackgroundTint(); 102 } 103 getSupportBackgroundTintList()104 ColorStateList getSupportBackgroundTintList() { 105 return mBackgroundTint != null ? mBackgroundTint.mTintList : null; 106 } 107 setSupportBackgroundTintMode(PorterDuff.Mode tintMode)108 void setSupportBackgroundTintMode(PorterDuff.Mode tintMode) { 109 if (mBackgroundTint == null) { 110 mBackgroundTint = new TintInfo(); 111 } 112 mBackgroundTint.mTintMode = tintMode; 113 mBackgroundTint.mHasTintMode = true; 114 115 applySupportBackgroundTint(); 116 } 117 getSupportBackgroundTintMode()118 PorterDuff.Mode getSupportBackgroundTintMode() { 119 return mBackgroundTint != null ? mBackgroundTint.mTintMode : null; 120 } 121 applySupportBackgroundTint()122 void applySupportBackgroundTint() { 123 final Drawable background = mView.getBackground(); 124 if (background != null) { 125 if (shouldApplyFrameworkTintUsingColorFilter() 126 && applyFrameworkTintUsingColorFilter(background)) { 127 // This needs to be called before the internal tints below so it takes 128 // effect on any widgets using the compat tint on API 21 (EditText) 129 return; 130 } 131 132 if (mBackgroundTint != null) { 133 AppCompatDrawableManager.tintDrawable(background, mBackgroundTint, 134 mView.getDrawableState()); 135 } else if (mInternalBackgroundTint != null) { 136 AppCompatDrawableManager.tintDrawable(background, mInternalBackgroundTint, 137 mView.getDrawableState()); 138 } 139 } 140 } 141 setInternalBackgroundTint(ColorStateList tint)142 void setInternalBackgroundTint(ColorStateList tint) { 143 if (tint != null) { 144 if (mInternalBackgroundTint == null) { 145 mInternalBackgroundTint = new TintInfo(); 146 } 147 mInternalBackgroundTint.mTintList = tint; 148 mInternalBackgroundTint.mHasTintList = true; 149 } else { 150 mInternalBackgroundTint = null; 151 } 152 applySupportBackgroundTint(); 153 } 154 shouldApplyFrameworkTintUsingColorFilter()155 private boolean shouldApplyFrameworkTintUsingColorFilter() { 156 final int sdk = Build.VERSION.SDK_INT; 157 if (sdk > 21) { 158 // On API 22+, if we're using an internal compat background tint, we're also 159 // responsible for applying any custom tint set via the framework impl 160 return mInternalBackgroundTint != null; 161 } else if (sdk == 21) { 162 // GradientDrawable doesn't implement setTintList on API 21, and since there is 163 // no nice way to unwrap DrawableContainers we have to blanket apply this 164 // on API 21 165 return true; 166 } else { 167 // API 19 and below doesn't have framework tint 168 return false; 169 } 170 } 171 172 /** 173 * Applies the framework background tint to a view, but using the compat method (ColorFilter) 174 * 175 * @return true if a tint was applied 176 */ applyFrameworkTintUsingColorFilter(@onNull Drawable background)177 private boolean applyFrameworkTintUsingColorFilter(@NonNull Drawable background) { 178 if (mTmpInfo == null) { 179 mTmpInfo = new TintInfo(); 180 } 181 final TintInfo info = mTmpInfo; 182 info.clear(); 183 184 final ColorStateList tintList = ViewCompat.getBackgroundTintList(mView); 185 if (tintList != null) { 186 info.mHasTintList = true; 187 info.mTintList = tintList; 188 } 189 final PorterDuff.Mode mode = ViewCompat.getBackgroundTintMode(mView); 190 if (mode != null) { 191 info.mHasTintMode = true; 192 info.mTintMode = mode; 193 } 194 195 if (info.mHasTintList || info.mHasTintMode) { 196 AppCompatDrawableManager.tintDrawable(background, info, mView.getDrawableState()); 197 return true; 198 } 199 200 return false; 201 } 202 } 203