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