1 /*
2  * Copyright (C) 2006 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 android.widget;
18 
19 import android.annotation.DrawableRes;
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.annotation.TestApi;
23 import android.compat.annotation.UnsupportedAppUsage;
24 import android.content.ContentResolver;
25 import android.content.Context;
26 import android.content.res.ColorStateList;
27 import android.content.res.Resources;
28 import android.content.res.TypedArray;
29 import android.graphics.Bitmap;
30 import android.graphics.BlendMode;
31 import android.graphics.Canvas;
32 import android.graphics.ColorFilter;
33 import android.graphics.ImageDecoder;
34 import android.graphics.Matrix;
35 import android.graphics.PixelFormat;
36 import android.graphics.PorterDuff;
37 import android.graphics.PorterDuffColorFilter;
38 import android.graphics.Rect;
39 import android.graphics.RectF;
40 import android.graphics.Xfermode;
41 import android.graphics.drawable.BitmapDrawable;
42 import android.graphics.drawable.Drawable;
43 import android.graphics.drawable.Icon;
44 import android.net.Uri;
45 import android.os.Build;
46 import android.os.Handler;
47 import android.text.TextUtils;
48 import android.util.AttributeSet;
49 import android.util.Log;
50 import android.view.RemotableViewMethod;
51 import android.view.View;
52 import android.view.ViewDebug;
53 import android.view.ViewHierarchyEncoder;
54 import android.view.accessibility.AccessibilityEvent;
55 import android.view.inspector.InspectableProperty;
56 import android.widget.RemoteViews.RemoteView;
57 
58 import com.android.internal.R;
59 
60 import java.io.IOException;
61 
62 /**
63  * Displays image resources, for example {@link android.graphics.Bitmap}
64  * or {@link android.graphics.drawable.Drawable} resources.
65  * ImageView is also commonly used to
66  * <a href="#setImageTintMode(android.graphics.PorterDuff.Mode)">apply tints to an image</a> and
67  * handle <a href="#setScaleType(android.widget.ImageView.ScaleType)">image scaling</a>.
68  *
69  * <p>
70  * The following XML snippet is a common example of using an ImageView to display an image resource:
71  * </p>
72  * <pre>
73  * &lt;LinearLayout
74  *     xmlns:android="http://schemas.android.com/apk/res/android"
75  *     android:layout_width="match_parent"
76  *     android:layout_height="match_parent"&gt;
77  *     &lt;ImageView
78  *         android:layout_width="wrap_content"
79  *         android:layout_height="wrap_content"
80  *         android:src="@drawable/my_image"
81  *         android:contentDescription="@string/my_image_description"
82  *         /&gt;
83  * &lt;/LinearLayout&gt;
84  * </pre>
85  *
86  * <p>
87  * To learn more about Drawables, see: <a href="{@docRoot}guide/topics/resources/drawable-resource.html">Drawable Resources</a>.
88  * To learn more about working with Bitmaps, see: <a href="{@docRoot}topic/performance/graphics/index.html">Handling Bitmaps</a>.
89  * </p>
90  *
91  * @attr ref android.R.styleable#ImageView_adjustViewBounds
92  * @attr ref android.R.styleable#ImageView_src
93  * @attr ref android.R.styleable#ImageView_maxWidth
94  * @attr ref android.R.styleable#ImageView_maxHeight
95  * @attr ref android.R.styleable#ImageView_tint
96  * @attr ref android.R.styleable#ImageView_scaleType
97  * @attr ref android.R.styleable#ImageView_cropToPadding
98  */
99 @RemoteView
100 public class ImageView extends View {
101     private static final String LOG_TAG = "ImageView";
102 
103     // settable by the client
104     @UnsupportedAppUsage
105     private Uri mUri;
106     @UnsupportedAppUsage
107     private int mResource = 0;
108     private Matrix mMatrix;
109     private ScaleType mScaleType;
110     private boolean mHaveFrame = false;
111     @UnsupportedAppUsage
112     private boolean mAdjustViewBounds = false;
113     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
114     private int mMaxWidth = Integer.MAX_VALUE;
115     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
116     private int mMaxHeight = Integer.MAX_VALUE;
117 
118     // these are applied to the drawable
119     private ColorFilter mColorFilter = null;
120     private boolean mHasColorFilter = false;
121     private Xfermode mXfermode;
122     private boolean mHasXfermode = false;
123     @UnsupportedAppUsage
124     private int mAlpha = 255;
125     private boolean mHasAlpha = false;
126     private final int mViewAlphaScale = 256;
127 
128     @UnsupportedAppUsage
129     private Drawable mDrawable = null;
130     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
131     private BitmapDrawable mRecycleableBitmapDrawable = null;
132     private ColorStateList mDrawableTintList = null;
133     private BlendMode mDrawableBlendMode = null;
134     private boolean mHasDrawableTint = false;
135     private boolean mHasDrawableBlendMode = false;
136 
137     private int[] mState = null;
138     private boolean mMergeState = false;
139     private int mLevel = 0;
140     @UnsupportedAppUsage
141     private int mDrawableWidth;
142     @UnsupportedAppUsage
143     private int mDrawableHeight;
144     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 124051687)
145     private Matrix mDrawMatrix = null;
146 
147     // Avoid allocations...
148     private final RectF mTempSrc = new RectF();
149     private final RectF mTempDst = new RectF();
150 
151     @UnsupportedAppUsage
152     private boolean mCropToPadding;
153 
154     private int mBaseline = -1;
155     private boolean mBaselineAlignBottom = false;
156 
157     /** Compatibility modes dependent on targetSdkVersion of the app. */
158     private static boolean sCompatDone;
159 
160     /** AdjustViewBounds behavior will be in compatibility mode for older apps. */
161     private static boolean sCompatAdjustViewBounds;
162 
163     /** Whether to pass Resources when creating the source from a stream. */
164     private static boolean sCompatUseCorrectStreamDensity;
165 
166     /** Whether to use pre-Nougat drawable visibility dispatching conditions. */
167     private static boolean sCompatDrawableVisibilityDispatch;
168 
169     private static final ScaleType[] sScaleTypeArray = {
170         ScaleType.MATRIX,
171         ScaleType.FIT_XY,
172         ScaleType.FIT_START,
173         ScaleType.FIT_CENTER,
174         ScaleType.FIT_END,
175         ScaleType.CENTER,
176         ScaleType.CENTER_CROP,
177         ScaleType.CENTER_INSIDE
178     };
179 
ImageView(Context context)180     public ImageView(Context context) {
181         super(context);
182         initImageView();
183     }
184 
ImageView(Context context, @Nullable AttributeSet attrs)185     public ImageView(Context context, @Nullable AttributeSet attrs) {
186         this(context, attrs, 0);
187     }
188 
ImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr)189     public ImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
190         this(context, attrs, defStyleAttr, 0);
191     }
192 
ImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes)193     public ImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
194             int defStyleRes) {
195         super(context, attrs, defStyleAttr, defStyleRes);
196 
197         initImageView();
198 
199         final TypedArray a = context.obtainStyledAttributes(
200                 attrs, R.styleable.ImageView, defStyleAttr, defStyleRes);
201         saveAttributeDataForStyleable(context, R.styleable.ImageView,
202                 attrs, a, defStyleAttr, defStyleRes);
203 
204         final Drawable d = a.getDrawable(R.styleable.ImageView_src);
205         if (d != null) {
206             setImageDrawable(d);
207         }
208 
209         mBaselineAlignBottom = a.getBoolean(R.styleable.ImageView_baselineAlignBottom, false);
210         mBaseline = a.getDimensionPixelSize(R.styleable.ImageView_baseline, -1);
211 
212         setAdjustViewBounds(a.getBoolean(R.styleable.ImageView_adjustViewBounds, false));
213         setMaxWidth(a.getDimensionPixelSize(R.styleable.ImageView_maxWidth, Integer.MAX_VALUE));
214         setMaxHeight(a.getDimensionPixelSize(R.styleable.ImageView_maxHeight, Integer.MAX_VALUE));
215 
216         final int index = a.getInt(R.styleable.ImageView_scaleType, -1);
217         if (index >= 0) {
218             setScaleType(sScaleTypeArray[index]);
219         }
220 
221         if (a.hasValue(R.styleable.ImageView_tint)) {
222             mDrawableTintList = a.getColorStateList(R.styleable.ImageView_tint);
223             mHasDrawableTint = true;
224 
225             // Prior to L, this attribute would always set a color filter with
226             // blending mode SRC_ATOP. Preserve that default behavior.
227             mDrawableBlendMode = BlendMode.SRC_ATOP;
228             mHasDrawableBlendMode = true;
229         }
230 
231         if (a.hasValue(R.styleable.ImageView_tintMode)) {
232             mDrawableBlendMode = Drawable.parseBlendMode(a.getInt(
233                     R.styleable.ImageView_tintMode, -1), mDrawableBlendMode);
234             mHasDrawableBlendMode = true;
235         }
236 
237         applyImageTint();
238 
239         final int alpha = a.getInt(R.styleable.ImageView_drawableAlpha, 255);
240         if (alpha != 255) {
241             setImageAlpha(alpha);
242         }
243 
244         mCropToPadding = a.getBoolean(
245                 R.styleable.ImageView_cropToPadding, false);
246 
247         a.recycle();
248 
249         //need inflate syntax/reader for matrix
250     }
251 
initImageView()252     private void initImageView() {
253         mMatrix = new Matrix();
254         mScaleType = ScaleType.FIT_CENTER;
255 
256         if (!sCompatDone) {
257             final int targetSdkVersion = mContext.getApplicationInfo().targetSdkVersion;
258             sCompatAdjustViewBounds = targetSdkVersion <= Build.VERSION_CODES.JELLY_BEAN_MR1;
259             sCompatUseCorrectStreamDensity = targetSdkVersion > Build.VERSION_CODES.M;
260             sCompatDrawableVisibilityDispatch = targetSdkVersion < Build.VERSION_CODES.N;
261             sCompatDone = true;
262         }
263 
264         // By default, ImageView is not important for autofill but important for content capture.
265         // Developers can override these defaults via the corresponding attributes.
266         if (getImportantForAutofill() == IMPORTANT_FOR_AUTOFILL_AUTO) {
267             setImportantForAutofill(IMPORTANT_FOR_AUTOFILL_NO);
268         }
269         if (getImportantForContentCapture() == IMPORTANT_FOR_CONTENT_CAPTURE_AUTO) {
270             setImportantForContentCapture(IMPORTANT_FOR_CONTENT_CAPTURE_YES);
271         }
272     }
273 
274     @Override
275     protected boolean verifyDrawable(@NonNull Drawable dr) {
276         return mDrawable == dr || super.verifyDrawable(dr);
277     }
278 
279     @Override
280     public void jumpDrawablesToCurrentState() {
281         super.jumpDrawablesToCurrentState();
282         if (mDrawable != null) mDrawable.jumpToCurrentState();
283     }
284 
285     @Override
286     public void invalidateDrawable(@NonNull Drawable dr) {
287         if (dr == mDrawable) {
288             if (dr != null) {
289                 // update cached drawable dimensions if they've changed
290                 final int w = dr.getIntrinsicWidth();
291                 final int h = dr.getIntrinsicHeight();
292                 if (w != mDrawableWidth || h != mDrawableHeight) {
293                     mDrawableWidth = w;
294                     mDrawableHeight = h;
295                     // updates the matrix, which is dependent on the bounds
296                     configureBounds();
297                 }
298             }
299             /* we invalidate the whole view in this case because it's very
300              * hard to know where the drawable actually is. This is made
301              * complicated because of the offsets and transformations that
302              * can be applied. In theory we could get the drawable's bounds
303              * and run them through the transformation and offsets, but this
304              * is probably not worth the effort.
305              */
306             invalidate();
307         } else {
308             super.invalidateDrawable(dr);
309         }
310     }
311 
312     @Override
313     public boolean hasOverlappingRendering() {
314         return (getBackground() != null && getBackground().getCurrent() != null);
315     }
316 
317     /** @hide */
318     @Override
319     public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) {
320         super.onPopulateAccessibilityEventInternal(event);
321 
322         final CharSequence contentDescription = getContentDescription();
323         if (!TextUtils.isEmpty(contentDescription)) {
324             event.getText().add(contentDescription);
325         }
326     }
327 
328     /**
329      * True when ImageView is adjusting its bounds
330      * to preserve the aspect ratio of its drawable
331      *
332      * @return whether to adjust the bounds of this view
333      * to preserve the original aspect ratio of the drawable
334      *
335      * @see #setAdjustViewBounds(boolean)
336      *
337      * @attr ref android.R.styleable#ImageView_adjustViewBounds
338      */
339     @InspectableProperty
340     public boolean getAdjustViewBounds() {
341         return mAdjustViewBounds;
342     }
343 
344     /**
345      * Set this to true if you want the ImageView to adjust its bounds
346      * to preserve the aspect ratio of its drawable.
347      *
348      * <p><strong>Note:</strong> If the application targets API level 17 or lower,
349      * adjustViewBounds will allow the drawable to shrink the view bounds, but not grow
350      * to fill available measured space in all cases. This is for compatibility with
351      * legacy {@link android.view.View.MeasureSpec MeasureSpec} and
352      * {@link android.widget.RelativeLayout RelativeLayout} behavior.</p>
353      *
354      * @param adjustViewBounds Whether to adjust the bounds of this view
355      * to preserve the original aspect ratio of the drawable.
356      *
357      * @see #getAdjustViewBounds()
358      *
359      * @attr ref android.R.styleable#ImageView_adjustViewBounds
360      */
361     @android.view.RemotableViewMethod
362     public void setAdjustViewBounds(boolean adjustViewBounds) {
363         mAdjustViewBounds = adjustViewBounds;
364         if (adjustViewBounds) {
365             setScaleType(ScaleType.FIT_CENTER);
366         }
367     }
368 
369     /**
370      * The maximum width of this view.
371      *
372      * @return The maximum width of this view
373      *
374      * @see #setMaxWidth(int)
375      *
376      * @attr ref android.R.styleable#ImageView_maxWidth
377      */
378     @InspectableProperty
379     public int getMaxWidth() {
380         return mMaxWidth;
381     }
382 
383     /**
384      * An optional argument to supply a maximum width for this view. Only valid if
385      * {@link #setAdjustViewBounds(boolean)} has been set to true. To set an image to be a maximum
386      * of 100 x 100 while preserving the original aspect ratio, do the following: 1) set
387      * adjustViewBounds to true 2) set maxWidth and maxHeight to 100 3) set the height and width
388      * layout params to WRAP_CONTENT.
389      *
390      * <p>
391      * Note that this view could be still smaller than 100 x 100 using this approach if the original
392      * image is small. To set an image to a fixed size, specify that size in the layout params and
393      * then use {@link #setScaleType(android.widget.ImageView.ScaleType)} to determine how to fit
394      * the image within the bounds.
395      * </p>
396      *
397      * @param maxWidth maximum width for this view
398      *
399      * @see #getMaxWidth()
400      *
401      * @attr ref android.R.styleable#ImageView_maxWidth
402      */
403     @android.view.RemotableViewMethod
404     public void setMaxWidth(int maxWidth) {
405         mMaxWidth = maxWidth;
406     }
407 
408     /**
409      * The maximum height of this view.
410      *
411      * @return The maximum height of this view
412      *
413      * @see #setMaxHeight(int)
414      *
415      * @attr ref android.R.styleable#ImageView_maxHeight
416      */
417     @InspectableProperty
418     public int getMaxHeight() {
419         return mMaxHeight;
420     }
421 
422     /**
423      * An optional argument to supply a maximum height for this view. Only valid if
424      * {@link #setAdjustViewBounds(boolean)} has been set to true. To set an image to be a
425      * maximum of 100 x 100 while preserving the original aspect ratio, do the following: 1) set
426      * adjustViewBounds to true 2) set maxWidth and maxHeight to 100 3) set the height and width
427      * layout params to WRAP_CONTENT.
428      *
429      * <p>
430      * Note that this view could be still smaller than 100 x 100 using this approach if the original
431      * image is small. To set an image to a fixed size, specify that size in the layout params and
432      * then use {@link #setScaleType(android.widget.ImageView.ScaleType)} to determine how to fit
433      * the image within the bounds.
434      * </p>
435      *
436      * @param maxHeight maximum height for this view
437      *
438      * @see #getMaxHeight()
439      *
440      * @attr ref android.R.styleable#ImageView_maxHeight
441      */
442     @android.view.RemotableViewMethod
443     public void setMaxHeight(int maxHeight) {
444         mMaxHeight = maxHeight;
445     }
446 
447     /**
448      * Gets the current Drawable, or null if no Drawable has been
449      * assigned.
450      *
451      * @return the view's drawable, or null if no drawable has been
452      * assigned.
453      */
454     @InspectableProperty(name = "src")
455     public Drawable getDrawable() {
456         if (mDrawable == mRecycleableBitmapDrawable) {
457             // Consider our cached version dirty since app code now has a reference to it
458             mRecycleableBitmapDrawable = null;
459         }
460         return mDrawable;
461     }
462 
463     private class ImageDrawableCallback implements Runnable {
464 
465         private final Drawable drawable;
466         private final Uri uri;
467         private final int resource;
468 
469         ImageDrawableCallback(Drawable drawable, Uri uri, int resource) {
470             this.drawable = drawable;
471             this.uri = uri;
472             this.resource = resource;
473         }
474 
475         @Override
476         public void run() {
477             setImageDrawable(drawable);
478             mUri = uri;
479             mResource = resource;
480         }
481     }
482 
483     /**
484      * Sets a drawable as the content of this ImageView.
485      * <p class="note">This does Bitmap reading and decoding on the UI
486      * thread, which can cause a latency hiccup.  If that's a concern,
487      * consider using {@link #setImageDrawable(android.graphics.drawable.Drawable)} or
488      * {@link #setImageBitmap(android.graphics.Bitmap)} and
489      * {@link android.graphics.BitmapFactory} instead.</p>
490      *
491      * @param resId the resource identifier of the drawable
492      *
493      * @attr ref android.R.styleable#ImageView_src
494      */
495     @android.view.RemotableViewMethod(asyncImpl="setImageResourceAsync")
496     public void setImageResource(@DrawableRes int resId) {
497         // The resource configuration may have changed, so we should always
498         // try to load the resource even if the resId hasn't changed.
499         final int oldWidth = mDrawableWidth;
500         final int oldHeight = mDrawableHeight;
501 
502         updateDrawable(null);
503         mResource = resId;
504         mUri = null;
505 
506         resolveUri();
507 
508         if (oldWidth != mDrawableWidth || oldHeight != mDrawableHeight) {
509             requestLayout();
510         }
511         invalidate();
512     }
513 
514     /** @hide **/
515     @UnsupportedAppUsage
516     public Runnable setImageResourceAsync(@DrawableRes int resId) {
517         Drawable d = null;
518         if (resId != 0) {
519             try {
520                 d = getContext().getDrawable(resId);
521             } catch (Exception e) {
522                 Log.w(LOG_TAG, "Unable to find resource: " + resId, e);
523                 resId = 0;
524             }
525         }
526         return new ImageDrawableCallback(d, null, resId);
527     }
528 
529     /**
530      * Sets the content of this ImageView to the specified Uri.
531      * Note that you use this method to load images from a local Uri only.
532      * <p/>
533      * To learn how to display images from a remote Uri see: <a href="https://developer.android.com/topic/performance/graphics/index.html">Handling Bitmaps</a>
534      * <p/>
535      * <p class="note">This does Bitmap reading and decoding on the UI
536      * thread, which can cause a latency hiccup.  If that's a concern,
537      * consider using {@link #setImageDrawable(Drawable)} or
538      * {@link #setImageBitmap(android.graphics.Bitmap)} and
539      * {@link android.graphics.BitmapFactory} instead.</p>
540      *
541      * <p class="note">On devices running SDK < 24, this method will fail to
542      * apply correct density scaling to images loaded from
543      * {@link ContentResolver#SCHEME_CONTENT content} and
544      * {@link ContentResolver#SCHEME_FILE file} schemes. Applications running
545      * on devices with SDK >= 24 <strong>MUST</strong> specify the
546      * {@code targetSdkVersion} in their manifest as 24 or above for density
547      * scaling to be applied to images loaded from these schemes.</p>
548      *
549      * @param uri the Uri of an image, or {@code null} to clear the content
550      */
551     @android.view.RemotableViewMethod(asyncImpl="setImageURIAsync")
552     public void setImageURI(@Nullable Uri uri) {
553         if (mResource != 0 || (mUri != uri && (uri == null || mUri == null || !uri.equals(mUri)))) {
554             updateDrawable(null);
555             mResource = 0;
556             mUri = uri;
557 
558             final int oldWidth = mDrawableWidth;
559             final int oldHeight = mDrawableHeight;
560 
561             resolveUri();
562 
563             if (oldWidth != mDrawableWidth || oldHeight != mDrawableHeight) {
564                 requestLayout();
565             }
566             invalidate();
567         }
568     }
569 
570     /** @hide **/
571     @UnsupportedAppUsage
572     public Runnable setImageURIAsync(@Nullable Uri uri) {
573         if (mResource != 0 || (mUri != uri && (uri == null || mUri == null || !uri.equals(mUri)))) {
574             Drawable d = uri == null ? null : getDrawableFromUri(uri);
575             if (d == null) {
576                 // Do not set the URI if the drawable couldn't be loaded.
577                 uri = null;
578             }
579             return new ImageDrawableCallback(d, uri, 0);
580         }
581         return null;
582     }
583 
584     /**
585      * Sets a drawable as the content of this ImageView.
586      *
587      * @param drawable the Drawable to set, or {@code null} to clear the
588      *                 content
589      */
590     public void setImageDrawable(@Nullable Drawable drawable) {
591         if (mDrawable != drawable) {
592             mResource = 0;
593             mUri = null;
594 
595             final int oldWidth = mDrawableWidth;
596             final int oldHeight = mDrawableHeight;
597 
598             updateDrawable(drawable);
599 
600             if (oldWidth != mDrawableWidth || oldHeight != mDrawableHeight) {
601                 requestLayout();
602             }
603             invalidate();
604         }
605     }
606 
607     /**
608      * Sets the content of this ImageView to the specified Icon.
609      *
610      * <p class="note">Depending on the Icon type, this may do Bitmap reading
611      * and decoding on the UI thread, which can cause UI jank.  If that's a
612      * concern, consider using
613      * {@link Icon#loadDrawableAsync(Context, Icon.OnDrawableLoadedListener, Handler)}
614      * and then {@link #setImageDrawable(android.graphics.drawable.Drawable)}
615      * instead.</p>
616      *
617      * @param icon an Icon holding the desired image, or {@code null} to clear
618      *             the content
619      */
620     @android.view.RemotableViewMethod(asyncImpl="setImageIconAsync")
621     public void setImageIcon(@Nullable Icon icon) {
622         setImageDrawable(icon == null ? null : icon.loadDrawable(mContext));
623     }
624 
625     /** @hide **/
626     public Runnable setImageIconAsync(@Nullable Icon icon) {
627         return new ImageDrawableCallback(icon == null ? null : icon.loadDrawable(mContext), null, 0);
628     }
629 
630     /**
631      * Applies a tint to the image drawable. Does not modify the current tint
632      * mode, which is {@link PorterDuff.Mode#SRC_IN} by default.
633      * <p>
634      * Subsequent calls to {@link #setImageDrawable(Drawable)} will automatically
635      * mutate the drawable and apply the specified tint and tint mode using
636      * {@link Drawable#setTintList(ColorStateList)}.
637      * <p>
638      * <em>Note:</em> The default tint mode used by this setter is NOT
639      * consistent with the default tint mode used by the
640      * {@link android.R.styleable#ImageView_tint android:tint}
641      * attribute. If the {@code android:tint} attribute is specified, the
642      * default tint mode will be set to {@link PorterDuff.Mode#SRC_ATOP} to
643      * ensure consistency with earlier versions of the platform.
644      *
645      * @param tint the tint to apply, may be {@code null} to clear tint
646      *
647      * @attr ref android.R.styleable#ImageView_tint
648      * @see #getImageTintList()
649      * @see Drawable#setTintList(ColorStateList)
650      */
651     @android.view.RemotableViewMethod
652     public void setImageTintList(@Nullable ColorStateList tint) {
653         mDrawableTintList = tint;
654         mHasDrawableTint = true;
655 
656         applyImageTint();
657     }
658 
659     /**
660      * Get the current {@link android.content.res.ColorStateList} used to tint the image Drawable,
661      * or null if no tint is applied.
662      *
663      * @return the tint applied to the image drawable
664      * @attr ref android.R.styleable#ImageView_tint
665      * @see #setImageTintList(ColorStateList)
666      */
667     @Nullable
668     @InspectableProperty(name = "tint")
669     public ColorStateList getImageTintList() {
670         return mDrawableTintList;
671     }
672 
673     /**
674      * Specifies the blending mode used to apply the tint specified by
675      * {@link #setImageTintList(ColorStateList)}} to the image drawable. The default
676      * mode is {@link PorterDuff.Mode#SRC_IN}.
677      *
678      * @param tintMode the blending mode used to apply the tint, may be
679      *                 {@code null} to clear tint
680      * @attr ref android.R.styleable#ImageView_tintMode
681      * @see #getImageTintMode()
682      * @see Drawable#setTintMode(PorterDuff.Mode)
683      */
684     public void setImageTintMode(@Nullable PorterDuff.Mode tintMode) {
685         setImageTintBlendMode(tintMode != null ? BlendMode.fromValue(tintMode.nativeInt) : null);
686     }
687 
688     /**
689      * Specifies the blending mode used to apply the tint specified by
690      * {@link #setImageTintList(ColorStateList)}} to the image drawable. The default
691      * mode is {@link BlendMode#SRC_IN}.
692      *
693      * @param blendMode the blending mode used to apply the tint, may be
694      *                 {@code null} to clear tint
695      * @attr ref android.R.styleable#ImageView_tintMode
696      * @see #getImageTintMode()
697      * @see Drawable#setTintBlendMode(BlendMode)
698      */
699     @RemotableViewMethod
700     public void setImageTintBlendMode(@Nullable BlendMode blendMode) {
701         mDrawableBlendMode = blendMode;
702         mHasDrawableBlendMode = true;
703 
704         applyImageTint();
705     }
706 
707     /**
708      * Gets the blending mode used to apply the tint to the image Drawable
709      * @return the blending mode used to apply the tint to the image Drawable
710      * @attr ref android.R.styleable#ImageView_tintMode
711      * @see #setImageTintMode(PorterDuff.Mode)
712      */
713     @Nullable
714     @InspectableProperty(name = "tintMode")
715     public PorterDuff.Mode getImageTintMode() {
716         return mDrawableBlendMode != null
717                 ? BlendMode.blendModeToPorterDuffMode(mDrawableBlendMode) : null;
718     }
719 
720     /**
721      * Gets the blending mode used to apply the tint to the image Drawable
722      * @return the blending mode used to apply the tint to the image Drawable
723      * @attr ref android.R.styleable#ImageView_tintMode
724      * @see #setImageTintBlendMode(BlendMode)
725      */
726     @Nullable
727     @InspectableProperty(name = "blendMode", attributeId = android.R.styleable.ImageView_tintMode)
728     public BlendMode getImageTintBlendMode() {
729         return mDrawableBlendMode;
730     }
731 
732     private void applyImageTint() {
733         if (mDrawable != null && (mHasDrawableTint || mHasDrawableBlendMode)) {
734             mDrawable = mDrawable.mutate();
735 
736             if (mHasDrawableTint) {
737                 mDrawable.setTintList(mDrawableTintList);
738             }
739 
740             if (mHasDrawableBlendMode) {
741                 mDrawable.setTintBlendMode(mDrawableBlendMode);
742             }
743 
744             // The drawable (or one of its children) may not have been
745             // stateful before applying the tint, so let's try again.
746             if (mDrawable.isStateful()) {
747                 mDrawable.setState(getDrawableState());
748             }
749         }
750     }
751 
752     /**
753      * Sets a Bitmap as the content of this ImageView.
754      *
755      * @param bm The bitmap to set
756      */
757     @android.view.RemotableViewMethod
758     public void setImageBitmap(Bitmap bm) {
759         // Hacky fix to force setImageDrawable to do a full setImageDrawable
760         // instead of doing an object reference comparison
761         mDrawable = null;
762         if (mRecycleableBitmapDrawable == null) {
763             mRecycleableBitmapDrawable = new BitmapDrawable(mContext.getResources(), bm);
764         } else {
765             mRecycleableBitmapDrawable.setBitmap(bm);
766         }
767         setImageDrawable(mRecycleableBitmapDrawable);
768     }
769 
770     /**
771      * Set the state of the current {@link android.graphics.drawable.StateListDrawable}.
772      * For more information about State List Drawables, see: <a href="https://developer.android.com/guide/topics/resources/drawable-resource.html#StateList">the Drawable Resource Guide</a>.
773      *
774      * @param state the state to set for the StateListDrawable
775      * @param merge if true, merges the state values for the state you specify into the current state
776      */
777     public void setImageState(int[] state, boolean merge) {
778         mState = state;
779         mMergeState = merge;
780         if (mDrawable != null) {
781             refreshDrawableState();
782             resizeFromDrawable();
783         }
784     }
785 
786     @Override
787     public void setSelected(boolean selected) {
788         super.setSelected(selected);
789         resizeFromDrawable();
790     }
791 
792     /**
793      * Sets the image level, when it is constructed from a
794      * {@link android.graphics.drawable.LevelListDrawable}.
795      *
796      * @param level The new level for the image.
797      */
798     @android.view.RemotableViewMethod
799     public void setImageLevel(int level) {
800         mLevel = level;
801         if (mDrawable != null) {
802             mDrawable.setLevel(level);
803             resizeFromDrawable();
804         }
805     }
806 
807     /**
808      * Options for scaling the bounds of an image to the bounds of this view.
809      */
810     public enum ScaleType {
811         /**
812          * Scale using the image matrix when drawing. The image matrix can be set using
813          * {@link ImageView#setImageMatrix(Matrix)}. From XML, use this syntax:
814          * <code>android:scaleType="matrix"</code>.
815          */
816         MATRIX      (0),
817         /**
818          * Scale the image using {@link Matrix.ScaleToFit#FILL}.
819          * From XML, use this syntax: <code>android:scaleType="fitXY"</code>.
820          */
821         FIT_XY      (1),
822         /**
823          * Scale the image using {@link Matrix.ScaleToFit#START}.
824          * From XML, use this syntax: <code>android:scaleType="fitStart"</code>.
825          */
826         FIT_START   (2),
827         /**
828          * Scale the image using {@link Matrix.ScaleToFit#CENTER}.
829          * From XML, use this syntax:
830          * <code>android:scaleType="fitCenter"</code>.
831          */
832         FIT_CENTER  (3),
833         /**
834          * Scale the image using {@link Matrix.ScaleToFit#END}.
835          * From XML, use this syntax: <code>android:scaleType="fitEnd"</code>.
836          */
837         FIT_END     (4),
838         /**
839          * Center the image in the view, but perform no scaling.
840          * From XML, use this syntax: <code>android:scaleType="center"</code>.
841          */
842         CENTER      (5),
843         /**
844          * Scale the image uniformly (maintain the image's aspect ratio) so
845          * that both dimensions (width and height) of the image will be equal
846          * to or larger than the corresponding dimension of the view
847          * (minus padding). The image is then centered in the view.
848          * From XML, use this syntax: <code>android:scaleType="centerCrop"</code>.
849          */
850         CENTER_CROP (6),
851         /**
852          * Scale the image uniformly (maintain the image's aspect ratio) so
853          * that both dimensions (width and height) of the image will be equal
854          * to or less than the corresponding dimension of the view
855          * (minus padding). The image is then centered in the view.
856          * From XML, use this syntax: <code>android:scaleType="centerInside"</code>.
857          */
858         CENTER_INSIDE (7);
859 
860         ScaleType(int ni) {
861             nativeInt = ni;
862         }
863         final int nativeInt;
864     }
865 
866     /**
867      * Controls how the image should be resized or moved to match the size
868      * of this ImageView.
869      *
870      * @param scaleType The desired scaling mode.
871      *
872      * @attr ref android.R.styleable#ImageView_scaleType
873      */
874     public void setScaleType(ScaleType scaleType) {
875         if (scaleType == null) {
876             throw new NullPointerException();
877         }
878 
879         if (mScaleType != scaleType) {
880             mScaleType = scaleType;
881 
882             requestLayout();
883             invalidate();
884         }
885     }
886 
887     /**
888      * Returns the current ScaleType that is used to scale the bounds of an image to the bounds of the ImageView.
889      * @return The ScaleType used to scale the image.
890      * @see ImageView.ScaleType
891      * @attr ref android.R.styleable#ImageView_scaleType
892      */
893     @InspectableProperty
894     public ScaleType getScaleType() {
895         return mScaleType;
896     }
897 
898     /** Returns the view's optional matrix. This is applied to the
899         view's drawable when it is drawn. If there is no matrix,
900         this method will return an identity matrix.
901         Do not change this matrix in place but make a copy.
902         If you want a different matrix applied to the drawable,
903         be sure to call setImageMatrix().
904     */
905     public Matrix getImageMatrix() {
906         if (mDrawMatrix == null) {
907             return new Matrix(Matrix.IDENTITY_MATRIX);
908         }
909         return mDrawMatrix;
910     }
911 
912     /**
913      * Adds a transformation {@link Matrix} that is applied
914      * to the view's drawable when it is drawn.  Allows custom scaling,
915      * translation, and perspective distortion.
916      *
917      * @param matrix The transformation parameters in matrix form.
918      */
919     public void setImageMatrix(Matrix matrix) {
920         // collapse null and identity to just null
921         if (matrix != null && matrix.isIdentity()) {
922             matrix = null;
923         }
924 
925         // don't invalidate unless we're actually changing our matrix
926         if (matrix == null && !mMatrix.isIdentity() ||
927                 matrix != null && !mMatrix.equals(matrix)) {
928             mMatrix.set(matrix);
929             configureBounds();
930             invalidate();
931         }
932     }
933 
934     /**
935      * Return whether this ImageView crops to padding.
936      *
937      * @return whether this ImageView crops to padding
938      *
939      * @see #setCropToPadding(boolean)
940      *
941      * @attr ref android.R.styleable#ImageView_cropToPadding
942      */
943     @InspectableProperty
944     public boolean getCropToPadding() {
945         return mCropToPadding;
946     }
947 
948     /**
949      * Sets whether this ImageView will crop to padding.
950      *
951      * @param cropToPadding whether this ImageView will crop to padding
952      *
953      * @see #getCropToPadding()
954      *
955      * @attr ref android.R.styleable#ImageView_cropToPadding
956      */
957     public void setCropToPadding(boolean cropToPadding) {
958         if (mCropToPadding != cropToPadding) {
959             mCropToPadding = cropToPadding;
960             requestLayout();
961             invalidate();
962         }
963     }
964 
965     @UnsupportedAppUsage
966     private void resolveUri() {
967         if (mDrawable != null) {
968             return;
969         }
970 
971         if (getResources() == null) {
972             return;
973         }
974 
975         Drawable d = null;
976 
977         if (mResource != 0) {
978             try {
979                 d = mContext.getDrawable(mResource);
980             } catch (Exception e) {
981                 Log.w(LOG_TAG, "Unable to find resource: " + mResource, e);
982                 // Don't try again.
983                 mResource = 0;
984             }
985         } else if (mUri != null) {
986             d = getDrawableFromUri(mUri);
987 
988             if (d == null) {
989                 Log.w(LOG_TAG, "resolveUri failed on bad bitmap uri: " + mUri);
990                 // Don't try again.
991                 mUri = null;
992             }
993         } else {
994             return;
995         }
996 
997         updateDrawable(d);
998     }
999 
1000     private Drawable getDrawableFromUri(Uri uri) {
1001         final String scheme = uri.getScheme();
1002         if (ContentResolver.SCHEME_ANDROID_RESOURCE.equals(scheme)) {
1003             try {
1004                 // Load drawable through Resources, to get the source density information
1005                 ContentResolver.OpenResourceIdResult r =
1006                         mContext.getContentResolver().getResourceId(uri);
1007                 return r.r.getDrawable(r.id, mContext.getTheme());
1008             } catch (Exception e) {
1009                 Log.w(LOG_TAG, "Unable to open content: " + uri, e);
1010             }
1011         } else if (ContentResolver.SCHEME_CONTENT.equals(scheme)
1012                 || ContentResolver.SCHEME_FILE.equals(scheme)) {
1013             try {
1014                 Resources res = sCompatUseCorrectStreamDensity ? getResources() : null;
1015                 ImageDecoder.Source src = ImageDecoder.createSource(mContext.getContentResolver(),
1016                         uri, res);
1017                 return ImageDecoder.decodeDrawable(src, (decoder, info, s) -> {
1018                     decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
1019                 });
1020             } catch (IOException e) {
1021                 Log.w(LOG_TAG, "Unable to open content: " + uri, e);
1022             }
1023         } else {
1024             return Drawable.createFromPath(uri.toString());
1025         }
1026         return null;
1027     }
1028 
1029     @Override
onCreateDrawableState(int extraSpace)1030     public int[] onCreateDrawableState(int extraSpace) {
1031         if (mState == null) {
1032             return super.onCreateDrawableState(extraSpace);
1033         } else if (!mMergeState) {
1034             return mState;
1035         } else {
1036             return mergeDrawableStates(
1037                     super.onCreateDrawableState(extraSpace + mState.length), mState);
1038         }
1039     }
1040 
1041     @UnsupportedAppUsage
updateDrawable(Drawable d)1042     private void updateDrawable(Drawable d) {
1043         if (d != mRecycleableBitmapDrawable && mRecycleableBitmapDrawable != null) {
1044             mRecycleableBitmapDrawable.setBitmap(null);
1045         }
1046 
1047         boolean sameDrawable = false;
1048 
1049         if (mDrawable != null) {
1050             sameDrawable = mDrawable == d;
1051             mDrawable.setCallback(null);
1052             unscheduleDrawable(mDrawable);
1053             if (!sCompatDrawableVisibilityDispatch && !sameDrawable && isAttachedToWindow()) {
1054                 mDrawable.setVisible(false, false);
1055             }
1056         }
1057 
1058         mDrawable = d;
1059 
1060         if (d != null) {
1061             d.setCallback(this);
1062             d.setLayoutDirection(getLayoutDirection());
1063             if (d.isStateful()) {
1064                 d.setState(getDrawableState());
1065             }
1066             if (!sameDrawable || sCompatDrawableVisibilityDispatch) {
1067                 final boolean visible = sCompatDrawableVisibilityDispatch
1068                         ? getVisibility() == VISIBLE
1069                         : isAttachedToWindow() && getWindowVisibility() == VISIBLE && isShown();
1070                 d.setVisible(visible, true);
1071             }
1072             d.setLevel(mLevel);
1073             mDrawableWidth = d.getIntrinsicWidth();
1074             mDrawableHeight = d.getIntrinsicHeight();
1075             applyImageTint();
1076             applyColorFilter();
1077             applyAlpha();
1078             applyXfermode();
1079 
1080             configureBounds();
1081         } else {
1082             mDrawableWidth = mDrawableHeight = -1;
1083         }
1084     }
1085 
1086     @UnsupportedAppUsage
resizeFromDrawable()1087     private void resizeFromDrawable() {
1088         final Drawable d = mDrawable;
1089         if (d != null) {
1090             int w = d.getIntrinsicWidth();
1091             if (w < 0) w = mDrawableWidth;
1092             int h = d.getIntrinsicHeight();
1093             if (h < 0) h = mDrawableHeight;
1094             if (w != mDrawableWidth || h != mDrawableHeight) {
1095                 mDrawableWidth = w;
1096                 mDrawableHeight = h;
1097                 requestLayout();
1098             }
1099         }
1100     }
1101 
1102     @Override
onRtlPropertiesChanged(int layoutDirection)1103     public void onRtlPropertiesChanged(int layoutDirection) {
1104         super.onRtlPropertiesChanged(layoutDirection);
1105 
1106         if (mDrawable != null) {
1107             mDrawable.setLayoutDirection(layoutDirection);
1108         }
1109     }
1110 
1111     private static final Matrix.ScaleToFit[] sS2FArray = {
1112         Matrix.ScaleToFit.FILL,
1113         Matrix.ScaleToFit.START,
1114         Matrix.ScaleToFit.CENTER,
1115         Matrix.ScaleToFit.END
1116     };
1117 
1118     @UnsupportedAppUsage
scaleTypeToScaleToFit(ScaleType st)1119     private static Matrix.ScaleToFit scaleTypeToScaleToFit(ScaleType st)  {
1120         // ScaleToFit enum to their corresponding Matrix.ScaleToFit values
1121         return sS2FArray[st.nativeInt - 1];
1122     }
1123 
1124     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)1125     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
1126         resolveUri();
1127         int w;
1128         int h;
1129 
1130         // Desired aspect ratio of the view's contents (not including padding)
1131         float desiredAspect = 0.0f;
1132 
1133         // We are allowed to change the view's width
1134         boolean resizeWidth = false;
1135 
1136         // We are allowed to change the view's height
1137         boolean resizeHeight = false;
1138 
1139         final int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
1140         final int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
1141 
1142         if (mDrawable == null) {
1143             // If no drawable, its intrinsic size is 0.
1144             mDrawableWidth = -1;
1145             mDrawableHeight = -1;
1146             w = h = 0;
1147         } else {
1148             w = mDrawableWidth;
1149             h = mDrawableHeight;
1150             if (w <= 0) w = 1;
1151             if (h <= 0) h = 1;
1152 
1153             // We are supposed to adjust view bounds to match the aspect
1154             // ratio of our drawable. See if that is possible.
1155             if (mAdjustViewBounds) {
1156                 resizeWidth = widthSpecMode != MeasureSpec.EXACTLY;
1157                 resizeHeight = heightSpecMode != MeasureSpec.EXACTLY;
1158 
1159                 desiredAspect = (float) w / (float) h;
1160             }
1161         }
1162 
1163         final int pleft = mPaddingLeft;
1164         final int pright = mPaddingRight;
1165         final int ptop = mPaddingTop;
1166         final int pbottom = mPaddingBottom;
1167 
1168         int widthSize;
1169         int heightSize;
1170 
1171         if (resizeWidth || resizeHeight) {
1172             /* If we get here, it means we want to resize to match the
1173                 drawables aspect ratio, and we have the freedom to change at
1174                 least one dimension.
1175             */
1176 
1177             // Get the max possible width given our constraints
1178             widthSize = resolveAdjustedSize(w + pleft + pright, mMaxWidth, widthMeasureSpec);
1179 
1180             // Get the max possible height given our constraints
1181             heightSize = resolveAdjustedSize(h + ptop + pbottom, mMaxHeight, heightMeasureSpec);
1182 
1183             if (desiredAspect != 0.0f) {
1184                 // See what our actual aspect ratio is
1185                 final float actualAspect = (float)(widthSize - pleft - pright) /
1186                                         (heightSize - ptop - pbottom);
1187 
1188                 if (Math.abs(actualAspect - desiredAspect) > 0.0000001) {
1189 
1190                     boolean done = false;
1191 
1192                     // Try adjusting width to be proportional to height
1193                     if (resizeWidth) {
1194                         int newWidth = (int)(desiredAspect * (heightSize - ptop - pbottom)) +
1195                                 pleft + pright;
1196 
1197                         // Allow the width to outgrow its original estimate if height is fixed.
1198                         if (!resizeHeight && !sCompatAdjustViewBounds) {
1199                             widthSize = resolveAdjustedSize(newWidth, mMaxWidth, widthMeasureSpec);
1200                         }
1201 
1202                         if (newWidth <= widthSize) {
1203                             widthSize = newWidth;
1204                             done = true;
1205                         }
1206                     }
1207 
1208                     // Try adjusting height to be proportional to width
1209                     if (!done && resizeHeight) {
1210                         int newHeight = (int)((widthSize - pleft - pright) / desiredAspect) +
1211                                 ptop + pbottom;
1212 
1213                         // Allow the height to outgrow its original estimate if width is fixed.
1214                         if (!resizeWidth && !sCompatAdjustViewBounds) {
1215                             heightSize = resolveAdjustedSize(newHeight, mMaxHeight,
1216                                     heightMeasureSpec);
1217                         }
1218 
1219                         if (newHeight <= heightSize) {
1220                             heightSize = newHeight;
1221                         }
1222                     }
1223                 }
1224             }
1225         } else {
1226             /* We are either don't want to preserve the drawables aspect ratio,
1227                or we are not allowed to change view dimensions. Just measure in
1228                the normal way.
1229             */
1230             w += pleft + pright;
1231             h += ptop + pbottom;
1232 
1233             w = Math.max(w, getSuggestedMinimumWidth());
1234             h = Math.max(h, getSuggestedMinimumHeight());
1235 
1236             widthSize = resolveSizeAndState(w, widthMeasureSpec, 0);
1237             heightSize = resolveSizeAndState(h, heightMeasureSpec, 0);
1238         }
1239 
1240         setMeasuredDimension(widthSize, heightSize);
1241     }
1242 
resolveAdjustedSize(int desiredSize, int maxSize, int measureSpec)1243     private int resolveAdjustedSize(int desiredSize, int maxSize,
1244                                    int measureSpec) {
1245         int result = desiredSize;
1246         final int specMode = MeasureSpec.getMode(measureSpec);
1247         final int specSize =  MeasureSpec.getSize(measureSpec);
1248         switch (specMode) {
1249             case MeasureSpec.UNSPECIFIED:
1250                 /* Parent says we can be as big as we want. Just don't be larger
1251                    than max size imposed on ourselves.
1252                 */
1253                 result = Math.min(desiredSize, maxSize);
1254                 break;
1255             case MeasureSpec.AT_MOST:
1256                 // Parent says we can be as big as we want, up to specSize.
1257                 // Don't be larger than specSize, and don't be larger than
1258                 // the max size imposed on ourselves.
1259                 result = Math.min(Math.min(desiredSize, specSize), maxSize);
1260                 break;
1261             case MeasureSpec.EXACTLY:
1262                 // No choice. Do what we are told.
1263                 result = specSize;
1264                 break;
1265         }
1266         return result;
1267     }
1268 
1269     @Override
setFrame(int l, int t, int r, int b)1270     protected boolean setFrame(int l, int t, int r, int b) {
1271         final boolean changed = super.setFrame(l, t, r, b);
1272         mHaveFrame = true;
1273         configureBounds();
1274         return changed;
1275     }
1276 
configureBounds()1277     private void configureBounds() {
1278         if (mDrawable == null || !mHaveFrame) {
1279             return;
1280         }
1281 
1282         final int dwidth = mDrawableWidth;
1283         final int dheight = mDrawableHeight;
1284 
1285         final int vwidth = getWidth() - mPaddingLeft - mPaddingRight;
1286         final int vheight = getHeight() - mPaddingTop - mPaddingBottom;
1287 
1288         final boolean fits = (dwidth < 0 || vwidth == dwidth)
1289                 && (dheight < 0 || vheight == dheight);
1290 
1291         if (dwidth <= 0 || dheight <= 0 || ScaleType.FIT_XY == mScaleType) {
1292             /* If the drawable has no intrinsic size, or we're told to
1293                 scaletofit, then we just fill our entire view.
1294             */
1295             mDrawable.setBounds(0, 0, vwidth, vheight);
1296             mDrawMatrix = null;
1297         } else {
1298             // We need to do the scaling ourself, so have the drawable
1299             // use its native size.
1300             mDrawable.setBounds(0, 0, dwidth, dheight);
1301 
1302             if (ScaleType.MATRIX == mScaleType) {
1303                 // Use the specified matrix as-is.
1304                 if (mMatrix.isIdentity()) {
1305                     mDrawMatrix = null;
1306                 } else {
1307                     mDrawMatrix = mMatrix;
1308                 }
1309             } else if (fits) {
1310                 // The bitmap fits exactly, no transform needed.
1311                 mDrawMatrix = null;
1312             } else if (ScaleType.CENTER == mScaleType) {
1313                 // Center bitmap in view, no scaling.
1314                 mDrawMatrix = mMatrix;
1315                 mDrawMatrix.setTranslate(Math.round((vwidth - dwidth) * 0.5f),
1316                                          Math.round((vheight - dheight) * 0.5f));
1317             } else if (ScaleType.CENTER_CROP == mScaleType) {
1318                 mDrawMatrix = mMatrix;
1319 
1320                 float scale;
1321                 float dx = 0, dy = 0;
1322 
1323                 if (dwidth * vheight > vwidth * dheight) {
1324                     scale = (float) vheight / (float) dheight;
1325                     dx = (vwidth - dwidth * scale) * 0.5f;
1326                 } else {
1327                     scale = (float) vwidth / (float) dwidth;
1328                     dy = (vheight - dheight * scale) * 0.5f;
1329                 }
1330 
1331                 mDrawMatrix.setScale(scale, scale);
1332                 mDrawMatrix.postTranslate(Math.round(dx), Math.round(dy));
1333             } else if (ScaleType.CENTER_INSIDE == mScaleType) {
1334                 mDrawMatrix = mMatrix;
1335                 float scale;
1336                 float dx;
1337                 float dy;
1338 
1339                 if (dwidth <= vwidth && dheight <= vheight) {
1340                     scale = 1.0f;
1341                 } else {
1342                     scale = Math.min((float) vwidth / (float) dwidth,
1343                             (float) vheight / (float) dheight);
1344                 }
1345 
1346                 dx = Math.round((vwidth - dwidth * scale) * 0.5f);
1347                 dy = Math.round((vheight - dheight * scale) * 0.5f);
1348 
1349                 mDrawMatrix.setScale(scale, scale);
1350                 mDrawMatrix.postTranslate(dx, dy);
1351             } else {
1352                 // Generate the required transform.
1353                 mTempSrc.set(0, 0, dwidth, dheight);
1354                 mTempDst.set(0, 0, vwidth, vheight);
1355 
1356                 mDrawMatrix = mMatrix;
1357                 mDrawMatrix.setRectToRect(mTempSrc, mTempDst, scaleTypeToScaleToFit(mScaleType));
1358             }
1359         }
1360     }
1361 
1362     @Override
drawableStateChanged()1363     protected void drawableStateChanged() {
1364         super.drawableStateChanged();
1365 
1366         final Drawable drawable = mDrawable;
1367         if (drawable != null && drawable.isStateful()
1368                 && drawable.setState(getDrawableState())) {
1369             invalidateDrawable(drawable);
1370         }
1371     }
1372 
1373     @Override
drawableHotspotChanged(float x, float y)1374     public void drawableHotspotChanged(float x, float y) {
1375         super.drawableHotspotChanged(x, y);
1376 
1377         if (mDrawable != null) {
1378             mDrawable.setHotspot(x, y);
1379         }
1380     }
1381 
1382     /**
1383      * Applies a temporary transformation {@link Matrix} to the view's drawable when it is drawn.
1384      * Allows custom scaling, translation, and perspective distortion during an animation.
1385      *
1386      * This method is a lightweight analogue of {@link ImageView#setImageMatrix(Matrix)} to use
1387      * only during animations as this matrix will be cleared after the next drawable
1388      * update or view's bounds change.
1389      *
1390      * @param matrix The transformation parameters in matrix form.
1391      */
animateTransform(@ullable Matrix matrix)1392     public void animateTransform(@Nullable Matrix matrix) {
1393         if (mDrawable == null) {
1394             return;
1395         }
1396         if (matrix == null) {
1397             final int vwidth = getWidth() - mPaddingLeft - mPaddingRight;
1398             final int vheight = getHeight() - mPaddingTop - mPaddingBottom;
1399             mDrawable.setBounds(0, 0, vwidth, vheight);
1400             mDrawMatrix = null;
1401         } else {
1402             mDrawable.setBounds(0, 0, mDrawableWidth, mDrawableHeight);
1403             if (mDrawMatrix == null) {
1404                 mDrawMatrix = new Matrix();
1405             }
1406             mDrawMatrix.set(matrix);
1407         }
1408         invalidate();
1409     }
1410 
1411     @Override
onDraw(Canvas canvas)1412     protected void onDraw(Canvas canvas) {
1413         super.onDraw(canvas);
1414 
1415         if (mDrawable == null) {
1416             return; // couldn't resolve the URI
1417         }
1418 
1419         if (mDrawableWidth == 0 || mDrawableHeight == 0) {
1420             return;     // nothing to draw (empty bounds)
1421         }
1422 
1423         if (mDrawMatrix == null && mPaddingTop == 0 && mPaddingLeft == 0) {
1424             mDrawable.draw(canvas);
1425         } else {
1426             final int saveCount = canvas.getSaveCount();
1427             canvas.save();
1428 
1429             if (mCropToPadding) {
1430                 final int scrollX = mScrollX;
1431                 final int scrollY = mScrollY;
1432                 canvas.clipRect(scrollX + mPaddingLeft, scrollY + mPaddingTop,
1433                         scrollX + mRight - mLeft - mPaddingRight,
1434                         scrollY + mBottom - mTop - mPaddingBottom);
1435             }
1436 
1437             canvas.translate(mPaddingLeft, mPaddingTop);
1438 
1439             if (mDrawMatrix != null) {
1440                 canvas.concat(mDrawMatrix);
1441             }
1442             mDrawable.draw(canvas);
1443             canvas.restoreToCount(saveCount);
1444         }
1445     }
1446 
1447     /**
1448      * <p>Return the offset of the widget's text baseline from the widget's top
1449      * boundary. </p>
1450      *
1451      * @return the offset of the baseline within the widget's bounds or -1
1452      *         if baseline alignment is not supported.
1453      */
1454     @Override
1455     @InspectableProperty
1456     @ViewDebug.ExportedProperty(category = "layout")
getBaseline()1457     public int getBaseline() {
1458         if (mBaselineAlignBottom) {
1459             return getMeasuredHeight();
1460         } else {
1461             return mBaseline;
1462         }
1463     }
1464 
1465     /**
1466      * <p>Set the offset of the widget's text baseline from the widget's top
1467      * boundary.  This value is overridden by the {@link #setBaselineAlignBottom(boolean)}
1468      * property.</p>
1469      *
1470      * @param baseline The baseline to use, or -1 if none is to be provided.
1471      *
1472      * @see #setBaseline(int)
1473      * @attr ref android.R.styleable#ImageView_baseline
1474      */
setBaseline(int baseline)1475     public void setBaseline(int baseline) {
1476         if (mBaseline != baseline) {
1477             mBaseline = baseline;
1478             requestLayout();
1479         }
1480     }
1481 
1482     /**
1483      * Sets whether the baseline of this view to the bottom of the view.
1484      * Setting this value overrides any calls to setBaseline.
1485      *
1486      * @param aligned If true, the image view will be baseline aligned by its bottom edge.
1487      *
1488      * @attr ref android.R.styleable#ImageView_baselineAlignBottom
1489      */
setBaselineAlignBottom(boolean aligned)1490     public void setBaselineAlignBottom(boolean aligned) {
1491         if (mBaselineAlignBottom != aligned) {
1492             mBaselineAlignBottom = aligned;
1493             requestLayout();
1494         }
1495     }
1496 
1497     /**
1498      * Checks whether this view's baseline is considered the bottom of the view.
1499      *
1500      * @return True if the ImageView's baseline is considered the bottom of the view, false if otherwise.
1501      * @see #setBaselineAlignBottom(boolean)
1502      */
1503     @InspectableProperty
getBaselineAlignBottom()1504     public boolean getBaselineAlignBottom() {
1505         return mBaselineAlignBottom;
1506     }
1507 
1508     /**
1509      * Sets a tinting option for the image.
1510      *
1511      * @param color Color tint to apply.
1512      * @param mode How to apply the color.  The standard mode is
1513      * {@link PorterDuff.Mode#SRC_ATOP}
1514      *
1515      * @attr ref android.R.styleable#ImageView_tint
1516      */
setColorFilter(int color, PorterDuff.Mode mode)1517     public final void setColorFilter(int color, PorterDuff.Mode mode) {
1518         setColorFilter(new PorterDuffColorFilter(color, mode));
1519     }
1520 
1521     /**
1522      * Set a tinting option for the image. Assumes
1523      * {@link PorterDuff.Mode#SRC_ATOP} blending mode.
1524      *
1525      * @param color Color tint to apply.
1526      * @attr ref android.R.styleable#ImageView_tint
1527      */
1528     @RemotableViewMethod
setColorFilter(int color)1529     public final void setColorFilter(int color) {
1530         setColorFilter(color, PorterDuff.Mode.SRC_ATOP);
1531     }
1532 
1533     /**
1534      * Removes the image's {@link android.graphics.ColorFilter}.
1535      *
1536      * @see #setColorFilter(int)
1537      * @see #getColorFilter()
1538      */
clearColorFilter()1539     public final void clearColorFilter() {
1540         setColorFilter(null);
1541     }
1542 
1543     /**
1544      * @hide Candidate for future API inclusion
1545      */
setXfermode(Xfermode mode)1546     public final void setXfermode(Xfermode mode) {
1547         if (mXfermode != mode) {
1548             mXfermode = mode;
1549             mHasXfermode = true;
1550             applyXfermode();
1551             invalidate();
1552         }
1553     }
1554 
1555     /**
1556      * Returns the active color filter for this ImageView.
1557      *
1558      * @return the active color filter for this ImageView
1559      *
1560      * @see #setColorFilter(android.graphics.ColorFilter)
1561      */
getColorFilter()1562     public ColorFilter getColorFilter() {
1563         return mColorFilter;
1564     }
1565 
1566     /**
1567      * Apply an arbitrary colorfilter to the image.
1568      *
1569      * @param cf the colorfilter to apply (may be null)
1570      *
1571      * @see #getColorFilter()
1572      */
setColorFilter(ColorFilter cf)1573     public void setColorFilter(ColorFilter cf) {
1574         if (mColorFilter != cf) {
1575             mColorFilter = cf;
1576             mHasColorFilter = true;
1577             applyColorFilter();
1578             invalidate();
1579         }
1580     }
1581 
1582     /**
1583      * Returns the alpha that will be applied to the drawable of this ImageView.
1584      *
1585      * @return the alpha value that will be applied to the drawable of this
1586      * ImageView (between 0 and 255 inclusive, with 0 being transparent and
1587      * 255 being opaque)
1588      *
1589      * @see #setImageAlpha(int)
1590      */
getImageAlpha()1591     public int getImageAlpha() {
1592         return mAlpha;
1593     }
1594 
1595     /**
1596      * Sets the alpha value that should be applied to the image.
1597      *
1598      * @param alpha the alpha value that should be applied to the image (between
1599      * 0 and 255 inclusive, with 0 being transparent and 255 being opaque)
1600      *
1601      * @see #getImageAlpha()
1602      */
1603     @RemotableViewMethod
setImageAlpha(int alpha)1604     public void setImageAlpha(int alpha) {
1605         setAlpha(alpha);
1606     }
1607 
1608     /**
1609      * Sets the alpha value that should be applied to the image.
1610      *
1611      * @param alpha the alpha value that should be applied to the image
1612      *
1613      * @deprecated use #setImageAlpha(int) instead
1614      */
1615     @Deprecated
1616     @RemotableViewMethod
setAlpha(int alpha)1617     public void setAlpha(int alpha) {
1618         alpha &= 0xFF;          // keep it legal
1619         if (mAlpha != alpha) {
1620             mAlpha = alpha;
1621             mHasAlpha = true;
1622             applyAlpha();
1623             invalidate();
1624         }
1625     }
1626 
applyXfermode()1627     private void applyXfermode() {
1628         if (mDrawable != null && mHasXfermode) {
1629             mDrawable = mDrawable.mutate();
1630             mDrawable.setXfermode(mXfermode);
1631         }
1632     }
1633 
applyColorFilter()1634     private void applyColorFilter() {
1635         if (mDrawable != null && mHasColorFilter) {
1636             mDrawable = mDrawable.mutate();
1637             mDrawable.setColorFilter(mColorFilter);
1638         }
1639     }
1640 
applyAlpha()1641     private void applyAlpha() {
1642         if (mDrawable != null && mHasAlpha) {
1643             mDrawable = mDrawable.mutate();
1644             mDrawable.setAlpha(mAlpha * mViewAlphaScale >> 8);
1645         }
1646     }
1647 
1648     @Override
isOpaque()1649     public boolean isOpaque() {
1650         return super.isOpaque() || mDrawable != null && mXfermode == null
1651                 && mDrawable.getOpacity() == PixelFormat.OPAQUE
1652                 && mAlpha * mViewAlphaScale >> 8 == 255
1653                 && isFilledByImage();
1654     }
1655 
isFilledByImage()1656     private boolean isFilledByImage() {
1657         if (mDrawable == null) {
1658             return false;
1659         }
1660 
1661         final Rect bounds = mDrawable.getBounds();
1662         final Matrix matrix = mDrawMatrix;
1663         if (matrix == null) {
1664             return bounds.left <= 0 && bounds.top <= 0 && bounds.right >= getWidth()
1665                     && bounds.bottom >= getHeight();
1666         } else if (matrix.rectStaysRect()) {
1667             final RectF boundsSrc = mTempSrc;
1668             final RectF boundsDst = mTempDst;
1669             boundsSrc.set(bounds);
1670             matrix.mapRect(boundsDst, boundsSrc);
1671             return boundsDst.left <= 0 && boundsDst.top <= 0 && boundsDst.right >= getWidth()
1672                     && boundsDst.bottom >= getHeight();
1673         } else {
1674             // If the matrix doesn't map to a rectangle, assume the worst.
1675             return false;
1676         }
1677     }
1678 
1679     @Override
onVisibilityAggregated(boolean isVisible)1680     public void onVisibilityAggregated(boolean isVisible) {
1681         super.onVisibilityAggregated(isVisible);
1682         // Only do this for new apps post-Nougat
1683         if (mDrawable != null && !sCompatDrawableVisibilityDispatch) {
1684             mDrawable.setVisible(isVisible, false);
1685         }
1686     }
1687 
1688     @RemotableViewMethod
1689     @Override
setVisibility(int visibility)1690     public void setVisibility(int visibility) {
1691         super.setVisibility(visibility);
1692         // Only do this for old apps pre-Nougat; new apps use onVisibilityAggregated
1693         if (mDrawable != null && sCompatDrawableVisibilityDispatch) {
1694             mDrawable.setVisible(visibility == VISIBLE, false);
1695         }
1696     }
1697 
1698     @Override
onAttachedToWindow()1699     protected void onAttachedToWindow() {
1700         super.onAttachedToWindow();
1701         // Only do this for old apps pre-Nougat; new apps use onVisibilityAggregated
1702         if (mDrawable != null && sCompatDrawableVisibilityDispatch) {
1703             mDrawable.setVisible(getVisibility() == VISIBLE, false);
1704         }
1705     }
1706 
1707     @Override
onDetachedFromWindow()1708     protected void onDetachedFromWindow() {
1709         super.onDetachedFromWindow();
1710         // Only do this for old apps pre-Nougat; new apps use onVisibilityAggregated
1711         if (mDrawable != null && sCompatDrawableVisibilityDispatch) {
1712             mDrawable.setVisible(false, false);
1713         }
1714     }
1715 
1716     @Override
getAccessibilityClassName()1717     public CharSequence getAccessibilityClassName() {
1718         return ImageView.class.getName();
1719     }
1720 
1721     /** @hide */
1722     @Override
encodeProperties(@onNull ViewHierarchyEncoder stream)1723     protected void encodeProperties(@NonNull ViewHierarchyEncoder stream) {
1724         super.encodeProperties(stream);
1725         stream.addProperty("layout:baseline", getBaseline());
1726     }
1727 
1728     /** @hide */
1729     @Override
1730     @TestApi
isDefaultFocusHighlightNeeded(Drawable background, Drawable foreground)1731     public boolean isDefaultFocusHighlightNeeded(Drawable background, Drawable foreground) {
1732         final boolean lackFocusState = mDrawable == null || !mDrawable.isStateful()
1733                 || !mDrawable.hasFocusStateSpecified();
1734         return super.isDefaultFocusHighlightNeeded(background, foreground) && lackFocusState;
1735     }
1736 }
1737