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_PREFIX;
20 
21 import android.content.Context;
22 import android.content.res.ColorStateList;
23 import android.graphics.Bitmap;
24 import android.graphics.PorterDuff;
25 import android.graphics.drawable.Drawable;
26 import android.net.Uri;
27 import android.util.AttributeSet;
28 import android.widget.ImageView;
29 
30 import androidx.annotation.DrawableRes;
31 import androidx.annotation.RestrictTo;
32 import androidx.core.view.TintableBackgroundView;
33 import androidx.core.widget.ImageViewCompat;
34 import androidx.core.widget.TintableImageSourceView;
35 import androidx.resourceinspection.annotation.AppCompatShadowedAttributes;
36 
37 import org.jspecify.annotations.NonNull;
38 import org.jspecify.annotations.Nullable;
39 
40 /**
41  * A {@link ImageView} which supports compatible features on older versions of the platform,
42  * including:
43  * <ul>
44  *     <li>Allows dynamic tint of its background via the background tint methods in
45  *     {@link androidx.core.view.ViewCompat}.</li>
46  *     <li>Allows setting of the background tint using
47  *     {@link androidx.appcompat.R.attr#backgroundTint} and
48  *     {@link androidx.appcompat.R.attr#backgroundTintMode}.</li>
49  *     <li>Allows dynamic tint of its image via the image tint methods in
50  *     {@link ImageViewCompat}.</li>
51  *     <li>Allows setting of the image tint using {@link androidx.appcompat.R.attr#tint} and
52  *     {@link androidx.appcompat.R.attr#tintMode}.</li>
53  * </ul>
54  *
55  * <p>This will automatically be used when you use {@link ImageView} in your layouts
56  * and the top-level activity / dialog is provided by
57  * <a href="{@docRoot}topic/libraries/support-library/packages.html#v7-appcompat">appcompat</a>.
58  * You should only need to manually use this class when writing custom views.</p>
59  */
60 @AppCompatShadowedAttributes
61 public class AppCompatImageView extends ImageView implements TintableBackgroundView,
62         TintableImageSourceView {
63 
64     private final AppCompatBackgroundHelper mBackgroundTintHelper;
65     private final AppCompatImageHelper mImageHelper;
66 
67     private boolean mHasLevel = false;
68 
AppCompatImageView(@onNull Context context)69     public AppCompatImageView(@NonNull Context context) {
70         this(context, null);
71     }
72 
AppCompatImageView(@onNull Context context, @Nullable AttributeSet attrs)73     public AppCompatImageView(@NonNull Context context, @Nullable AttributeSet attrs) {
74         this(context, attrs, 0);
75     }
76 
AppCompatImageView( @onNull Context context, @Nullable AttributeSet attrs, int defStyleAttr)77     public AppCompatImageView(
78             @NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
79         super(TintContextWrapper.wrap(context), attrs, defStyleAttr);
80 
81         ThemeUtils.checkAppCompatTheme(this, getContext());
82 
83         mBackgroundTintHelper = new AppCompatBackgroundHelper(this);
84         mBackgroundTintHelper.loadFromAttributes(attrs, defStyleAttr);
85 
86         mImageHelper = new AppCompatImageHelper(this);
87         mImageHelper.loadFromAttributes(attrs, defStyleAttr);
88     }
89 
90     /**
91      * Sets a drawable as the content of this ImageView.
92      *
93      * <p>Allows the use of vector drawables when running on older versions of the platform.</p>
94      *
95      * @param resId the resource identifier of the drawable
96      * @see ImageView#setImageResource(int)
97      * {@link androidx.appcompat.R.attr#srcCompat}
98      */
99     @Override
setImageResource(@rawableRes int resId)100     public void setImageResource(@DrawableRes int resId) {
101         if (mImageHelper != null) {
102             // Intercept this call and instead retrieve the Drawable via the image helper
103             mImageHelper.setImageResource(resId);
104         }
105     }
106 
107     @Override
setImageDrawable(@ullable Drawable drawable)108     public void setImageDrawable(@Nullable Drawable drawable) {
109         if (mImageHelper != null && drawable != null && !mHasLevel) {
110             // If there is no level set already then obtain the level from the drawable
111             mImageHelper.obtainLevelFromDrawable(drawable);
112         }
113         super.setImageDrawable(drawable);
114         if (mImageHelper != null) {
115             mImageHelper.applySupportImageTint();
116             if (!mHasLevel) {
117                 // Apply the level from drawable
118                 mImageHelper.applyImageLevel();
119             }
120         }
121     }
122 
123     @Override
setImageBitmap(Bitmap bm)124     public void setImageBitmap(Bitmap bm) {
125         super.setImageBitmap(bm);
126         if (mImageHelper != null) {
127             mImageHelper.applySupportImageTint();
128         }
129     }
130 
131     @Override
setImageURI(@ullable Uri uri)132     public void setImageURI(@Nullable Uri uri) {
133         super.setImageURI(uri);
134         if (mImageHelper != null) {
135             mImageHelper.applySupportImageTint();
136         }
137     }
138 
139     @Override
setBackgroundResource(@rawableRes int resId)140     public void setBackgroundResource(@DrawableRes int resId) {
141         super.setBackgroundResource(resId);
142         if (mBackgroundTintHelper != null) {
143             mBackgroundTintHelper.onSetBackgroundResource(resId);
144         }
145     }
146 
147     @Override
setBackgroundDrawable(@ullable Drawable background)148     public void setBackgroundDrawable(@Nullable Drawable background) {
149         super.setBackgroundDrawable(background);
150         if (mBackgroundTintHelper != null) {
151             mBackgroundTintHelper.onSetBackgroundDrawable(background);
152         }
153     }
154 
155     /**
156      * This should be accessed via
157      * {@link androidx.core.view.ViewCompat#setBackgroundTintList(android.view.View, ColorStateList)}
158      *
159      */
160     @RestrictTo(LIBRARY_GROUP_PREFIX)
161     @Override
setSupportBackgroundTintList(@ullable ColorStateList tint)162     public void setSupportBackgroundTintList(@Nullable ColorStateList tint) {
163         if (mBackgroundTintHelper != null) {
164             mBackgroundTintHelper.setSupportBackgroundTintList(tint);
165         }
166     }
167 
168     /**
169      * This should be accessed via
170      * {@link androidx.core.view.ViewCompat#getBackgroundTintList(android.view.View)}
171      *
172      */
173     @RestrictTo(LIBRARY_GROUP_PREFIX)
174     @Override
getSupportBackgroundTintList()175     public @Nullable ColorStateList getSupportBackgroundTintList() {
176         return mBackgroundTintHelper != null
177                 ? mBackgroundTintHelper.getSupportBackgroundTintList() : null;
178     }
179 
180     /**
181      * This should be accessed via
182      * {@link androidx.core.view.ViewCompat#setBackgroundTintMode(android.view.View, PorterDuff.Mode)}
183      *
184      */
185     @RestrictTo(LIBRARY_GROUP_PREFIX)
186     @Override
setSupportBackgroundTintMode(PorterDuff.@ullable Mode tintMode)187     public void setSupportBackgroundTintMode(PorterDuff.@Nullable Mode tintMode) {
188         if (mBackgroundTintHelper != null) {
189             mBackgroundTintHelper.setSupportBackgroundTintMode(tintMode);
190         }
191     }
192 
193     /**
194      * This should be accessed via
195      * {@link androidx.core.view.ViewCompat#getBackgroundTintMode(android.view.View)}
196      *
197      */
198     @RestrictTo(LIBRARY_GROUP_PREFIX)
199     @Override
getSupportBackgroundTintMode()200     public PorterDuff.@Nullable Mode getSupportBackgroundTintMode() {
201         return mBackgroundTintHelper != null
202                 ? mBackgroundTintHelper.getSupportBackgroundTintMode() : null;
203     }
204 
205     /**
206      * This should be accessed via
207      * {@link androidx.core.widget.ImageViewCompat#setImageTintList(ImageView, ColorStateList)}
208      *
209      */
210     @RestrictTo(LIBRARY_GROUP_PREFIX)
211     @Override
setSupportImageTintList(@ullable ColorStateList tint)212     public void setSupportImageTintList(@Nullable ColorStateList tint) {
213         if (mImageHelper != null) {
214             mImageHelper.setSupportImageTintList(tint);
215         }
216     }
217 
218     /**
219      * This should be accessed via
220      * {@link androidx.core.widget.ImageViewCompat#getImageTintList(ImageView)}
221      *
222      */
223     @RestrictTo(LIBRARY_GROUP_PREFIX)
224     @Override
getSupportImageTintList()225     public @Nullable ColorStateList getSupportImageTintList() {
226         return mImageHelper != null
227                 ? mImageHelper.getSupportImageTintList() : null;
228     }
229 
230     /**
231      * This should be accessed via
232      * {@link androidx.core.widget.ImageViewCompat#setImageTintMode(ImageView, PorterDuff.Mode)}
233      *
234      */
235     @RestrictTo(LIBRARY_GROUP_PREFIX)
236     @Override
setSupportImageTintMode(PorterDuff.@ullable Mode tintMode)237     public void setSupportImageTintMode(PorterDuff.@Nullable Mode tintMode) {
238         if (mImageHelper != null) {
239             mImageHelper.setSupportImageTintMode(tintMode);
240         }
241     }
242 
243     /**
244      * This should be accessed via
245      * {@link androidx.core.widget.ImageViewCompat#getImageTintMode(ImageView)}
246      *
247      */
248     @RestrictTo(LIBRARY_GROUP_PREFIX)
249     @Override
getSupportImageTintMode()250     public PorterDuff.@Nullable Mode getSupportImageTintMode() {
251         return mImageHelper != null
252                 ? mImageHelper.getSupportImageTintMode() : null;
253     }
254 
255     @Override
drawableStateChanged()256     protected void drawableStateChanged() {
257         super.drawableStateChanged();
258         if (mBackgroundTintHelper != null) {
259             mBackgroundTintHelper.applySupportBackgroundTint();
260         }
261         if (mImageHelper != null) {
262             mImageHelper.applySupportImageTint();
263         }
264     }
265 
266     @Override
hasOverlappingRendering()267     public boolean hasOverlappingRendering() {
268         return mImageHelper.hasOverlappingRendering() && super.hasOverlappingRendering();
269     }
270 
271     @Override
setImageLevel(int level)272     public void setImageLevel(int level) {
273         super.setImageLevel(level);
274         mHasLevel = true;
275     }
276 }
277