Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> ImageView的源碼解讀,以及幾種ScaleType的分析

ImageView的源碼解讀,以及幾種ScaleType的分析

編輯:關於Android編程

前言

ImageView是android開發中非常常用的一種控件,在顯示圖片時,我們可以直接拿來用,也可以根據使用場景,結合幾種不同的顯示方式ScaleType,來對顯示的圖片進行圖片縮放規則的定制。

實例分析ScaleType可見文章後半部分

源碼分析

ImageView直接繼承於View,路徑是android.widget.ImageView。

構造函數如下:

public ImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
        int defStyleRes) {
    super(context, attrs, defStyleAttr, defStyleRes);

    initImageView();

    //省略部分屬性初始化代碼   

    final int index = a.getInt(com.android.internal.R.styleable.ImageView_scaleType, -1);
    if (index >= 0) {
        setScaleType(sScaleTypeArray[index]);
    }

    //省略部分代碼,tint相關

    applyImageTint();

    //省略部分屬性初始化代碼

    a.recycle();
}

構造函數中使用initImageView( )初始化mMatrix 和 mScaleType ,applyImageTint( )設置著色,同時對一些屬性進行初始化。initImageView( )方法如下:

private void initImageView() {
    mMatrix     = new Matrix();
    mScaleType  = ScaleType.FIT_CENTER;
    mAdjustViewBoundsCompat = mContext.getApplicationInfo().targetSdkVersion <=
            Build.VERSION_CODES.JELLY_BEAN_MR1;
}

ImageView中根據不同的ScaleType模式,使用Matrix進行圖像變換,其中mMatrix 是動態設置的Matrix,而mDrawMatrix是最終應用到圖像中的matrix。

這裡詳細說明一下ScaleType,ScaleType是ImageView的內部枚舉類,其功能是為ImageView提供8中圖片顯示方式,在ImageView的 configureBounds( ) 方法使用了ScaleType作為”標志位”來更改matrix,並在onDraw()中調用canvas.concat(mDrawMatrix)最終將matrix應用到canvas上。

ScaleType 源碼:

/**
 * Options for scaling the bounds of an image to the bounds of this view.
 */
public enum ScaleType {
    /**
     * Scale using the image matrix when drawing. The image matrix can be set using
     * {@link ImageView#setImageMatrix(Matrix)}. From XML, use this syntax:
     * android:scaleType="matrix".
     */
    MATRIX      (0),
    /**
     * Scale the image using {@link Matrix.ScaleToFit#FILL}.
     * From XML, use this syntax: android:scaleType="fitXY".
     */
    FIT_XY      (1),
    /**
     * Scale the image using {@link Matrix.ScaleToFit#START}.
     * From XML, use this syntax: android:scaleType="fitStart".
     */
    FIT_START   (2),
    /**
     * Scale the image using {@link Matrix.ScaleToFit#CENTER}.
     * From XML, use this syntax:
     * android:scaleType="fitCenter".
     */
    FIT_CENTER  (3),
    /**
     * Scale the image using {@link Matrix.ScaleToFit#END}.
     * From XML, use this syntax: android:scaleType="fitEnd".
     */
    FIT_END     (4),
    /**
     * Center the image in the view, but perform no scaling.
     * From XML, use this syntax: android:scaleType="center".
     */
    CENTER      (5),
    /**
     * Scale the image uniformly (maintain the image's aspect ratio) so
     * that both dimensions (width and height) of the image will be equal
     * to or larger than the corresponding dimension of the view
     * (minus padding). The image is then centered in the view.
     * From XML, use this syntax: android:scaleType="centerCrop".
     */
    CENTER_CROP (6),
    /**
     * Scale the image uniformly (maintain the image's aspect ratio) so
     * that both dimensions (width and height) of the image will be equal
     * to or less than the corresponding dimension of the view
     * (minus padding). The image is then centered in the view.
     * From XML, use this syntax: android:scaleType="centerInside".
     */
    CENTER_INSIDE (7);

    ScaleType(int ni) {
        nativeInt = ni;
    }
    final int nativeInt;
}

configureBounds( )函數源碼及注釋:

