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