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 TintInfo mInternalBackgroundTint; 37 private TintInfo mBackgroundTint; 38 private TintInfo 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 applySupportBackgroundTint(); 80 } 81 82 void onSetBackgroundDrawable(Drawable background) { 83 mBackgroundResId = -1; 84 // We don't know that this drawable is, so we need to clear the default background tint 85 setInternalBackgroundTint(null); 86 applySupportBackgroundTint(); 87 } 88 89 void setSupportBackgroundTintList(ColorStateList tint) { 90 if (mBackgroundTint == null) { 91 mBackgroundTint = new TintInfo(); 92 } 93 mBackgroundTint.mTintList = tint; 94 mBackgroundTint.mHasTintList = true; 95 applySupportBackgroundTint(); 96 } 97 98 ColorStateList getSupportBackgroundTintList() { 99 return mBackgroundTint != null ? mBackgroundTint.mTintList : null; 100 } 101 102 void setSupportBackgroundTintMode(PorterDuff.Mode tintMode) { 103 if (mBackgroundTint == null) { 104 mBackgroundTint = new TintInfo(); 105 } 106 mBackgroundTint.mTintMode = tintMode; 107 mBackgroundTint.mHasTintMode = true; 108 109 applySupportBackgroundTint(); 110 } 111 112 PorterDuff.Mode getSupportBackgroundTintMode() { 113 return mBackgroundTint != null ? mBackgroundTint.mTintMode : null; 114 } 115 116 void applySupportBackgroundTint() { 117 final Drawable background = mView.getBackground(); 118 if (background != null) { 119 if (shouldApplyFrameworkTintUsingColorFilter() 120 && applyFrameworkTintUsingColorFilter(background)) { 121 // This needs to be called before the internal tints below so it takes 122 // effect on any widgets using the compat tint on API 21 (EditText) 123 return; 124 } 125 126 if (mBackgroundTint != null) { 127 AppCompatDrawableManager.tintDrawable(background, mBackgroundTint, 128 mView.getDrawableState()); 129 } else if (mInternalBackgroundTint != null) { 130 AppCompatDrawableManager.tintDrawable(background, mInternalBackgroundTint, 131 mView.getDrawableState()); 132 } 133 } 134 } 135 136 void setInternalBackgroundTint(ColorStateList tint) { 137 if (tint != null) { 138 if (mInternalBackgroundTint == null) { 139 mInternalBackgroundTint = new TintInfo(); 140 } 141 mInternalBackgroundTint.mTintList = tint; 142 mInternalBackgroundTint.mHasTintList = true; 143 } else { 144 mInternalBackgroundTint = null; 145 } 146 applySupportBackgroundTint(); 147 } 148 149 private boolean shouldApplyFrameworkTintUsingColorFilter() { 150 final int sdk = Build.VERSION.SDK_INT; 151 if (sdk > 21) { 152 // On API 22+, if we're using an internal compat background tint, we're also 153 // responsible for applying any custom tint set via the framework impl 154 return mInternalBackgroundTint != null; 155 } else if (sdk == 21) { 156 // GradientDrawable doesn't implement setTintList on API 21, and since there is 157 // no nice way to unwrap DrawableContainers we have to blanket apply this 158 // on API 21 159 return true; 160 } else { 161 // API 19 and below doesn't have framework tint 162 return false; 163 } 164 } 165 166 /** 167 * Applies the framework background tint to a view, but using the compat method (ColorFilter) 168 * 169 * @return true if a tint was applied 170 */ 171 private boolean applyFrameworkTintUsingColorFilter(@NonNull Drawable background) { 172 if (mTmpInfo == null) { 173 mTmpInfo = new TintInfo(); 174 } 175 final TintInfo info = mTmpInfo; 176 info.clear(); 177 178 final ColorStateList tintList = ViewCompat.getBackgroundTintList(mView); 179 if (tintList != null) { 180 info.mHasTintList = true; 181 info.mTintList = tintList; 182 } 183 final PorterDuff.Mode mode = ViewCompat.getBackgroundTintMode(mView); 184 if (mode != null) { 185 info.mHasTintMode = true; 186 info.mTintMode = mode; 187 } 188 189 if (info.mHasTintList || info.mHasTintMode) { 190 AppCompatDrawableManager.tintDrawable(background, info, mView.getDrawableState()); 191 return true; 192 } 193 194 return false; 195 } 196} 197