private void configureBounds() {
    if (mDrawable == null || !mHaveFrame) {
        return;
    }

    int dwidth = mDrawableWidth;
    int dheight = mDrawableHeight;

    int vwidth = getWidth() - mPaddingLeft - mPaddingRight;
    int vheight = getHeight() - mPaddingTop - mPaddingBottom;

    boolean fits = (dwidth < 0 || vwidth == dwidth) &&
                   (dheight < 0 || vheight == dheight);

    if (dwidth <= 0 || dheight <= 0 || ScaleType.FIT_XY == mScaleType) {
        /* If the drawable has no intrinsic size, or we're told to
            scaletofit, then we just fill our entire view.
        */
        mDrawable.setBounds(0, 0, vwidth, vheight);
        mDrawMatrix = null;
    } else {
        // We need to do the scaling ourself, so have the drawable
        // use its native size.
        mDrawable.setBounds(0, 0, dwidth, dheight);

          //如果ScaleType 是MATRIX 類型
        if (ScaleType.MATRIX == mScaleType) {
            // Use the specified matrix as-is.
            if (mMatrix.isIdentity()) {
            //如果mMatrix是單位矩陣,即矩陣的左上-右下對角線上的值為1,其它值均為0
            //這說明矩陣為初始化時的矩陣,應用到canvas上將沒有任何變化。
                mDrawMatrix = null;
            } else {
            //mMatrix非單位矩陣,需要將其應用到canvas上
                mDrawMatrix = mMatrix;
            }
        } else if (fits) {
            //如果ScaleType 不是MATRIX 類型 ,並且圖片大小和imageView正好相同,那麼也不需要做matrix變換
            // The bitmap fits exactly, no transform needed.
            mDrawMatrix = null;
        } else if (ScaleType.CENTER == mScaleType) {
            //如果ScaleType是CENTER類型,圖片不進行放大縮小,只將圖片中心與ImageView中心重合
            // Center bitmap in view, no scaling.
            mDrawMatrix = mMatrix;
           //默認從ImageView的(0,0)點開始畫,
           //現在將其向右移動(ImageView寬-圖片寬)/2的距離,向下移動(ImageView高-圖片高)/2的距離
            mDrawMatrix.setTranslate(Math.round((vwidth - dwidth) * 0.5f),
                                     Math.round((vheight - dheight) * 0.5f));
        } else if (ScaleType.CENTER_CROP == mScaleType) {
            //如果是CENTER_CROP類型,也就是中心重合,並將圖片裁剪,圖片寬高伸縮相同比例
            mDrawMatrix = mMatrix;

            float scale;
            float dx = 0, dy = 0;
            //這裡是通過(dwidth/dheight > vwidth/vheight)變換而來的,可將其理解為:
            //如果(圖片的寬:圖像的高 > ImageView的寬:ImageView的高),則根據(ImageView的高:圖片的高)的值來對圖像的寬和高一起縮放。
            //這裡如果不好理解,可以畫圖來琢磨一下其中的原理。
            if (dwidth * vheight > vwidth * dheight) {
                //圖片寬高的縮放尺寸(寬高縮放尺寸相同)
                scale = (float) vheight / (float) dheight; 
                //圖片的水平平移,此尺寸是按照放大後的尺寸來計算的
                dx = (vwidth - dwidth * scale) * 0.5f;
            } else {//else同理,不多講了
                scale = (float) vwidth / (float) dwidth;
                dy = (vheight - dheight * scale) * 0.5f;
            }
          //先進行放大
            mDrawMatrix.setScale(scale, scale);
          //再進行平移
            mDrawMatrix.postTranslate(Math.round(dx), Math.round(dy));
          //知乎、一覽等app開場動畫是一張圖片居中顯示,並將其緩緩放大,這種動畫的實現原理就是上述這種方式,具體可看我做的一個view
        } else if (ScaleType.CENTER_INSIDE == mScaleType) {
        //圖片居中,並完全顯示在內部,這種模式下,圖片將完全顯示,不會被“裁剪”,且寬高保持原始比例

            mDrawMatrix = mMatrix;
            float scale;
            float dx;
            float dy;

            if (dwidth <= vwidth && dheight <= vheight) {
               //如果圖片寬高均小於ImageView的寬高,則放大尺寸為1
                scale = 1.0f;
            } else {
                //如果圖片寬高原始尺寸有一個超出了ImageView的范圍,
                //則將按照(ImageView寬:圖片寬)與(ImageView高:圖片高)的最小值進行縮放,使圖片充滿ImageView。
                //換句話說,如果(ImageView寬:圖片寬) < (ImageView高:圖片高), 
                //那麼以(ImageView寬:圖片寬)比例同時伸縮圖片,並居中顯示,
                //效果就是圖片上下留白,左右充滿ImageView。反之同理。
               scale = Math.min((float) vwidth / (float) dwidth,
                        (float) vheight / (float) dheight);
            }

            //計算平移距離,用以居中
            dx = Math.round((vwidth - dwidth * scale) * 0.5f);
            dy = Math.round((vheight - dheight * scale) * 0.5f);
            //縮放比例
            mDrawMatrix.setScale(scale, scale);
            //居中顯示
            mDrawMatrix.postTranslate(dx, dy);
        } else {
            // Generate the required transform.
            mTempSrc.set(0, 0, dwidth, dheight);
            mTempDst.set(0, 0, vwidth, vheight);

            mDrawMatrix = mMatrix;
            mDrawMatrix.setRectToRect(mTempSrc, mTempDst, scaleTypeToScaleToFit(mScaleType));
        }
    }
}

