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