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 static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP; 20 21import android.content.res.ColorStateList; 22import android.graphics.PorterDuff; 23import android.graphics.drawable.Drawable; 24import android.os.Build; 25import android.support.annotation.NonNull; 26import android.support.annotation.RestrictTo; 27import android.support.v4.widget.ImageViewCompat; 28import android.support.v7.appcompat.R; 29import android.support.v7.content.res.AppCompatResources; 30import android.util.AttributeSet; 31import android.widget.ImageView; 32 33/** 34 * @hide 35 */ 36@RestrictTo(LIBRARY_GROUP) 37public class AppCompatImageHelper { 38 private final ImageView mView; 39 40 private TintInfo mInternalImageTint; 41 private TintInfo mImageTint; 42 private TintInfo mTmpInfo; 43 44 public AppCompatImageHelper(ImageView view) { 45 mView = view; 46 } 47 48 public void loadFromAttributes(AttributeSet attrs, int defStyleAttr) { 49 TintTypedArray a = TintTypedArray.obtainStyledAttributes(mView.getContext(), attrs, 50 R.styleable.AppCompatImageView, defStyleAttr, 0); 51 try { 52 Drawable drawable = mView.getDrawable(); 53 if (drawable == null) { 54 // If the view doesn't already have a drawable (from android:src), try loading 55 // it from srcCompat 56 final int id = a.getResourceId(R.styleable.AppCompatImageView_srcCompat, -1); 57 if (id != -1) { 58 drawable = AppCompatResources.getDrawable(mView.getContext(), id); 59 if (drawable != null) { 60 mView.setImageDrawable(drawable); 61 } 62 } 63 } 64 65 if (drawable != null) { 66 DrawableUtils.fixDrawable(drawable); 67 } 68 69 if (a.hasValue(R.styleable.AppCompatImageView_tint)) { 70 ImageViewCompat.setImageTintList(mView, 71 a.getColorStateList(R.styleable.AppCompatImageView_tint)); 72 } 73 if (a.hasValue(R.styleable.AppCompatImageView_tintMode)) { 74 ImageViewCompat.setImageTintMode(mView, 75 DrawableUtils.parseTintMode( 76 a.getInt(R.styleable.AppCompatImageView_tintMode, -1), null)); 77 } 78 } finally { 79 a.recycle(); 80 } 81 } 82 83 public void setImageResource(int resId) { 84 if (resId != 0) { 85 final Drawable d = AppCompatResources.getDrawable(mView.getContext(), resId); 86 if (d != null) { 87 DrawableUtils.fixDrawable(d); 88 } 89 mView.setImageDrawable(d); 90 } else { 91 mView.setImageDrawable(null); 92 } 93 94 applySupportImageTint(); 95 } 96 97 boolean hasOverlappingRendering() { 98 final Drawable background = mView.getBackground(); 99 if (Build.VERSION.SDK_INT >= 21 100 && background instanceof android.graphics.drawable.RippleDrawable) { 101 // RippleDrawable has an issue on L+ when used with an alpha animation. 102 // This workaround should be disabled when the platform bug is fixed. See b/27715789 103 return false; 104 } 105 return true; 106 } 107 108 void setSupportImageTintList(ColorStateList tint) { 109 if (mImageTint == null) { 110 mImageTint = new TintInfo(); 111 } 112 mImageTint.mTintList = tint; 113 mImageTint.mHasTintList = true; 114 applySupportImageTint(); 115 } 116 117 ColorStateList getSupportImageTintList() { 118 return mImageTint != null ? mImageTint.mTintList : null; 119 } 120 121 void setSupportImageTintMode(PorterDuff.Mode tintMode) { 122 if (mImageTint == null) { 123 mImageTint = new TintInfo(); 124 } 125 mImageTint.mTintMode = tintMode; 126 mImageTint.mHasTintMode = true; 127 128 applySupportImageTint(); 129 } 130 131 PorterDuff.Mode getSupportImageTintMode() { 132 return mImageTint != null ? mImageTint.mTintMode : null; 133 } 134 135 void applySupportImageTint() { 136 final Drawable imageViewDrawable = mView.getDrawable(); 137 if (imageViewDrawable != null) { 138 DrawableUtils.fixDrawable(imageViewDrawable); 139 } 140 141 if (imageViewDrawable != null) { 142 if (shouldApplyFrameworkTintUsingColorFilter() 143 && applyFrameworkTintUsingColorFilter(imageViewDrawable)) { 144 // This needs to be called before the internal tints below so it takes 145 // effect on any widgets using the compat tint on API 21 146 return; 147 } 148 149 if (mImageTint != null) { 150 AppCompatDrawableManager.tintDrawable(imageViewDrawable, mImageTint, 151 mView.getDrawableState()); 152 } else if (mInternalImageTint != null) { 153 AppCompatDrawableManager.tintDrawable(imageViewDrawable, mInternalImageTint, 154 mView.getDrawableState()); 155 } 156 } 157 } 158 159 void setInternalImageTint(ColorStateList tint) { 160 if (tint != null) { 161 if (mInternalImageTint == null) { 162 mInternalImageTint = new TintInfo(); 163 } 164 mInternalImageTint.mTintList = tint; 165 mInternalImageTint.mHasTintList = true; 166 } else { 167 mInternalImageTint = null; 168 } 169 applySupportImageTint(); 170 } 171 172 private boolean shouldApplyFrameworkTintUsingColorFilter() { 173 final int sdk = Build.VERSION.SDK_INT; 174 if (sdk > 21) { 175 // On API 22+, if we're using an internal compat image source tint, we're also 176 // responsible for applying any custom tint set via the framework impl 177 return mInternalImageTint != null; 178 } else if (sdk == 21) { 179 // GradientDrawable doesn't implement setTintList on API 21, and since there is 180 // no nice way to unwrap DrawableContainers we have to blanket apply this 181 // on API 21 182 return true; 183 } else { 184 // API 19 and below doesn't have framework tint 185 return false; 186 } 187 } 188 189 /** 190 * Applies the framework image source 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 imageSource) { 195 if (mTmpInfo == null) { 196 mTmpInfo = new TintInfo(); 197 } 198 final TintInfo info = mTmpInfo; 199 info.clear(); 200 201 final ColorStateList tintList = ImageViewCompat.getImageTintList(mView); 202 if (tintList != null) { 203 info.mHasTintList = true; 204 info.mTintList = tintList; 205 } 206 final PorterDuff.Mode mode = ImageViewCompat.getImageTintMode(mView); 207 if (mode != null) { 208 info.mHasTintMode = true; 209 info.mTintMode = mode; 210 } 211 212 if (info.mHasTintList || info.mHasTintMode) { 213 AppCompatDrawableManager.tintDrawable(imageSource, info, mView.getDrawableState()); 214 return true; 215 } 216 217 return false; 218 } 219} 220