onDraw()函數的源碼如下:

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);

    if (mDrawable == null) {
        return; // couldn't resolve the URI
    }

    if (mDrawableWidth == 0 || mDrawableHeight == 0) {
        return;     // nothing to draw (empty bounds)
    }

    if (mDrawMatrix == null && mPaddingTop == 0 && mPaddingLeft == 0) {
        mDrawable.draw(canvas);
    } else {
        int saveCount = canvas.getSaveCount();
        canvas.save();

        if (mCropToPadding) {
            final int scrollX = mScrollX;
            final int scrollY = mScrollY;
            canvas.clipRect(scrollX + mPaddingLeft, scrollY + mPaddingTop,
                    scrollX + mRight - mLeft - mPaddingRight,
                    scrollY + mBottom - mTop - mPaddingBottom);
        }

        canvas.translate(mPaddingLeft, mPaddingTop);

        if (mDrawMatrix != null) {
            //將mDrawMatrix矩陣通過preConcat的方式應用到canvas上
            canvas.concat(mDrawMatrix);
        }
        mDrawable.draw(canvas);
        canvas.restoreToCount(saveCount);
    }
}

示例總結ScaleType的幾種模式

說明:使用兩張圖片作為示例,其中,
- ImageView的大小為 500x500
- 圖片蒙娜麗莎的大小為 268x413
- 圖片星空的大小為 751x600
- 藍色邊框為ImageView區域

0.Matrix

  矩陣模式,在這種模式下,可以在外部設置ImageView的matrix顯示效果,matrix是3x3的矩陣,可以通過matrix.setValues(float[] value)傳入一個長度為9的float數組為Matrix進行賦值,其中:
  (1)value[0]為x方向的放大系數,1為原始尺寸。
  (2)value[4]為y方向的放大系數,1為原始尺寸。
  (3)value[2]是x軸方向的位移,當其小於0時,代表將圖片相對於ImageView本身向右移動,也就是顯示圖片偏右的內容,value[2]的數值以原圖實際大小*value[0]後的數值作為參照。
  (4)value[5]是y軸方向的位移,當其小於0時,代表將圖片相對於ImageView本身向上移動,也就是顯示圖片偏下的內容,value[5]的數值以原圖實際大小*value[4]後的數值作為參照。

  
matrix
0. Matrix模式,默認顯示位置。默認圖像未被拉伸縮放

 

1.FIT_XY

  將圖片拉伸/縮放至充滿ImageView的模式,即使得圖片的寬高等於ImageView的寬高,如果ImageView與圖片的寬高比例不同,則圖片不會保持原寬高比例,一般在項目中很少使用。

 
center
1. fit_xy模式,ImageView:500x500 Bitmap:268x413

 

2.FIT_START

  將圖片拉伸/縮放,圖片保持原寬高比例:
  (1)如果(圖片寬:高 < ImageView寬:高),則使其圖片的高度等同於ImageView的高度,圖片顯示在ImageView的左部;
  (2)如果(圖片寬:高 > ImageView寬:高),則使其圖片的寬度等同於ImageView的寬度,圖片顯示在ImageView的上部;


fit_start
2.1 圖片寬:高 < ImageView寬:高,靠左顯示
fit_start_2
2.2 圖片寬:高 > ImageView寬:高,靠上顯示

 

3.FIT_CENTER

