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