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