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