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