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