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