圖片基礎知識梳理(1) ImageView 的 ScaleType 屬性解析

1、概述

在使用ImageView的過程中,常常須要經過scaleType來對原始的圖像進行處理,使得它能在空間中合理地展現。android

2、scaleType的分類

首先,咱們簡單介紹一下scaleType的分類:canvas

2.1 經過Matrix設置

這種狀況下,對應的模式只有一種:bash

  • ScaleType.MATRIX

最終,在這種狀況下,咱們能夠同setImageMatrix(Matrix matrix)來改變。ide

2.2 填充類型

這一類屬性的特色就是經過拉伸或者壓縮圖片,使得原圖片中全部元素都可以展示,而且至少填滿控件x,y軸的其中一個。 一共有四類:函數

  • ScaleType.FIX_XY不考慮原圖的比例,拉伸或者壓縮使得它等於控件的寬高。

下面三種都會維持原圖的比例,使得它們的x,y都小於等於控件的寬高,只是最終的圖形放的位置不一樣。源碼分析

  • ScaleType.FIT_START:放置在左上角。
  • ScaleType.FIT_CENTER:放置在中間。
  • ScaleType.FIT_END:放置在右下角。

2.3 中心重合類型

下面的三種類型都會使得控件的中心和圖片中心重合:佈局

  • ScaleType.CENTER 要求一點:post

  • 原圖的中心和控件的中心重合ui

  • ScaleType.CENTER_CROP 要求三點:spa

  • 整個控件可以被填滿

  • 原圖的比例不變

  • 原圖的中心和控件的中心重合。

  • 保證原圖的x,y軸上的元素至少有一個在控件中能被徹底展現,那麼有一下兩種狀況,在下面的操做作完以後,裁剪掉多餘的部分:

    • 若是原圖沒有填滿控件,那麼會慢慢按比例放大,直到填滿控件;
    • 若是原圖已經填滿控件,那麼它會慢慢縮小,直到某一邊和控件重合。
  • ScaleType.CENTER_INSIDE 要求三點:

  • 原圖的全部像素位於控件內部

  • 原圖的比例不變

  • 圖片的中心和控件的中心重合。

它不要求原始圖片填滿x,y軸的任意一個,所以,若是原圖的長寬都小於等於控件的長寬,不會進行放大操做,這也是它和ScaleType.FIT_CENTER的區別

3、示例

下面,咱們經過一個簡單的Demo來展現一下各類類型的具體表現,咱們有兩個大小同樣的ImageView和兩個大小不一樣的原圖,其中左邊的ImageView要比原圖小,右邊的ImageView要比原圖大。

  • ScaleType.FIX_XY
  • ScaleType.FIT_START
  • ScaleType.FIT_CENTER
  • ScaleType.FIT_END
  • ScaleType.CENTER
  • ScaleType.CENTER_CROP
  • ScaleType.CENTER_INSIDE

4、源碼分析

4.1 給ImageVIew設置src的接口

ImageView當中,設置圖片的接口主要有下面幾個函數:

public void setImageBitmap(Bitmap bm)
public void setImageResource(@DrawableRes int resId)
public void setImageURI(@Nullable Uri uri)
public void setImageDrawable(@Nullable Drawable drawable)
複製代碼

4.2 setImageBitmap的流程

咱們就以平時經常使用的setImageBitmap爲例,分析一下它整個的流程:

  • 第一步:當咱們調用setImageBitmap以後,它會把Bitmap封裝在BitmapDrawable當中,以後調用了setImageDrawable(Drawable drawable)方法:
public void setImageBitmap(Bitmap bm) {
        mDrawable = null;
        if (mRecycleableBitmapDrawable == null) {
            mRecycleableBitmapDrawable = new BitmapDrawable(mContext.getResources(), bm);
        } else {
            mRecycleableBitmapDrawable.setBitmap(bm);
        }
        setImageDrawable(mRecycleableBitmapDrawable);
    }
複製代碼
  • 第二步:調用setImageDrawable
public void setImageDrawable(@Nullable Drawable drawable) {
        //若是不是同一個資源.
        if (mDrawable != drawable) {
            mResource = 0;
            mUri = null;
            //舊的寬高.
            final int oldWidth = mDrawableWidth;
            final int oldHeight = mDrawableHeight;
            //關鍵方法
            updateDrawable(drawable);
            //若是寬高不一樣,才請求從新佈局.
            if (oldWidth != mDrawableWidth || oldHeight != mDrawableHeight) {
                requestLayout();
            }
            //只要替換了資源就須要從新繪製.
            invalidate();
        }
    }
複製代碼

上面的關鍵方法在updateDrawable當中:

private void updateDrawable(Drawable d) {
        //....
        if (d != null) {
            //...
            //這裏面根據scaleType配置mDrawMatrix.
            configureBounds();
        } else {
            mDrawableWidth = mDrawableHeight = -1;
        }
    }
複製代碼

4.3 configureBounds改變Matrix

configureBounds裏就會根據咱們所配置的scaleType來決定mDrawable如何顯示,在這裏面有一個重要的變量mDrawMatrix,咱們前面說到的全部變換都是經過它來實現的,固然,咱們除了可讓系統本身根據scaleType來生成matrix,也能夠經過setImageMatrix手動的指定本身的變換:

private void configureBounds() {
        if (mDrawable == null || !mHaveFrame) {
            return;
        }
      //1.獲得原始資源的寬高.
        final int dwidth = mDrawableWidth;
        final int dheight = mDrawableHeight;
        //2.獲得控件的寬高,這裏去掉了控件的padding.
        final int vwidth = getWidth() - mPaddingLeft - mPaddingRight;
        final int vheight = getHeight() - mPaddingTop - mPaddingBottom;

        //3.表示原始資源已經可以填滿控件.
        final boolean fits = (dwidth < 0 || vwidth == dwidth)
                && (dheight < 0 || vheight == dheight);

        //4.假若有一邊是wrap_content,或者是FIX_XY,那麼填滿整個控件.
        if (dwidth <= 0 || dheight <= 0 || ScaleType.FIT_XY == mScaleType) {
            mDrawable.setBounds(0, 0, vwidth, vheight);
            mDrawMatrix = null;
        } else {
            mDrawable.setBounds(0, 0, dwidth, dheight);
            //若是scaleType是matrix.
            if (ScaleType.MATRIX == mScaleType) {
                //單位矩陣的狀況,設爲null.
                if (mMatrix.isIdentity()) {
                    mDrawMatrix = null;
                } else {
                    //不然最後的DrawMatrix就是咱們傳入的Matrix.
                    mDrawMatrix = mMatrix;
                }
            } else if (fits) {
                //若是原始資源已經填滿控件,那麼不須要考慮其它的變換了.
                mDrawMatrix = null;
            } else if (ScaleType.CENTER == mScaleType) {
                //當scaleType爲center的時候.
                mDrawMatrix = mMatrix;
                //移動到中心,初始時候,控件和原始資源的(0,0)點是重合的.
                mDrawMatrix.setTranslate(Math.round((vwidth - dwidth) * 0.5f), Math.round((vheight - dheight) * 0.5f));
            } else if (ScaleType.CENTER_CROP == mScaleType) {
                mDrawMatrix = mMatrix;
                //當scaleType是centerCrop的時候.
                float scale;
                float dx = 0, dy = 0;
                //取須要變換最小的軸,進行等比縮放.
                if (dwidth * vheight > vwidth * dheight) {
                    scale = (float) vheight / (float) dheight;
                    dx = (vwidth - dwidth * scale) * 0.5f;
                } else {
                    scale = (float) vwidth / (float) dwidth;
                    dy = (vheight - dheight * scale) * 0.5f;
                }
                //先縮放,再移動到中心點.
                mDrawMatrix.setScale(scale, scale);
                mDrawMatrix.postTranslate(Math.round(dx), Math.round(dy));
            } else if (ScaleType.CENTER_INSIDE == mScaleType) {
                //當scaleType是centetInside時
                mDrawMatrix = mMatrix;
                float scale;
                float dx;
                float dy;
                //若是原始資源的寬高都小於控件的寬高,那麼不作縮放.
                if (dwidth <= vwidth && dheight <= vheight) {
                    scale = 1.0f;
                } else {
                   //不然等比縮放.
                    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 {
                //設置兩個區域的大小.
                mTempSrc.set(0, 0, dwidth, dheight);
                mTempDst.set(0, 0, vwidth, vheight);
                mDrawMatrix = mMatrix;
                //這裏處理FIX_START,FIX_END,FIX_CENTER的狀況.
                mDrawMatrix.setRectToRect(mTempSrc, mTempDst, scaleTypeToScaleToFit(mScaleType));
            }
        }
    }
複製代碼

4.4 onDraw中進行繪製

那麼這個mDrawMatrix是在何時使用的呢,咱們看一下onDraw方法:

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

        if (mDrawMatrix == null && mPaddingTop == 0 && mPaddingLeft == 0) {
            mDrawable.draw(canvas);
        } else {
            final 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);
            }
            //移動到去掉padding的左上角.
            canvas.translate(mPaddingLeft, mPaddingTop);
            //根據mDrawMatrix進行變換.
            if (mDrawMatrix != null) {
                canvas.concat(mDrawMatrix);
            }
            //再在這個變換上面繪製咱們的資源.
            mDrawable.draw(canvas);
            //把合成完成的圖片繪製上去.
            canvas.restoreToCount(saveCount);
        }
    }
複製代碼

4.5 小結

咱們總結一下,整個scaleType的原理就是在configureBounds中配置了mDrawMatrix,而在onDraw當中會根據mDrawMatrix來對圖層進行變換,在這個變換以後的圖層上進行繪製mDrawable,以後再恢復圖層。

5、ImageViewsrcbackground的區別

上面,咱們看到的都是src設置的效果,咱們回憶一下,經過設置android:background也能夠設置一個圖片給它,其實backgroundView的屬性,在咱們以前分析View的繪製流程的時候,draw(canvas)中有一步就是繪製背景:

private void drawBackground(Canvas canvas) {
        //1.設置背景的邊界.
        setBackgroundBounds();
       //2.若是有滾動,那麼背景須要相應的滾動.
        final int scrollX = mScrollX;
        final int scrollY = mScrollY;
        if ((scrollX | scrollY) == 0) {
            background.draw(canvas);
        } else {
            canvas.translate(scrollX, scrollY);
            //3.繪製背景.
            background.draw(canvas);
            canvas.translate(-scrollX, -scrollY);
        }
    }
複製代碼

咱們來看一下設置背景的邊界的函數,能夠看到,這裏沒有考慮padding值,也就是說咱們經過background設置的圖片是填滿整個控件,而且不考慮padding的:

void setBackgroundBounds() {
        if (mBackgroundSizeChanged && mBackground != null) {
            //沒有考慮padding部分.
            mBackground.setBounds(0, 0, mRight - mLeft, mBottom - mTop);
            mBackgroundSizeChanged = false;
            rebuildOutline();
        }
    }
複製代碼

最後再結合一下第四節的知識,咱們是先繪製背景,而後纔在ImageViewonDraw函數當中在canvas上繪製的,所以,src的圖片必定會繪製在backgroud之上。

相關文章
相關標籤/搜索