ImageView.java revision d5edc7721791ad807b9a8fbd923b8d6e73c399cc
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
793                        // Allow the width to outgrow its original estimate if height is fixed.
794                        if (!resizeHeight) {
795                            widthSize = resolveAdjustedSize(newWidth, mMaxWidth, widthMeasureSpec);
796                        }
797
798                        if (newWidth <= widthSize) {
799                            widthSize = newWidth;
800                            done = true;
801                        }
802                    }
803
804                    // Try adjusting height to be proportional to width
805                    if (!done && resizeHeight) {
806                        int newHeight = (int)((widthSize - pleft - pright) / desiredAspect) +
807                                ptop + pbottom;
808
809                        // Allow the height to outgrow its original estimate if width is fixed.
810                        if (!resizeWidth) {
811                            heightSize = resolveAdjustedSize(newHeight, mMaxHeight,
812                                    heightMeasureSpec);
813                        }
814
815                        if (newHeight <= heightSize) {
816                            heightSize = newHeight;
817                        }
818                    }
819                }
820            }
821        } else {
822            /* We are either don't want to preserve the drawables aspect ratio,
823               or we are not allowed to change view dimensions. Just measure in
824               the normal way.
825            */
826            w += pleft + pright;
827            h += ptop + pbottom;
828
829            w = Math.max(w, getSuggestedMinimumWidth());
830            h = Math.max(h, getSuggestedMinimumHeight());
831
832            widthSize = resolveSizeAndState(w, widthMeasureSpec, 0);
833            heightSize = resolveSizeAndState(h, heightMeasureSpec, 0);
834        }
835
836        setMeasuredDimension(widthSize, heightSize);
837    }
838
839    private int resolveAdjustedSize(int desiredSize, int maxSize,
840                                   int measureSpec) {
841        int result = desiredSize;
842        int specMode = MeasureSpec.getMode(measureSpec);
843        int specSize =  MeasureSpec.getSize(measureSpec);
844        switch (specMode) {
845            case MeasureSpec.UNSPECIFIED:
846                /* Parent says we can be as big as we want. Just don't be larger
847                   than max size imposed on ourselves.
848                */
849                result = Math.min(desiredSize, maxSize);
850                break;
851            case MeasureSpec.AT_MOST:
852                // Parent says we can be as big as we want, up to specSize.
853                // Don't be larger than specSize, and don't be larger than
854                // the max size imposed on ourselves.
855                result = Math.min(Math.min(desiredSize, specSize), maxSize);
856                break;
857            case MeasureSpec.EXACTLY:
858                // No choice. Do what we are told.
859                result = specSize;
860                break;
861        }
862        return result;
863    }
864
865    @Override
866    protected boolean setFrame(int l, int t, int r, int b) {
867        boolean changed = super.setFrame(l, t, r, b);
868        mHaveFrame = true;
869        configureBounds();
870        return changed;
871    }
872
873    private void configureBounds() {
874        if (mDrawable == null || !mHaveFrame) {
875            return;
876        }
877
878        int dwidth = mDrawableWidth;
879        int dheight = mDrawableHeight;
880
881        int vwidth = getWidth() - mPaddingLeft - mPaddingRight;
882        int vheight = getHeight() - mPaddingTop - mPaddingBottom;
883
884        boolean fits = (dwidth < 0 || vwidth == dwidth) &&
885                       (dheight < 0 || vheight == dheight);
886
887        if (dwidth <= 0 || dheight <= 0 || ScaleType.FIT_XY == mScaleType) {
888            /* If the drawable has no intrinsic size, or we're told to
889                scaletofit, then we just fill our entire view.
890            */
891            mDrawable.setBounds(0, 0, vwidth, vheight);
892            mDrawMatrix = null;
893        } else {
894            // We need to do the scaling ourself, so have the drawable
895            // use its native size.
896            mDrawable.setBounds(0, 0, dwidth, dheight);
897
898            if (ScaleType.MATRIX == mScaleType) {
899                // Use the specified matrix as-is.
900                if (mMatrix.isIdentity()) {
901                    mDrawMatrix = null;
902                } else {
903                    mDrawMatrix = mMatrix;
904                }
905            } else if (fits) {
906                // The bitmap fits exactly, no transform needed.
907                mDrawMatrix = null;
908            } else if (ScaleType.CENTER == mScaleType) {
909                // Center bitmap in view, no scaling.
910                mDrawMatrix = mMatrix;
911                mDrawMatrix.setTranslate((int) ((vwidth - dwidth) * 0.5f + 0.5f),
912                                         (int) ((vheight - dheight) * 0.5f + 0.5f));
913            } else if (ScaleType.CENTER_CROP == mScaleType) {
914                mDrawMatrix = mMatrix;
915
916                float scale;
917                float dx = 0, dy = 0;
918
919                if (dwidth * vheight > vwidth * dheight) {
920                    scale = (float) vheight / (float) dheight;
921                    dx = (vwidth - dwidth * scale) * 0.5f;
922                } else {
923                    scale = (float) vwidth / (float) dwidth;
924                    dy = (vheight - dheight * scale) * 0.5f;
925                }
926
927                mDrawMatrix.setScale(scale, scale);
928                mDrawMatrix.postTranslate((int) (dx + 0.5f), (int) (dy + 0.5f));
929            } else if (ScaleType.CENTER_INSIDE == mScaleType) {
930                mDrawMatrix = mMatrix;
931                float scale;
932                float dx;
933                float dy;
934
935                if (dwidth <= vwidth && dheight <= vheight) {
936                    scale = 1.0f;
937                } else {
938                    scale = Math.min((float) vwidth / (float) dwidth,
939                            (float) vheight / (float) dheight);
940                }
941
942                dx = (int) ((vwidth - dwidth * scale) * 0.5f + 0.5f);
943                dy = (int) ((vheight - dheight * scale) * 0.5f + 0.5f);
944
945                mDrawMatrix.setScale(scale, scale);
946                mDrawMatrix.postTranslate(dx, dy);
947            } else {
948                // Generate the required transform.
949                mTempSrc.set(0, 0, dwidth, dheight);
950                mTempDst.set(0, 0, vwidth, vheight);
951
952                mDrawMatrix = mMatrix;
953                mDrawMatrix.setRectToRect(mTempSrc, mTempDst, scaleTypeToScaleToFit(mScaleType));
954            }
955        }
956    }
957
958    @Override
959    protected void drawableStateChanged() {
960        super.drawableStateChanged();
961        Drawable d = mDrawable;
962        if (d != null && d.isStateful()) {
963            d.setState(getDrawableState());
964        }
965    }
966
967    @Override
968    protected void onDraw(Canvas canvas) {
969        super.onDraw(canvas);
970
971        if (mDrawable == null) {
972            return; // couldn't resolve the URI
973        }
974
975        if (mDrawableWidth == 0 || mDrawableHeight == 0) {
976            return;     // nothing to draw (empty bounds)
977        }
978
979        if (mDrawMatrix == null && mPaddingTop == 0 && mPaddingLeft == 0) {
980            mDrawable.draw(canvas);
981        } else {
982            int saveCount = canvas.getSaveCount();
983            canvas.save();
984
985            if (mCropToPadding) {
986                final int scrollX = mScrollX;
987                final int scrollY = mScrollY;
988                canvas.clipRect(scrollX + mPaddingLeft, scrollY + mPaddingTop,
989                        scrollX + mRight - mLeft - mPaddingRight,
990                        scrollY + mBottom - mTop - mPaddingBottom);
991            }
992
993            canvas.translate(mPaddingLeft, mPaddingTop);
994
995            if (mDrawMatrix != null) {
996                canvas.concat(mDrawMatrix);
997            }
998            mDrawable.draw(canvas);
999            canvas.restoreToCount(saveCount);
1000        }
1001    }
1002
1003    /**
1004     * <p>Return the offset of the widget's text baseline from the widget's top
1005     * boundary. </p>
1006     *
1007     * @return the offset of the baseline within the widget's bounds or -1
1008     *         if baseline alignment is not supported.
1009     */
1010    @Override
1011    @ViewDebug.ExportedProperty(category = "layout")
1012    public int getBaseline() {
1013        if (mBaselineAlignBottom) {
1014            return getMeasuredHeight();
1015        } else {
1016            return mBaseline;
1017        }
1018    }
1019
1020    /**
1021     * <p>Set the offset of the widget's text baseline from the widget's top
1022     * boundary.  This value is overridden by the {@link #setBaselineAlignBottom(boolean)}
1023     * property.</p>
1024     *
1025     * @param baseline The baseline to use, or -1 if none is to be provided.
1026     *
1027     * @see #setBaseline(int)
1028     * @attr ref android.R.styleable#ImageView_baseline
1029     */
1030    public void setBaseline(int baseline) {
1031        if (mBaseline != baseline) {
1032            mBaseline = baseline;
1033            requestLayout();
1034        }
1035    }
1036
1037    /**
1038     * Set whether to set the baseline of this view to the bottom of the view.
1039     * Setting this value overrides any calls to setBaseline.
1040     *
1041     * @param aligned If true, the image view will be baseline aligned with
1042     *      based on its bottom edge.
1043     *
1044     * @attr ref android.R.styleable#ImageView_baselineAlignBottom
1045     */
1046    public void setBaselineAlignBottom(boolean aligned) {
1047        if (mBaselineAlignBottom != aligned) {
1048            mBaselineAlignBottom = aligned;
1049            requestLayout();
1050        }
1051    }
1052
1053    /**
1054     * Return whether this view's baseline will be considered the bottom of the view.
1055     *
1056     * @see #setBaselineAlignBottom(boolean)
1057     */
1058    public boolean getBaselineAlignBottom() {
1059        return mBaselineAlignBottom;
1060    }
1061
1062    /**
1063     * Set a tinting option for the image.
1064     *
1065     * @param color Color tint to apply.
1066     * @param mode How to apply the color.  The standard mode is
1067     * {@link PorterDuff.Mode#SRC_ATOP}
1068     *
1069     * @attr ref android.R.styleable#ImageView_tint
1070     */
1071    public final void setColorFilter(int color, PorterDuff.Mode mode) {
1072        setColorFilter(new PorterDuffColorFilter(color, mode));
1073    }
1074
1075    /**
1076     * Set a tinting option for the image. Assumes
1077     * {@link PorterDuff.Mode#SRC_ATOP} blending mode.
1078     *
1079     * @param color Color tint to apply.
1080     * @attr ref android.R.styleable#ImageView_tint
1081     */
1082    @RemotableViewMethod
1083    public final void setColorFilter(int color) {
1084        setColorFilter(color, PorterDuff.Mode.SRC_ATOP);
1085    }
1086
1087    public final void clearColorFilter() {
1088        setColorFilter(null);
1089    }
1090
1091    /**
1092     * Returns the active color filter for this ImageView.
1093     *
1094     * @return the active color filter for this ImageView
1095     *
1096     * @see #setColorFilter(android.graphics.ColorFilter)
1097     */
1098    public ColorFilter getColorFilter() {
1099        return mColorFilter;
1100    }
1101
1102    /**
1103     * Apply an arbitrary colorfilter to the image.
1104     *
1105     * @param cf the colorfilter to apply (may be null)
1106     *
1107     * @see #getColorFilter()
1108     */
1109    public void setColorFilter(ColorFilter cf) {
1110        if (mColorFilter != cf) {
1111            mColorFilter = cf;
1112            mColorMod = true;
1113            applyColorMod();
1114            invalidate();
1115        }
1116    }
1117
1118    /**
1119     * Returns the alpha that will be applied to the drawable of this ImageView.
1120     *
1121     * @return the alpha that will be applied to the drawable of this ImageView
1122     *
1123     * @see #setImageAlpha(int)
1124     */
1125    public int getImageAlpha() {
1126        return mAlpha;
1127    }
1128
1129    /**
1130     * Sets the alpha value that should be applied to the image.
1131     *
1132     * @param alpha the alpha value that should be applied to the image
1133     *
1134     * @see #getImageAlpha()
1135     */
1136    @RemotableViewMethod
1137    public void setImageAlpha(int alpha) {
1138        setAlpha(alpha);
1139    }
1140
1141    /**
1142     * Sets the alpha value that should be applied to the image.
1143     *
1144     * @param alpha the alpha value that should be applied to the image
1145     *
1146     * @deprecated use #setImageAlpha(int) instead
1147     */
1148    @Deprecated
1149    @RemotableViewMethod
1150    public void setAlpha(int alpha) {
1151        alpha &= 0xFF;          // keep it legal
1152        if (mAlpha != alpha) {
1153            mAlpha = alpha;
1154            mColorMod = true;
1155            applyColorMod();
1156            invalidate();
1157        }
1158    }
1159
1160    private void applyColorMod() {
1161        // Only mutate and apply when modifications have occurred. This should
1162        // not reset the mColorMod flag, since these filters need to be
1163        // re-applied if the Drawable is changed.
1164        if (mDrawable != null && mColorMod) {
1165            mDrawable = mDrawable.mutate();
1166            mDrawable.setColorFilter(mColorFilter);
1167            mDrawable.setAlpha(mAlpha * mViewAlphaScale >> 8);
1168        }
1169    }
1170
1171    @RemotableViewMethod
1172    @Override
1173    public void setVisibility(int visibility) {
1174        super.setVisibility(visibility);
1175        if (mDrawable != null) {
1176            mDrawable.setVisible(visibility == VISIBLE, false);
1177        }
1178    }
1179
1180    @Override
1181    protected void onAttachedToWindow() {
1182        super.onAttachedToWindow();
1183        if (mDrawable != null) {
1184            mDrawable.setVisible(getVisibility() == VISIBLE, false);
1185        }
1186    }
1187
1188    @Override
1189    protected void onDetachedFromWindow() {
1190        super.onDetachedFromWindow();
1191        if (mDrawable != null) {
1192            mDrawable.setVisible(false, false);
1193        }
1194    }
1195
1196    @Override
1197    public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
1198        super.onInitializeAccessibilityEvent(event);
1199        event.setClassName(ImageView.class.getName());
1200    }
1201
1202    @Override
1203    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
1204        super.onInitializeAccessibilityNodeInfo(info);
1205        info.setClassName(ImageView.class.getName());
1206    }
1207}
1208