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