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