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