ImageView.java revision f6c763d6a4c84a576ed0ec99b8c22f3550eb39ba
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        if (mUri != null || mResource != resId) {
390            final int oldWidth = mDrawableWidth;
391            final int oldHeight = mDrawableHeight;
392
393            updateDrawable(null);
394            mResource = resId;
395            mUri = null;
396
397            resolveUri();
398
399            if (oldWidth != mDrawableWidth || oldHeight != mDrawableHeight) {
400                requestLayout();
401            }
402            invalidate();
403        }
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    }
532
533    /**
534     * Sets a Bitmap as the content of this ImageView.
535     *
536     * @param bm The bitmap to set
537     */
538    @android.view.RemotableViewMethod
539    public void setImageBitmap(Bitmap bm) {
540        // if this is used frequently, may handle bitmaps explicitly
541        // to reduce the intermediate drawable object
542        setImageDrawable(new BitmapDrawable(mContext.getResources(), bm));
543    }
544
545    public void setImageState(int[] state, boolean merge) {
546        mState = state;
547        mMergeState = merge;
548        if (mDrawable != null) {
549            refreshDrawableState();
550            resizeFromDrawable();
551        }
552    }
553
554    @Override
555    public void setSelected(boolean selected) {
556        super.setSelected(selected);
557        resizeFromDrawable();
558    }
559
560    /**
561     * Sets the image level, when it is constructed from a
562     * {@link android.graphics.drawable.LevelListDrawable}.
563     *
564     * @param level The new level for the image.
565     */
566    @android.view.RemotableViewMethod
567    public void setImageLevel(int level) {
568        mLevel = level;
569        if (mDrawable != null) {
570            mDrawable.setLevel(level);
571            resizeFromDrawable();
572        }
573    }
574
575    /**
576     * Options for scaling the bounds of an image to the bounds of this view.
577     */
578    public enum ScaleType {
579        /**
580         * Scale using the image matrix when drawing. The image matrix can be set using
581         * {@link ImageView#setImageMatrix(Matrix)}. From XML, use this syntax:
582         * <code>android:scaleType="matrix"</code>.
583         */
584        MATRIX      (0),
585        /**
586         * Scale the image using {@link Matrix.ScaleToFit#FILL}.
587         * From XML, use this syntax: <code>android:scaleType="fitXY"</code>.
588         */
589        FIT_XY      (1),
590        /**
591         * Scale the image using {@link Matrix.ScaleToFit#START}.
592         * From XML, use this syntax: <code>android:scaleType="fitStart"</code>.
593         */
594        FIT_START   (2),
595        /**
596         * Scale the image using {@link Matrix.ScaleToFit#CENTER}.
597         * From XML, use this syntax:
598         * <code>android:scaleType="fitCenter"</code>.
599         */
600        FIT_CENTER  (3),
601        /**
602         * Scale the image using {@link Matrix.ScaleToFit#END}.
603         * From XML, use this syntax: <code>android:scaleType="fitEnd"</code>.
604         */
605        FIT_END     (4),
606        /**
607         * Center the image in the view, but perform no scaling.
608         * From XML, use this syntax: <code>android:scaleType="center"</code>.
609         */
610        CENTER      (5),
611        /**
612         * Scale the image uniformly (maintain the image's aspect ratio) so
613         * that both dimensions (width and height) of the image will be equal
614         * to or larger than the corresponding dimension of the view
615         * (minus padding). The image is then centered in the view.
616         * From XML, use this syntax: <code>android:scaleType="centerCrop"</code>.
617         */
618        CENTER_CROP (6),
619        /**
620         * Scale the image uniformly (maintain the image's aspect ratio) so
621         * that both dimensions (width and height) of the image will be equal
622         * to or less than the corresponding dimension of the view
623         * (minus padding). The image is then centered in the view.
624         * From XML, use this syntax: <code>android:scaleType="centerInside"</code>.
625         */
626        CENTER_INSIDE (7);
627
628        ScaleType(int ni) {
629            nativeInt = ni;
630        }
631        final int nativeInt;
632    }
633
634    /**
635     * Controls how the image should be resized or moved to match the size
636     * of this ImageView.
637     *
638     * @param scaleType The desired scaling mode.
639     *
640     * @attr ref android.R.styleable#ImageView_scaleType
641     */
642    public void setScaleType(ScaleType scaleType) {
643        if (scaleType == null) {
644            throw new NullPointerException();
645        }
646
647        if (mScaleType != scaleType) {
648            mScaleType = scaleType;
649
650            setWillNotCacheDrawing(mScaleType == ScaleType.CENTER);
651
652            requestLayout();
653            invalidate();
654        }
655    }
656
657    /**
658     * Return the current scale type in use by this ImageView.
659     *
660     * @see ImageView.ScaleType
661     *
662     * @attr ref android.R.styleable#ImageView_scaleType
663     */
664    public ScaleType getScaleType() {
665        return mScaleType;
666    }
667
668    /** Return the view's optional matrix. This is applied to the
669        view's drawable when it is drawn. If there is no matrix,
670        this method will return an identity matrix.
671        Do not change this matrix in place but make a copy.
672        If you want a different matrix applied to the drawable,
673        be sure to call setImageMatrix().
674    */
675    public Matrix getImageMatrix() {
676        if (mDrawMatrix == null) {
677            return new Matrix(Matrix.IDENTITY_MATRIX);
678        }
679        return mDrawMatrix;
680    }
681
682    public void setImageMatrix(Matrix matrix) {
683        // collaps null and identity to just null
684        if (matrix != null && matrix.isIdentity()) {
685            matrix = null;
686        }
687
688        // don't invalidate unless we're actually changing our matrix
689        if (matrix == null && !mMatrix.isIdentity() ||
690                matrix != null && !mMatrix.equals(matrix)) {
691            mMatrix.set(matrix);
692            configureBounds();
693            invalidate();
694        }
695    }
696
697    /**
698     * Return whether this ImageView crops to padding.
699     *
700     * @return whether this ImageView crops to padding
701     *
702     * @see #setCropToPadding(boolean)
703     *
704     * @attr ref android.R.styleable#ImageView_cropToPadding
705     */
706    public boolean getCropToPadding() {
707        return mCropToPadding;
708    }
709
710    /**
711     * Sets whether this ImageView will crop to padding.
712     *
713     * @param cropToPadding whether this ImageView will crop to padding
714     *
715     * @see #getCropToPadding()
716     *
717     * @attr ref android.R.styleable#ImageView_cropToPadding
718     */
719    public void setCropToPadding(boolean cropToPadding) {
720        if (mCropToPadding != cropToPadding) {
721            mCropToPadding = cropToPadding;
722            requestLayout();
723            invalidate();
724        }
725    }
726
727    private void resolveUri() {
728        if (mDrawable != null) {
729            return;
730        }
731
732        Resources rsrc = getResources();
733        if (rsrc == null) {
734            return;
735        }
736
737        Drawable d = null;
738
739        if (mResource != 0) {
740            try {
741                d = mContext.getDrawable(mResource);
742            } catch (Exception e) {
743                Log.w("ImageView", "Unable to find resource: " + mResource, e);
744                // Don't try again.
745                mUri = null;
746            }
747        } else if (mUri != null) {
748            String scheme = mUri.getScheme();
749            if (ContentResolver.SCHEME_ANDROID_RESOURCE.equals(scheme)) {
750                try {
751                    // Load drawable through Resources, to get the source density information
752                    ContentResolver.OpenResourceIdResult r =
753                            mContext.getContentResolver().getResourceId(mUri);
754                    d = r.r.getDrawable(r.id, mContext.getTheme());
755                } catch (Exception e) {
756                    Log.w("ImageView", "Unable to open content: " + mUri, e);
757                }
758            } else if (ContentResolver.SCHEME_CONTENT.equals(scheme)
759                    || ContentResolver.SCHEME_FILE.equals(scheme)) {
760                InputStream stream = null;
761                try {
762                    stream = mContext.getContentResolver().openInputStream(mUri);
763                    d = Drawable.createFromStream(stream, null);
764                } catch (Exception e) {
765                    Log.w("ImageView", "Unable to open content: " + mUri, e);
766                } finally {
767                    if (stream != null) {
768                        try {
769                            stream.close();
770                        } catch (IOException e) {
771                            Log.w("ImageView", "Unable to close content: " + mUri, e);
772                        }
773                    }
774                }
775        } else {
776                d = Drawable.createFromPath(mUri.toString());
777            }
778
779            if (d == null) {
780                System.out.println("resolveUri failed on bad bitmap uri: " + mUri);
781                // Don't try again.
782                mUri = null;
783            }
784        } else {
785            return;
786        }
787
788        updateDrawable(d);
789    }
790
791    @Override
792    public int[] onCreateDrawableState(int extraSpace) {
793        if (mState == null) {
794            return super.onCreateDrawableState(extraSpace);
795        } else if (!mMergeState) {
796            return mState;
797        } else {
798            return mergeDrawableStates(
799                    super.onCreateDrawableState(extraSpace + mState.length), mState);
800        }
801    }
802
803    private void updateDrawable(Drawable d) {
804        if (mDrawable != null) {
805            mDrawable.setCallback(null);
806            unscheduleDrawable(mDrawable);
807        }
808
809        mDrawable = d;
810
811        if (d != null) {
812            d.setCallback(this);
813            d.setLayoutDirection(getLayoutDirection());
814            if (d.isStateful()) {
815                d.setState(getDrawableState());
816            }
817            d.setVisible(getVisibility() == VISIBLE, true);
818            d.setLevel(mLevel);
819            mDrawableWidth = d.getIntrinsicWidth();
820            mDrawableHeight = d.getIntrinsicHeight();
821            applyImageTint();
822            applyColorMod();
823            configureBounds();
824        } else {
825            mDrawableWidth = mDrawableHeight = -1;
826        }
827    }
828
829    private void resizeFromDrawable() {
830        Drawable d = mDrawable;
831        if (d != null) {
832            int w = d.getIntrinsicWidth();
833            if (w < 0) w = mDrawableWidth;
834            int h = d.getIntrinsicHeight();
835            if (h < 0) h = mDrawableHeight;
836            if (w != mDrawableWidth || h != mDrawableHeight) {
837                mDrawableWidth = w;
838                mDrawableHeight = h;
839                requestLayout();
840            }
841        }
842    }
843
844    @Override
845    public void onRtlPropertiesChanged(int layoutDirection) {
846        super.onRtlPropertiesChanged(layoutDirection);
847
848        if (mDrawable != null) {
849            mDrawable.setLayoutDirection(layoutDirection);
850        }
851    }
852
853    private static final Matrix.ScaleToFit[] sS2FArray = {
854        Matrix.ScaleToFit.FILL,
855        Matrix.ScaleToFit.START,
856        Matrix.ScaleToFit.CENTER,
857        Matrix.ScaleToFit.END
858    };
859
860    private static Matrix.ScaleToFit scaleTypeToScaleToFit(ScaleType st)  {
861        // ScaleToFit enum to their corresponding Matrix.ScaleToFit values
862        return sS2FArray[st.nativeInt - 1];
863    }
864
865    @Override
866    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
867        resolveUri();
868        int w;
869        int h;
870
871        // Desired aspect ratio of the view's contents (not including padding)
872        float desiredAspect = 0.0f;
873
874        // We are allowed to change the view's width
875        boolean resizeWidth = false;
876
877        // We are allowed to change the view's height
878        boolean resizeHeight = false;
879
880        final int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
881        final int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
882
883        if (mDrawable == null) {
884            // If no drawable, its intrinsic size is 0.
885            mDrawableWidth = -1;
886            mDrawableHeight = -1;
887            w = h = 0;
888        } else {
889            w = mDrawableWidth;
890            h = mDrawableHeight;
891            if (w <= 0) w = 1;
892            if (h <= 0) h = 1;
893
894            // We are supposed to adjust view bounds to match the aspect
895            // ratio of our drawable. See if that is possible.
896            if (mAdjustViewBounds) {
897                resizeWidth = widthSpecMode != MeasureSpec.EXACTLY;
898                resizeHeight = heightSpecMode != MeasureSpec.EXACTLY;
899
900                desiredAspect = (float) w / (float) h;
901            }
902        }
903
904        int pleft = mPaddingLeft;
905        int pright = mPaddingRight;
906        int ptop = mPaddingTop;
907        int pbottom = mPaddingBottom;
908
909        int widthSize;
910        int heightSize;
911
912        if (resizeWidth || resizeHeight) {
913            /* If we get here, it means we want to resize to match the
914                drawables aspect ratio, and we have the freedom to change at
915                least one dimension.
916            */
917
918            // Get the max possible width given our constraints
919            widthSize = resolveAdjustedSize(w + pleft + pright, mMaxWidth, widthMeasureSpec);
920
921            // Get the max possible height given our constraints
922            heightSize = resolveAdjustedSize(h + ptop + pbottom, mMaxHeight, heightMeasureSpec);
923
924            if (desiredAspect != 0.0f) {
925                // See what our actual aspect ratio is
926                float actualAspect = (float)(widthSize - pleft - pright) /
927                                        (heightSize - ptop - pbottom);
928
929                if (Math.abs(actualAspect - desiredAspect) > 0.0000001) {
930
931                    boolean done = false;
932
933                    // Try adjusting width to be proportional to height
934                    if (resizeWidth) {
935                        int newWidth = (int)(desiredAspect * (heightSize - ptop - pbottom)) +
936                                pleft + pright;
937
938                        // Allow the width to outgrow its original estimate if height is fixed.
939                        if (!resizeHeight && !mAdjustViewBoundsCompat) {
940                            widthSize = resolveAdjustedSize(newWidth, mMaxWidth, widthMeasureSpec);
941                        }
942
943                        if (newWidth <= widthSize) {
944                            widthSize = newWidth;
945                            done = true;
946                        }
947                    }
948
949                    // Try adjusting height to be proportional to width
950                    if (!done && resizeHeight) {
951                        int newHeight = (int)((widthSize - pleft - pright) / desiredAspect) +
952                                ptop + pbottom;
953
954                        // Allow the height to outgrow its original estimate if width is fixed.
955                        if (!resizeWidth && !mAdjustViewBoundsCompat) {
956                            heightSize = resolveAdjustedSize(newHeight, mMaxHeight,
957                                    heightMeasureSpec);
958                        }
959
960                        if (newHeight <= heightSize) {
961                            heightSize = newHeight;
962                        }
963                    }
964                }
965            }
966        } else {
967            /* We are either don't want to preserve the drawables aspect ratio,
968               or we are not allowed to change view dimensions. Just measure in
969               the normal way.
970            */
971            w += pleft + pright;
972            h += ptop + pbottom;
973
974            w = Math.max(w, getSuggestedMinimumWidth());
975            h = Math.max(h, getSuggestedMinimumHeight());
976
977            widthSize = resolveSizeAndState(w, widthMeasureSpec, 0);
978            heightSize = resolveSizeAndState(h, heightMeasureSpec, 0);
979        }
980
981        setMeasuredDimension(widthSize, heightSize);
982    }
983
984    private int resolveAdjustedSize(int desiredSize, int maxSize,
985                                   int measureSpec) {
986        int result = desiredSize;
987        int specMode = MeasureSpec.getMode(measureSpec);
988        int specSize =  MeasureSpec.getSize(measureSpec);
989        switch (specMode) {
990            case MeasureSpec.UNSPECIFIED:
991                /* Parent says we can be as big as we want. Just don't be larger
992                   than max size imposed on ourselves.
993                */
994                result = Math.min(desiredSize, maxSize);
995                break;
996            case MeasureSpec.AT_MOST:
997                // Parent says we can be as big as we want, up to specSize.
998                // Don't be larger than specSize, and don't be larger than
999                // the max size imposed on ourselves.
1000                result = Math.min(Math.min(desiredSize, specSize), maxSize);
1001                break;
1002            case MeasureSpec.EXACTLY:
1003                // No choice. Do what we are told.
1004                result = specSize;
1005                break;
1006        }
1007        return result;
1008    }
1009
1010    @Override
1011    protected boolean setFrame(int l, int t, int r, int b) {
1012        boolean changed = super.setFrame(l, t, r, b);
1013        mHaveFrame = true;
1014        configureBounds();
1015        return changed;
1016    }
1017
1018    private void configureBounds() {
1019        if (mDrawable == null || !mHaveFrame) {
1020            return;
1021        }
1022
1023        int dwidth = mDrawableWidth;
1024        int dheight = mDrawableHeight;
1025
1026        int vwidth = getWidth() - mPaddingLeft - mPaddingRight;
1027        int vheight = getHeight() - mPaddingTop - mPaddingBottom;
1028
1029        boolean fits = (dwidth < 0 || vwidth == dwidth) &&
1030                       (dheight < 0 || vheight == dheight);
1031
1032        if (dwidth <= 0 || dheight <= 0 || ScaleType.FIT_XY == mScaleType) {
1033            /* If the drawable has no intrinsic size, or we're told to
1034                scaletofit, then we just fill our entire view.
1035            */
1036            mDrawable.setBounds(0, 0, vwidth, vheight);
1037            mDrawMatrix = null;
1038        } else {
1039            // We need to do the scaling ourself, so have the drawable
1040            // use its native size.
1041            mDrawable.setBounds(0, 0, dwidth, dheight);
1042
1043            if (ScaleType.MATRIX == mScaleType) {
1044                // Use the specified matrix as-is.
1045                if (mMatrix.isIdentity()) {
1046                    mDrawMatrix = null;
1047                } else {
1048                    mDrawMatrix = mMatrix;
1049                }
1050            } else if (fits) {
1051                // The bitmap fits exactly, no transform needed.
1052                mDrawMatrix = null;
1053            } else if (ScaleType.CENTER == mScaleType) {
1054                // Center bitmap in view, no scaling.
1055                mDrawMatrix = mMatrix;
1056                mDrawMatrix.setTranslate((int) ((vwidth - dwidth) * 0.5f + 0.5f),
1057                                         (int) ((vheight - dheight) * 0.5f + 0.5f));
1058            } else if (ScaleType.CENTER_CROP == mScaleType) {
1059                mDrawMatrix = mMatrix;
1060
1061                float scale;
1062                float dx = 0, dy = 0;
1063
1064                if (dwidth * vheight > vwidth * dheight) {
1065                    scale = (float) vheight / (float) dheight;
1066                    dx = (vwidth - dwidth * scale) * 0.5f;
1067                } else {
1068                    scale = (float) vwidth / (float) dwidth;
1069                    dy = (vheight - dheight * scale) * 0.5f;
1070                }
1071
1072                mDrawMatrix.setScale(scale, scale);
1073                mDrawMatrix.postTranslate((int) (dx + 0.5f), (int) (dy + 0.5f));
1074            } else if (ScaleType.CENTER_INSIDE == mScaleType) {
1075                mDrawMatrix = mMatrix;
1076                float scale;
1077                float dx;
1078                float dy;
1079
1080                if (dwidth <= vwidth && dheight <= vheight) {
1081                    scale = 1.0f;
1082                } else {
1083                    scale = Math.min((float) vwidth / (float) dwidth,
1084                            (float) vheight / (float) dheight);
1085                }
1086
1087                dx = (int) ((vwidth - dwidth * scale) * 0.5f + 0.5f);
1088                dy = (int) ((vheight - dheight * scale) * 0.5f + 0.5f);
1089
1090                mDrawMatrix.setScale(scale, scale);
1091                mDrawMatrix.postTranslate(dx, dy);
1092            } else {
1093                // Generate the required transform.
1094                mTempSrc.set(0, 0, dwidth, dheight);
1095                mTempDst.set(0, 0, vwidth, vheight);
1096
1097                mDrawMatrix = mMatrix;
1098                mDrawMatrix.setRectToRect(mTempSrc, mTempDst, scaleTypeToScaleToFit(mScaleType));
1099            }
1100        }
1101    }
1102
1103    @Override
1104    protected void drawableStateChanged() {
1105        super.drawableStateChanged();
1106        Drawable d = mDrawable;
1107        if (d != null && d.isStateful()) {
1108            d.setState(getDrawableState());
1109        }
1110    }
1111
1112    @Override
1113    public void drawableHotspotChanged(float x, float y) {
1114        super.drawableHotspotChanged(x, y);
1115
1116        if (mDrawable != null) {
1117            mDrawable.setHotspot(x, y);
1118        }
1119    }
1120
1121    /** @hide */
1122    public void animateTransform(Matrix matrix) {
1123        if (mDrawable == null) {
1124            return;
1125        }
1126        if (matrix == null) {
1127            mDrawable.setBounds(0, 0, getWidth(), getHeight());
1128        } else {
1129            mDrawable.setBounds(0, 0, mDrawableWidth, mDrawableHeight);
1130            if (mDrawMatrix == null) {
1131                mDrawMatrix = new Matrix();
1132            }
1133            mDrawMatrix.set(matrix);
1134        }
1135        invalidate();
1136    }
1137
1138    @Override
1139    protected void onDraw(Canvas canvas) {
1140        super.onDraw(canvas);
1141
1142        if (mDrawable == null) {
1143            return; // couldn't resolve the URI
1144        }
1145
1146        if (mDrawableWidth == 0 || mDrawableHeight == 0) {
1147            return;     // nothing to draw (empty bounds)
1148        }
1149
1150        if (mDrawMatrix == null && mPaddingTop == 0 && mPaddingLeft == 0) {
1151            mDrawable.draw(canvas);
1152        } else {
1153            int saveCount = canvas.getSaveCount();
1154            canvas.save();
1155
1156            if (mCropToPadding) {
1157                final int scrollX = mScrollX;
1158                final int scrollY = mScrollY;
1159                canvas.clipRect(scrollX + mPaddingLeft, scrollY + mPaddingTop,
1160                        scrollX + mRight - mLeft - mPaddingRight,
1161                        scrollY + mBottom - mTop - mPaddingBottom);
1162            }
1163
1164            canvas.translate(mPaddingLeft, mPaddingTop);
1165
1166            if (mDrawMatrix != null) {
1167                canvas.concat(mDrawMatrix);
1168            }
1169            mDrawable.draw(canvas);
1170            canvas.restoreToCount(saveCount);
1171        }
1172    }
1173
1174    /**
1175     * <p>Return the offset of the widget's text baseline from the widget's top
1176     * boundary. </p>
1177     *
1178     * @return the offset of the baseline within the widget's bounds or -1
1179     *         if baseline alignment is not supported.
1180     */
1181    @Override
1182    @ViewDebug.ExportedProperty(category = "layout")
1183    public int getBaseline() {
1184        if (mBaselineAlignBottom) {
1185            return getMeasuredHeight();
1186        } else {
1187            return mBaseline;
1188        }
1189    }
1190
1191    /**
1192     * <p>Set the offset of the widget's text baseline from the widget's top
1193     * boundary.  This value is overridden by the {@link #setBaselineAlignBottom(boolean)}
1194     * property.</p>
1195     *
1196     * @param baseline The baseline to use, or -1 if none is to be provided.
1197     *
1198     * @see #setBaseline(int)
1199     * @attr ref android.R.styleable#ImageView_baseline
1200     */
1201    public void setBaseline(int baseline) {
1202        if (mBaseline != baseline) {
1203            mBaseline = baseline;
1204            requestLayout();
1205        }
1206    }
1207
1208    /**
1209     * Set whether to set the baseline of this view to the bottom of the view.
1210     * Setting this value overrides any calls to setBaseline.
1211     *
1212     * @param aligned If true, the image view will be baseline aligned with
1213     *      based on its bottom edge.
1214     *
1215     * @attr ref android.R.styleable#ImageView_baselineAlignBottom
1216     */
1217    public void setBaselineAlignBottom(boolean aligned) {
1218        if (mBaselineAlignBottom != aligned) {
1219            mBaselineAlignBottom = aligned;
1220            requestLayout();
1221        }
1222    }
1223
1224    /**
1225     * Return whether this view's baseline will be considered the bottom of the view.
1226     *
1227     * @see #setBaselineAlignBottom(boolean)
1228     */
1229    public boolean getBaselineAlignBottom() {
1230        return mBaselineAlignBottom;
1231    }
1232
1233    /**
1234     * Set a tinting option for the image.
1235     *
1236     * @param color Color tint to apply.
1237     * @param mode How to apply the color.  The standard mode is
1238     * {@link PorterDuff.Mode#SRC_ATOP}
1239     *
1240     * @attr ref android.R.styleable#ImageView_tint
1241     */
1242    public final void setColorFilter(int color, PorterDuff.Mode mode) {
1243        setColorFilter(new PorterDuffColorFilter(color, mode));
1244    }
1245
1246    /**
1247     * Set a tinting option for the image. Assumes
1248     * {@link PorterDuff.Mode#SRC_ATOP} blending mode.
1249     *
1250     * @param color Color tint to apply.
1251     * @attr ref android.R.styleable#ImageView_tint
1252     */
1253    @RemotableViewMethod
1254    public final void setColorFilter(int color) {
1255        setColorFilter(color, PorterDuff.Mode.SRC_ATOP);
1256    }
1257
1258    public final void clearColorFilter() {
1259        setColorFilter(null);
1260    }
1261
1262    /**
1263     * @hide Candidate for future API inclusion
1264     */
1265    public final void setXfermode(Xfermode mode) {
1266        if (mXfermode != mode) {
1267            mXfermode = mode;
1268            mColorMod = true;
1269            applyColorMod();
1270            invalidate();
1271        }
1272    }
1273
1274    /**
1275     * Returns the active color filter for this ImageView.
1276     *
1277     * @return the active color filter for this ImageView
1278     *
1279     * @see #setColorFilter(android.graphics.ColorFilter)
1280     */
1281    public ColorFilter getColorFilter() {
1282        return mColorFilter;
1283    }
1284
1285    /**
1286     * Apply an arbitrary colorfilter to the image.
1287     *
1288     * @param cf the colorfilter to apply (may be null)
1289     *
1290     * @see #getColorFilter()
1291     */
1292    public void setColorFilter(ColorFilter cf) {
1293        if (mColorFilter != cf) {
1294            mColorFilter = cf;
1295            mHasColorFilter = true;
1296            mColorMod = true;
1297            applyColorMod();
1298            invalidate();
1299        }
1300    }
1301
1302    /**
1303     * Returns the alpha that will be applied to the drawable of this ImageView.
1304     *
1305     * @return the alpha that will be applied to the drawable of this ImageView
1306     *
1307     * @see #setImageAlpha(int)
1308     */
1309    public int getImageAlpha() {
1310        return mAlpha;
1311    }
1312
1313    /**
1314     * Sets the alpha value that should be applied to the image.
1315     *
1316     * @param alpha the alpha value that should be applied to the image
1317     *
1318     * @see #getImageAlpha()
1319     */
1320    @RemotableViewMethod
1321    public void setImageAlpha(int alpha) {
1322        setAlpha(alpha);
1323    }
1324
1325    /**
1326     * Sets the alpha value that should be applied to the image.
1327     *
1328     * @param alpha the alpha value that should be applied to the image
1329     *
1330     * @deprecated use #setImageAlpha(int) instead
1331     */
1332    @Deprecated
1333    @RemotableViewMethod
1334    public void setAlpha(int alpha) {
1335        alpha &= 0xFF;          // keep it legal
1336        if (mAlpha != alpha) {
1337            mAlpha = alpha;
1338            mColorMod = true;
1339            applyColorMod();
1340            invalidate();
1341        }
1342    }
1343
1344    private void applyColorMod() {
1345        // Only mutate and apply when modifications have occurred. This should
1346        // not reset the mColorMod flag, since these filters need to be
1347        // re-applied if the Drawable is changed.
1348        if (mDrawable != null && mColorMod) {
1349            mDrawable = mDrawable.mutate();
1350            if (mHasColorFilter) {
1351                mDrawable.setColorFilter(mColorFilter);
1352            }
1353            mDrawable.setXfermode(mXfermode);
1354            mDrawable.setAlpha(mAlpha * mViewAlphaScale >> 8);
1355        }
1356    }
1357
1358    @Override
1359    public boolean isOpaque() {
1360        return super.isOpaque() || mDrawable != null && mXfermode == null
1361                && mDrawable.getOpacity() == PixelFormat.OPAQUE
1362                && mAlpha * mViewAlphaScale >> 8 == 255
1363                && isFilledByImage();
1364    }
1365
1366    private boolean isFilledByImage() {
1367        if (mDrawable == null) {
1368            return false;
1369        }
1370
1371        final Rect bounds = mDrawable.getBounds();
1372        final Matrix matrix = mDrawMatrix;
1373        if (matrix == null) {
1374            return bounds.left <= 0 && bounds.top <= 0 && bounds.right >= getWidth()
1375                    && bounds.bottom >= getHeight();
1376        } else if (matrix.rectStaysRect()) {
1377            final RectF boundsSrc = mTempSrc;
1378            final RectF boundsDst = mTempDst;
1379            boundsSrc.set(bounds);
1380            matrix.mapRect(boundsDst, boundsSrc);
1381            return boundsDst.left <= 0 && boundsDst.top <= 0 && boundsDst.right >= getWidth()
1382                    && boundsDst.bottom >= getHeight();
1383        } else {
1384            // If the matrix doesn't map to a rectangle, assume the worst.
1385            return false;
1386        }
1387    }
1388
1389    @RemotableViewMethod
1390    @Override
1391    public void setVisibility(int visibility) {
1392        super.setVisibility(visibility);
1393        if (mDrawable != null) {
1394            mDrawable.setVisible(visibility == VISIBLE, false);
1395        }
1396    }
1397
1398    @Override
1399    protected void onAttachedToWindow() {
1400        super.onAttachedToWindow();
1401        if (mDrawable != null) {
1402            mDrawable.setVisible(getVisibility() == VISIBLE, false);
1403        }
1404    }
1405
1406    @Override
1407    protected void onDetachedFromWindow() {
1408        super.onDetachedFromWindow();
1409        if (mDrawable != null) {
1410            mDrawable.setVisible(false, false);
1411        }
1412    }
1413
1414    @Override
1415    public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
1416        super.onInitializeAccessibilityEvent(event);
1417        event.setClassName(ImageView.class.getName());
1418    }
1419
1420    @Override
1421    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
1422        super.onInitializeAccessibilityNodeInfo(info);
1423        info.setClassName(ImageView.class.getName());
1424    }
1425}
1426