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