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