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