將圖片拉伸/縮放,圖片保持原寬高比例: 
  (1)如果(圖片寬:高 < ImageView寬:高),則使其圖片的高度等同於ImageView的高度,圖片顯示在ImageView的中部;
  (2)如果(圖片寬:高 > ImageView寬:高),則使其圖片的寬度等同於ImageView的寬度,圖片顯示在ImageView的中部;


fit_center
3.1 圖片寬:高 < ImageView寬:高 Y軸方向充滿ImageView
fit_center_2
3.2 圖片寬:高 > ImageView寬:高 X軸方向充滿ImageView

 <喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxoNCBpZD0="4fitend">4.FIT_END

將圖片拉伸/縮放,圖片保持原寬高比例: 
  (1)如果(圖片寬:高 < ImageView寬:高),則使其圖片的高度等同於ImageView的高度,圖片顯示在ImageView的右部;
  (2)如果(圖片寬:高 > ImageView寬:高),則使其圖片的寬度等同於ImageView的寬度,圖片顯示在ImageView的底部;


fit_end
4.1 圖片寬:高 < ImageView寬:高
fit_end2
4.2 圖片寬:高 > ImageView寬:高

 

5.CENTER

  不對圖片進行任何拉伸縮放:圖片的中心點與ImageView的中心點重合,因此圖片有可能顯示不完全。


CENTER
5.1 center效果,圖片尺寸小於ImageView
CENTER
5.2 center效果,圖片尺寸大於ImageView,超出的部分無法顯示

 

6.CENTER_CROP

  圖片的中心點與ImageView的中心點重合。如有必要,將對圖片進行拉伸/縮放,並進行中心裁剪:
  (1)如果(圖片寬:高 < ImageView寬:高),則按照(ImageView的寬度 : 圖片的寬度)的比例,同等縮放圖片寬高,且使圖片中心顯示,最終效果是,圖片Y軸方向兩邊“被裁剪”,X軸方向完全顯示;X軸、Y軸方向均充滿整個ImageView.
  (2)如果(圖片寬:高 > ImageView寬:高),則按照(ImageView的高度 : 圖片的高度)的比例,同等縮放圖片寬高,且使圖片中心顯示,最終效果是,圖片X軸方向兩邊“被裁剪”,Y軸方向完全顯示;X軸、Y軸方向均充滿整個ImageView.


CENTER_CROP
6.1圖片寬:高 < ImageView寬:高,X軸方向充滿屏幕
CENTER_CROP2
6.2圖片寬:高 > ImageView寬:高,Y軸方向充滿屏幕

 

7.CENTER_INSIDE

  圖片的中心點與ImageView的中心點重合,並保持圖片完整顯示。如有必要,將對圖片進行遵循圖片原寬高比的拉伸/縮放。
  
  (0)如果圖片原始尺寸寬高均不大於ImageView,那麼將直接居中顯示圖片。

  (1)如果(圖片寬:高 < ImageView寬:高):
    (1.1)如果圖片高度>ImageView高度,則按照(ImageView的高度:圖片的高度)的比例,同等縮放圖片寬高,使其縮小,並使圖片中心顯示,因此此種情況下,圖片完整顯示在ImageView中心,左右兩邊顯示ImageView的Background;
    (1.2)如果圖片高度<=ImageView高度,不進行縮放,使圖片居中顯示,此種情況下,圖片完整顯示在ImageView中心,左右兩邊顯示ImageView的Background。

  (2)如果(圖片寬:高 > ImageView寬:高):
    (2.1)如果圖片寬度>ImageView寬度,則按照(ImageView的寬度:圖片的寬度)的比例,同等縮放圖片寬高,使其縮小,並使圖片中心顯示,因此此種情況下,圖片完整顯示在ImageView中心,上下兩邊顯示ImageView的Background;
    (2.2)如果圖片寬度<=ImageView寬度,不進行縮放,使圖片居中顯示,此種情況下,圖片完整顯示在ImageView中心,上下兩邊顯示ImageView的Background。

 


這裡寫圖片描述
7.1 圖片寬:高 < ImageView寬:高,並且圖片寬高小於ImageView的寬高

 

最後

最後分享一個我通過變換矩陣來達到圖片居中顯示並且具有放大動畫效果的AutoZoomInImageView,可以實現知乎等app的開場動畫
Github:
AutoZoomInImageView

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved