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