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