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