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