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 17package android.support.v7.widget; 18 19import android.content.res.ColorStateList; 20import android.graphics.PorterDuff; 21import android.graphics.drawable.Drawable; 22import android.os.Build; 23import android.support.annotation.NonNull; 24import android.support.v4.view.ViewCompat; 25import android.support.v7.appcompat.R; 26import android.util.AttributeSet; 27import android.view.View; 28 29class AppCompatBackgroundHelper { 30 31 private final View mView; 32 private final AppCompatDrawableManager mDrawableManager; 33 34 private int mBackgroundResId = -1; 35 36 private BackgroundTintInfo mInternalBackgroundTint; 37 private BackgroundTintInfo mBackgroundTint; 38 private BackgroundTintInfo mTmpInfo; 39 40 AppCompatBackgroundHelper(View view) { 41 mView = view; 42 mDrawableManager = AppCompatDrawableManager.get(); 43 } 44 45 void loadFromAttributes(AttributeSet attrs, int defStyleAttr) { 46 TintTypedArray a = TintTypedArray.obtainStyledAttributes(mView.getContext(), attrs, 47 R.styleable.ViewBackgroundHelper, defStyleAttr, 0); 48 try { 49 if (a.hasValue(R.styleable.ViewBackgroundHelper_android_background)) { 50 mBackgroundResId = a.getResourceId( 51 R.styleable.ViewBackgroundHelper_android_background, -1); 52 ColorStateList tint = mDrawableManager 53 .getTintList(mView.getContext(), mBackgroundResId); 54 if (tint != null) { 55 setInternalBackgroundTint(tint); 56 } 57 } 58 if (a.hasValue(R.styleable.ViewBackgroundHelper_backgroundTint)) { 59 ViewCompat.setBackgroundTintList(mView, 60 a.getColorStateList(R.styleable.ViewBackgroundHelper_backgroundTint)); 61 } 62 if (a.hasValue(R.styleable.ViewBackgroundHelper_backgroundTintMode)) { 63 ViewCompat.setBackgroundTintMode(mView, 64 DrawableUtils.parseTintMode( 65 a.getInt(R.styleable.ViewBackgroundHelper_backgroundTintMode, -1), 66 null)); 67 } 68 } finally { 69 a.recycle(); 70 } 71 } 72 73 void onSetBackgroundResource(int resId) { 74 mBackgroundResId = resId; 75 // Update the default background tint 76 setInternalBackgroundTint(mDrawableManager != null 77 ? mDrawableManager.getTintList(mView.getContext(), resId) 78 : null); 79 80 if (updateBackgroundTint()) { 81 applySupportBackgroundTint(); 82 } 83 } 84 85 void onSetBackgroundDrawable(Drawable background) { 86 mBackgroundResId = -1; 87 // We don't know that this drawable is, so we need to clear the default background tint 88 setInternalBackgroundTint(null); 89 90 if (updateBackgroundTint()) { 91 applySupportBackgroundTint(); 92 } 93 } 94 95 void setSupportBackgroundTintList(ColorStateList tint) { 96 if (mBackgroundTint == null) { 97 mBackgroundTint = new BackgroundTintInfo(); 98 } 99 100 // Store the original tint and null out the applicable tint. updateBackgroundTint() will 101 // set mTintList to the tint to actually use 102 mBackgroundTint.mOriginalTintList = tint; 103 mBackgroundTint.mTintList = null; 104 mBackgroundTint.mHasTintList = true; 105 106 if (updateBackgroundTint()) { 107 applySupportBackgroundTint(); 108 } 109 } 110 111 /** 112 * Updates the background tint state 113 * @return true if the state was changed and requires an apply 114 */ 115 private boolean updateBackgroundTint() { 116 if (mBackgroundTint != null && mBackgroundTint.mHasTintList) { 117 if (mBackgroundResId >= 0) { 118 // If we have a background resource id, lets see if we need to modify the tint 119 // list to add any touch highlights in (for example, Button needs this) 120 final ColorStateList updated = mDrawableManager.getTintList( 121 mView.getContext(), mBackgroundResId, mBackgroundTint.mOriginalTintList); 122 if (updated != null) { 123 mBackgroundTint.mTintList = updated; 124 return true; 125 } 126 } 127 // If we reach here then we should just be using the original tint list. Check if we 128 // need to set and apply 129 if (mBackgroundTint.mTintList != mBackgroundTint.mOriginalTintList) { 130 mBackgroundTint.mTintList = mBackgroundTint.mOriginalTintList; 131 return true; 132 } 133 } 134 return false; 135 } 136 137 ColorStateList getSupportBackgroundTintList() { 138 return mBackgroundTint != null ? mBackgroundTint.mTintList : null; 139 } 140 141 void setSupportBackgroundTintMode(PorterDuff.Mode tintMode) { 142 if (mBackgroundTint == null) { 143 mBackgroundTint = new BackgroundTintInfo(); 144 } 145 mBackgroundTint.mTintMode = tintMode; 146 mBackgroundTint.mHasTintMode = true; 147 148 applySupportBackgroundTint(); 149 } 150 151 PorterDuff.Mode getSupportBackgroundTintMode() { 152 return mBackgroundTint != null ? mBackgroundTint.mTintMode : null; 153 } 154 155 void applySupportBackgroundTint() { 156 final Drawable background = mView.getBackground(); 157 if (background != null) { 158 if (Build.VERSION.SDK_INT == 21 && applyFrameworkTintUsingColorFilter(background)) { 159 // GradientDrawable doesn't implement setTintList on API 21, and since there is 160 // no nice way to unwrap DrawableContainers we have to blanket apply this 161 // on API 21. This needs to be called before the internal tints below so it takes 162 // effect on any widgets using the compat tint on API 21 (EditText) 163 return; 164 } 165 166 if (mBackgroundTint != null) { 167 AppCompatDrawableManager.tintDrawable(background, mBackgroundTint, 168 mView.getDrawableState()); 169 } else if (mInternalBackgroundTint != null) { 170 AppCompatDrawableManager.tintDrawable(background, mInternalBackgroundTint, 171 mView.getDrawableState()); 172 } 173 } 174 } 175 176 void setInternalBackgroundTint(ColorStateList tint) { 177 if (tint != null) { 178 if (mInternalBackgroundTint == null) { 179 mInternalBackgroundTint = new BackgroundTintInfo(); 180 } 181 mInternalBackgroundTint.mTintList = tint; 182 mInternalBackgroundTint.mHasTintList = true; 183 } else { 184 mInternalBackgroundTint = null; 185 } 186 applySupportBackgroundTint(); 187 } 188 189 /** 190 * Applies the framework background tint to a view, but using the compat method (ColorFilter) 191 * 192 * @return true if a tint was applied 193 */ 194 private boolean applyFrameworkTintUsingColorFilter(@NonNull Drawable background) { 195 if (mTmpInfo == null) { 196 mTmpInfo = new BackgroundTintInfo(); 197 } 198 final TintInfo info = mTmpInfo; 199 info.clear(); 200 201 final ColorStateList tintList = ViewCompat.getBackgroundTintList(mView); 202 if (tintList != null) { 203 info.mHasTintList = true; 204 info.mTintList = tintList; 205 } 206 final PorterDuff.Mode mode = ViewCompat.getBackgroundTintMode(mView); 207 if (mode != null) { 208 info.mHasTintMode = true; 209 info.mTintMode = mode; 210 } 211 212 if (info.mHasTintList || info.mHasTintMode) { 213 AppCompatDrawableManager.tintDrawable(background, info, mView.getDrawableState()); 214 return true; 215 } 216 217 return false; 218 } 219 220 private static class BackgroundTintInfo extends TintInfo { 221 // The original tint list given to the call. We need this distinction because create a 222 // modified for actual tinting purposes 223 public ColorStateList mOriginalTintList; 224 225 BackgroundTintInfo() { 226 } 227 228 @Override 229 void clear() { 230 super.clear(); 231 mOriginalTintList = null; 232 } 233 } 234} 235