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