Home | History | Annotate | Download | only in widget
      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 
     17 package android.widget;
     18 
     19 import android.annotation.Nullable;
     20 import android.content.ContentResolver;
     21 import android.content.Context;
     22 import android.content.res.ColorStateList;
     23 import android.content.res.Resources;
     24 import android.content.res.TypedArray;
     25 import android.graphics.Bitmap;
     26 import android.graphics.Canvas;
     27 import android.graphics.ColorFilter;
     28 import android.graphics.Matrix;
     29 import android.graphics.PixelFormat;
     30 import android.graphics.PorterDuff;
     31 import android.graphics.PorterDuffColorFilter;
     32 import android.graphics.Rect;
     33 import android.graphics.RectF;
     34 import android.graphics.Xfermode;
     35 import android.graphics.drawable.BitmapDrawable;
     36 import android.graphics.drawable.Drawable;
     37 import android.net.Uri;
     38 import android.os.Build;
     39 import android.text.TextUtils;
     40 import android.util.AttributeSet;
     41 import android.util.Log;
     42 import android.view.RemotableViewMethod;
     43 import android.view.View;
     44 import android.view.ViewDebug;
     45 import android.view.accessibility.AccessibilityEvent;
     46 import android.view.accessibility.AccessibilityNodeInfo;
     47 import android.widget.RemoteViews.RemoteView;
     48 
     49 import com.android.internal.R;
     50 
     51 import java.io.IOException;
     52 import 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
     70 public 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