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