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