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