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