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