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