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