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