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