ImageView學習筆記

最近在研究圖片加載庫,趁機找時間學習下ImageView的源碼,探究下它內部機制,同時學習下View的封裝思想。

概述

Displays an arbitrary image, such as an icon. The ImageView class can load images from various sources (such as resources or content providers), takes care of computing its measurement from the image so that it can be used in any layout manager, and provides various display options such as scaling and tinting.

ImageView可以顯示各種圖片,比如icon圖。它可以顯示各種來源的圖片,包括Resource,Content Provider等,顯示的過程中會計算圖片的尺寸從而適應任何布局管理器,并且提供各種顯示可選項,比如縮放、著色等。(來自筆者蹩腳的翻譯)

XML屬性

我們平時一般在XML聲明ImageView,除了繼承自View的屬性,官方文檔列出了以下屬性,我們也主要從這些屬性了解ImageView的功能

XML屬性 說明
android:adjustViewBounds 設置為true,可以讓ImageView根據圖片的寬寬比調整大小,使用該屬性時不能固定ImageView的寬高,或者將寬高設置為match_parent
android:baseline 設置文字的baseLine到ImageView頂部的距離,如果baselineAlignBottom被設置為true,則該屬性會被覆蓋,即該屬性無效
android:baselineAlignBottom 設置為true,baseLine則為ImageView的底部
android:cropToPadding 設置為true,圖像會在Padding屬性的基礎上進行裁剪
android:maxHeight 設置ImageView的最大高度
android:maxWidth 設置ImageView的最大寬度
android:scaleType 設置ImageView的縮放模式
android:src 設置ImageView顯示圖片資源
android:tint 設置ImageView顯示圖片的著色顏色
android:tintMode 設置ImageView顯示圖片的著色模式

源碼學習

接下來通過查看ImageView源碼,分析下ImageView上面這些屬性是怎么起效果的,像adjustViewBoundcropToPadding這些屬性經常用不太順,從源碼的角度了解這些屬性是怎么影響ImageView最終效果。

圖片顯示

ImageView最大的功能就是顯示圖片,首先研究下圖片是怎么在ImageView中顯示的。
在平時使用中,我們可以通過以下兩種方式設置圖片:

  • XML 設置src屬性,可以設置drawable資源,eg.
    android:src="@drawable/test"
  • Java代碼設置,可以設置調用imageView的setImageBitmap, setImageDrawable, setImageResource , setImageURI 設置

注意如果使用 setImageResource , setImageURI 官方提示這兩個方法是需要在UI線程中解析圖片資源的,建議不要使用這兩個方法加載太大的圖,或者在其他線程中解析成Bitmap或者Drawable,避免畫面卡頓。

This does Bitmap reading and decoding on the UI thread, which can cause a latency hiccup. If that's a concern, consider using

接下來看一下在ImageView內部如何處理。
在構造函數中,XML中的src屬性會被讀取并轉化成Drawable,并通過setDrawable設置圖片。

Drawable d = a.getDrawable(com.android.internal.R.styleable.ImageView_src);
if (d != null) {
   setImageDrawable(d);
}

接著看下setDrawable代碼,判斷當前的Drawable與設置的Drawable是否不同,不同的話,把ImageView的成員變量mResourcemUri 值置空,避免其它方式的值影響內容顯示。然后調用updateDrawable這個更新Drawable的方法。如果圖片寬高不同,需要重新布局requestLayout,最后通過invalidate刷新頁面,因為最后圖片的顯示是通過onDraw刷新。

public void setImageDrawable(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主要功能是將ImageViewmDrawable中替換,同時修改mDrawable屬性,這里大部分是Drawable的知識,先不細究了,留意下最后三個方法applyImageTint applyColorMod configureBounds會影響圖片著色和縮放,后面會繼續提到。

private void updateDrawable(Drawable d) {
    if (mDrawable != null) {
        mDrawable.setCallback(null);
        unscheduleDrawable(mDrawable);
    }

    mDrawable = d; 

    if (d != null) {
        d.setCallback(this);
        d.setLayoutDirection(getLayoutDirection());
        if (d.isStateful()) {
            d.setState(getDrawableState());
        }
        d.setVisible(getVisibility() == VISIBLE, true);
        d.setLevel(mLevel);
        mDrawableWidth = d.getIntrinsicWidth();
        mDrawableHeight = d.getIntrinsicHeight();
        applyImageTint();
        applyColorMod();
        configureBounds();
    } else {
        mDrawableWidth = mDrawableHeight = -1;
    }
}

到這里我們可以了解到通過XML設置屬性就是最終是修改了ImageView中的mDrawable變量,同時setDrawable方法也是通過updateDrawable來更新mDrawable。

接下來看一下ImageView的幾個設置顯示圖片的方法。
setImageBitmap也是調用了setImageDrawable,同時使用BitmapDrawable可以減少中間過程中產生的drawable對象,可以大膽推測其他的Drawable最終會轉化成BitmapDrawable用于顯示。

public void setImageBitmap(Bitmap bm) {
        setImageDrawable(new BitmapDrawable(mContext.getResources(), bm));
}

然后看下setImageResourcesetImageUri兩個方法的思路是是一樣,首先判斷mResource或者mUri有值,然后將原有的mDrawable置空,resolverUri重新解析資源,這個方法最終也是調用了updateDrawable,更新圖片顯示,同樣也需要考慮到重新布局的問題。

public void setImageResource(int resId) {
     if (mUri != null || mResource != resId) {
         final int oldWidth = mDrawableWidth;
         final int oldHeight = mDrawableHeight;

         updateDrawable(null);
         mResource = resId;
         mUri = null;

         resolveUri();

         if (oldWidth != mDrawableWidth || oldHeight != mDrawableHeight) {
             requestLayout();
         }
         invalidate();
     }
 }
public void setImageURI(Uri uri) {
    if (mResource != 0 ||
            (mUri != uri &&
             (uri == null || mUri == null || !uri.equals(mUri)))) {
        updateDrawable(null);
        mResource = 0;
        mUri = uri;

        final int oldWidth = mDrawableWidth;
        final int oldHeight = mDrawableHeight;

        resolveUri();

        if (oldWidth != mDrawableWidth || oldHeight != mDrawableHeight) {
            requestLayout();
        }
        invalidate();
    }
}

圖片縮放 ScaleType

詳細大家都了解過ImageView的圖片縮放類型,現在看一下是怎么通過代碼實現的。
首先看setScaleType發現好像沒有什么特別,是重新設置ScaleType枚舉,然后重新布局與繪制。

public void setScaleType(ScaleType scaleType) {
    if (scaleType == null) {
        throw new NullPointerException();
    }

    if (mScaleType != scaleType) {
        mScaleType = scaleType;

        setWillNotCacheDrawing(mScaleType == ScaleType.CENTER);            

        requestLayout();
        invalidate();
    }
}

Ctrl+F搜索 ScaleType,仔細一看,原來藏在configureBounds里,這里對ScaleType枚舉進行處理,這個方法我們剛才已經在updateDrawable中看到過,
接下來看下代碼。

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

    //期望顯示的寬高,即要顯示的Drawable的寬高
    int dwidth = mDrawableWidth;
    int dheight = mDrawableHeight;
    
    //ImageView中顯示內容的寬與高(不包括內邊距)
    int vwidth = getWidth() - mPaddingLeft - mPaddingRight;
    int vheight = getHeight() - mPaddingTop - mPaddingBottom;

    //判斷ImageView的顯示內容寬高是否與期望顯示的寬高一致
    boolean fits = (dwidth < 0 || vwidth == dwidth) &&
                   (dheight < 0 || vheight == dheight);

    if (dwidth <= 0 || dheight <= 0 || ScaleType.FIT_XY == mScaleType) {
        /* 如果Drawable沒有明確的尺寸或者設定為FIX_XY,那么圖片內容會充滿整個ImageView
        */
        mDrawable.setBounds(0, 0, vwidth, vheight);
        mDrawMatrix = null;
    } else {
        //否則我們需要自己處理縮放,讓Drawable顯示原有尺寸
        mDrawable.setBounds(0, 0, dwidth, dheight);

        if (ScaleType.MATRIX == mScaleType) {
            //判斷mMatric是否為單位矩陣,如果不是的話將mMatric賦值給mDrawMatric,用于onDraw中對圖片進行變換
            if (mMatrix.isIdentity()) {
                mDrawMatrix = null;
            } else {
                mDrawMatrix = mMatrix;
            }
        } else if (fits) {
            //如果剛好合適則不需要變換
            mDrawMatrix = null;
        } else if (ScaleType.CENTER == mScaleType) {
            //如果ScaleType為CENTER,對圖片不進行縮放,將圖片移動到ImageView中心
            mDrawMatrix = mMatrix;
            mDrawMatrix.setTranslate((int) ((vwidth - dwidth) * 0.5f + 0.5f),
                                     (int) ((vheight - dheight) * 0.5f + 0.5f));
        } else if (ScaleType.CENTER_CROP == mScaleType) {
           //如果ScaleType為CENTER_CROP ,對圖片進行等比縮放至圖片寬高大于等于ImageView,將圖片中心移動到ImageView中心
            mDrawMatrix = mMatrix;

            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((int) (dx + 0.5f), (int) (dy + 0.5f));
        } else if (ScaleType.CENTER_INSIDE == mScaleType) {
         //如果ScaleType為CENTER_INSIDE ,對圖片進行等比縮放至圖片寬高小等于ImageView,將圖片中心移動到ImageView中心
            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 = (int) ((vwidth - dwidth * scale) * 0.5f + 0.5f);
            dy = (int) ((vheight - dheight * scale) * 0.5f + 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;
            mDrawMatrix.setRectToRect(mTempSrc, mTempDst, scaleTypeToScaleToFit(mScaleType));
        }
    }
}

通過configureBounds中可以知道ScaleType本質就是通過Matrix矩陣變換,主要通過縮放和位移實現具體效果。
ImageView構造函數中初始化時將ScaleType默認為FIT_CENTER

mScaleType  = ScaleType.FIT_CENTER;

cropToPadding 屬性

CropToPadding這個屬性感覺平時不太用,嘗試用了下又感覺沒什么效果,所以這個屬性究竟是怎么其效果的呢?
首先我們可以找到mCropToPadding這個變量,我們平時修改的就是這個值,進一步查找,可以發現在onDraw中有用到這個變量,所以這個變量是在圖形繪制過程中生效的。我們來看下onDraw中的相關代碼。

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

    if (mDrawable == null) {
        return; 
    }

    if (mDrawableWidth == 0 || mDrawableHeight == 0) {
        return;     
    }

    //如果變換矩陣為空,并且沒有上內邊距和左內邊距則只直接繪制
    if (mDrawMatrix == null && mPaddingTop == 0 && mPaddingLeft == 0) {
        mDrawable.draw(canvas);
    } else {
        int saveCount = canvas.getSaveCount();
        canvas.save();
        //如果mCropToPadding設置為ture,需要裁剪區域,裁剪區域要考慮到x和y方向的內容滾動位移
        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) {
            canvas.concat(mDrawMatrix);
        }
        mDrawable.draw(canvas);
        canvas.restoreToCount(saveCount);
    }
}

從上面代碼中我們可以看到mCropToPadding如果為true,canvas會劃出除去內邊距的實際顯示區域,這里我們還發現除了考慮到padding屬性,還要考慮mScrollX mScrollY兩個屬性,為什么呢?

The offset, in pixels, by which the content of this view is scrolled

官方的解釋是內容區域在View中滾動偏移量,View容器實際是不會滾動的,真正滾動效果是通過內容的移動達到滾動(偏移量,也可以理解成位移),而padding是包括在內容區域里的,這兩個偏移量會讓padding區域滾動,從而導致原有padding效果失效。以下代碼是View類中獲取繪制區域的方法,繪制區域是考慮到Scroll偏移的。

public void getDrawingRect(Rect outRect) {
    outRect.left = mScrollX;
    outRect.top = mScrollY;
    outRect.right = mScrollX + (mRight - mLeft);
    outRect.bottom = mScrollY + (mBottom - mTop);
}

為了讓padding區域是基于View,所以我們要事先繪制好期望的內容區域,避免Scroll帶來的影響。畫了張圖。

除了Scroll偏移會影響邊距,我們能從代碼中發現Matrix矩陣縮放也會影響到邊距,大家可以回到上面講ScaleType的部分,我們可以看到CENTER , CENTER_CROP , CENTER_INSIDE , FIT_CENTER , FIT_START , FIT_END的矩陣變換都是基于不包括內邊距的區域的(·vheightvwidth),而MATRIX只是設置了下mDrawMatix,沒有前幾種縮放類型做的范圍處理。onDraw代碼中我們也能看到是先translate確定繪制起點,然后再contact矩陣變換,如果我們這樣做。

<ImageView
    android:id="@+id/imageView"
    android:layout_width="80dp"
    android:layout_height="80dp"
    android:background="@color/colorAccent"
    android:cropToPadding="false"
    android:padding="10dp"
    android:scaleType="matrix"
    android:src="@mipmap/ic_launcher" />
ImageView imageView = (ImageView) findViewById(R.id.imageView);

Matrix matrix = new Matrix();
matrix.setTranslate(-40, -40);

imageView.setImageMatrix(matrix);

最后我們看到的效果是這樣的,同樣我們理解中的padding失效了


如果我們把cropToPadding屬性設置為true則得到下面的效果。

所以總結來說cropToPaddingImageView為了補償其他屬性導致我們最終的“理解的內邊距效果”實現而做出的調整,目前了解到如果你的ImageView設置了scrollX scrollY或者將scaleTypeMATRIX時又需要保證padding的效果,可以將cropToPadding設置為true。

adjustViewBounds屬性

我們可以通過ScaleType讓顯示的圖片根據ImageView大小進行各種類型的調整,我們可不可以讓ImageView根據圖片內容調整大小呢?這時候我們就需要用到adjustViewBounds這個屬性啦,能讓顯示的圖片原有比例,調整ImageView到合適的大小,達到我們想要的圖片與ImageView無縫銜接,我們從ImageView代碼中看下是怎么實現的。老規矩搜索mAdjustViewBounds屬性,就可以發現這個屬性主要是在onMeasure方法中起作用。

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    resolveUri();
    int w;
    int h;
    
    // 期望的顯示比例(不包括padding),也就是要顯示圖片的原有比例
    float desiredAspect = 0.0f;
    
    // 用于判斷是否允許調整寬度,默認為false
    boolean resizeWidth = false;
    
    // 用于判斷是否允許調整高度,默認為false
    boolean resizeHeight = false;
    
    //獲取父容器為ImageView設置的尺寸測量模式
    final int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
    final int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);

    if (mDrawable == null) {
        mDrawableWidth = -1;
        mDrawableHeight = -1;
        w = h = 0;
    } else {
        w = mDrawableWidth;
        h = mDrawableHeight;
        if (w <= 0) w = 1;
        if (h <= 0) h = 1;

        //如果設置mAdjustViewBounds為true,判斷widthSpecMode和heightSpecMode,如果不是EXACTLY則支持調整,并獲得期望的寬高比
        if (mAdjustViewBounds) {
            resizeWidth = widthSpecMode != MeasureSpec.EXACTLY;
            resizeHeight = heightSpecMode != MeasureSpec.EXACTLY;
            
            desiredAspect = (float) w / (float) h;
        }
    }
    
    int pleft = mPaddingLeft;
    int pright = mPaddingRight;
    int ptop = mPaddingTop;
    int pbottom = mPaddingBottom;

    int widthSize;
    int heightSize;

    //如果寬或者高可以調整則進行調整,并且只能調整一個
    if (resizeWidth || resizeHeight) {

        // 獲取最大可能的寬度
        widthSize = resolveAdjustedSize(w + pleft + pright, mMaxWidth, widthMeasureSpec);

        // 獲取最大可能的高度
        heightSize = resolveAdjustedSize(h + ptop + pbottom, mMaxHeight, heightMeasureSpec);

        if (desiredAspect != 0.0f) {
            // 查看實際的寬高比
            float actualAspect = (float)(widthSize - pleft - pright) /
                                    (heightSize - ptop - pbottom);
            // 如果實際比例與期望比例差距小于0.0000001則不進行調整
            if (Math.abs(actualAspect - desiredAspect) > 0.0000001) {
                
                boolean done = false;
                
                // 調整寬度去適應高
                if (resizeWidth) {
                    int newWidth = (int)(desiredAspect * (heightSize - ptop - pbottom)) +
                            pleft + pright;

                    //是否允許調整寬度,即高度固定,另一個參數是和兼容性有關先不研究
                    if (!resizeHeight && !mAdjustViewBoundsCompat) {
                        //計算調整后的高度
                        widthSize = resolveAdjustedSize(newWidth, mMaxWidth, widthMeasureSpec);
                    }
                       
                    //調整高度符合規則,調整結束
                    if (newWidth <= widthSize) {
                        widthSize = newWidth;
                        done = true;
                    } 
                }
                
                // 調整高度,思路和寬度一致
                if (!done && resizeHeight) {
                    int newHeight = (int)((widthSize - pleft - pright) / desiredAspect) +
                            ptop + pbottom;

                    if (!resizeWidth && !mAdjustViewBoundsCompat) {
                        heightSize = resolveAdjustedSize(newHeight, mMaxHeight,
                                heightMeasureSpec);
                    }

                    if (newHeight <= heightSize) {
                        heightSize = newHeight;
                    }
                }
            }
        }
    } else {
        // 如果不需要尺寸調整,只做常規的測量計算
        w += pleft + pright;
        h += ptop + pbottom;
            
        w = Math.max(w, getSuggestedMinimumWidth());
        h = Math.max(h, getSuggestedMinimumHeight());
        // 計算高度和狀態
        widthSize = resolveSizeAndState(w, widthMeasureSpec, 0);
        heightSize = resolveSizeAndState(h, heightMeasureSpec, 0);
    }

    setMeasuredDimension(widthSize, heightSize);
}

上面遇到了resolveAdjustedSize resolveSizeAndState之前沒遇到過,接下看看下這兩個方法。

private int resolveAdjustedSize(int desiredSize, int maxSize,
                               int measureSpec) {
    int result = desiredSize;
    int specMode = MeasureSpec.getMode(measureSpec);
    int specSize =  MeasureSpec.getSize(measureSpec);
    switch (specMode) {
        case MeasureSpec.UNSPECIFIED:
            // 父容器沒有約束,如果期望值在最大值范圍內則取期望值
            result = Math.min(desiredSize, maxSize);
            break;
        case MeasureSpec.AT_MOST:
            // 父容器給出調整的最大值,這個值與期望值,設置的最大值之間取最小值
            result = Math.min(Math.min(desiredSize, specSize), maxSize);
            break;
        case MeasureSpec.EXACTLY:
            // 沒有選擇,直接返回父容器分配的值
            result = specSize;
            break;
    }
    return result;
}

resolveAdjustedSize就是通過父容器分配的MeasureSpec的約束下盡可能去取期望值。

public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) {
    int result = size;
    int specMode = MeasureSpec.getMode(measureSpec);
    int specSize =  MeasureSpec.getSize(measureSpec);
    switch (specMode) {
    case MeasureSpec.UNSPECIFIED:
        result = size;
        break;
    case MeasureSpec.AT_MOST:
        if (specSize < size) {
            result = specSize | MEASURED_STATE_TOO_SMALL;
        } else {
            result = size;
        }
        break;
    case MeasureSpec.EXACTLY:
        result = specSize;
        break;
    }
    return result | (childMeasuredState&MEASURED_STATE_MASK);
}

resolveSizeAndState是父類View中的一個靜態方法,和上面resolveAdjustedSize差不多,也是根據父容器分配的MeasureSpec的約束下盡可能去取要顯示Drawable的寬高,也包括一些位操作用于表示狀態。
了解了原理我們可以結合實際情況分析一下。
(1)寬度設置為match_parent高度設置為wrap_content,第一張圖為adjustViewBoundsfalse,第二張圖為true



match_parent對應的specModeEXACTLY,wrap_contentspecModeAT_MOST,如果將adjustViewBounds設置為trueImageView的高度依據圖片的原有比例調整,否則ImageView的高度為顯示圖片的高度。
(2)寬度與高度設置均為wrap_content,無論adjustViewBounds取值如何,效果一樣。

wrap_contentspecModeAT_MOST,從onMeasure方法中可知,如果兩個尺寸都不定的話,是不會進行調整。
(3)寬度與高度設置均為match_parent,無論adjustViewBounds取值如何,效果也都一樣。

wrap_contentspecModeEXACTLY,所以說寬和高均不可變,也就是說不能調整,同理如果寬高設置為具體尺寸,adjustViewBounds也是起不了作用的。

ImageView拓展

ImageView是我們UI繪制過程一個必不可少的基礎控件,然而在實際開發過程中我們發現有些情況下ImageView功能還不夠,所以我們會基于ImageView基礎上去拓展,這里找了兩個小案例。

RoundImageView

現在很多應用中,頭像采用是圓形的,而ImageVeiw只能顯示方形圖片,最簡單的方法是在加一層背景色遮罩,當然也可以通過自定View來解決。這是一個自定義控件用來顯示圓形圖片,當然現在也有很多圖片庫來顯示圓形頭像。具體代碼就不貼了,實現思路主要是繼承ImageView,重寫OnDraw,通過設置Xfermodes(這里我理解為圖形疊加模式)在圓形上繪制圖片,達到繪制圓形圖像的效果。

public Bitmap getCroppedRoundBitmap(Bitmap bmp, int radius) {

    Bitmap scaledSrcBmp;

    int diameter = radius * 2;

   ...
    // 為了防止寬高不相等,造成圓形圖片變形,因此截取長方形中處于中間位置最大的正方形圖片

    int bmpWidth = bmp.getWidth();
    int bmpHeight = bmp.getHeight();
    int squareWidth = 0, squareHeight = 0;
    int x = 0, y = 0;

    Bitmap squareBitmap;

    if (bmpHeight > bmpWidth) {
    
        squareWidth = squareHeight = bmpWidth;
        x = 0;
        y = (bmpHeight - bmpWidth) / 2;
        
        squareBitmap = Bitmap.createBitmap(bmp, x, y, squareWidth, squareHeight);
    } else if (bmpHeight < bmpWidth) {
    
        squareWidth = squareHeight = bmpHeight;
        x = (bmpWidth - bmpHeight) / 2;
        y = 0;

        squareBitmap = Bitmap.createBitmap(bmp, x, y, squareWidth, squareHeight);
    } else {
        squareBitmap = bmp;
    }

    //將正方形圖片縮放至與ImageView直徑相同
    if (squareBitmap.getWidth() != diameter || squareBitmap.getHeight() != diameter) {

        scaledSrcBmp = Bitmap.createScaledBitmap(squareBitmap, diameter, diameter, true);
    } else {
        scaledSrcBmp = squareBitmap;
    }

    Bitmap output = Bitmap.createBitmap(scaledSrcBmp.getWidth(),
            scaledSrcBmp.getHeight(),
            Bitmap.Config.ARGB_8888);

    //獲取處理圖片的Canvas
    Canvas canvas = new
            Canvas(output);

    Paint paint = new Paint();

    Rect rect = new Rect(0, 0, scaledSrcBmp.getWidth(), scaledSrcBmp.getHeight());

    paint.setAntiAlias(true);//開啟抗鋸齒
    paint.setFilterBitmap(true);//開啟過濾效果
    paint.setDither(true);//開啟抖動

    canvas.drawARGB(0, 0, 0, 0);

    //繪制圓形
    canvas.drawCircle(scaledSrcBmp.getWidth() / 2, scaledSrcBmp.getHeight() / 2, scaledSrcBmp.getWidth() / 2, paint);

    //設置疊加模式為SRC_IN,即在圓形的返回內繪制圖像
    paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));

    canvas.drawBitmap(scaledSrcBmp, rect, rect, paint);

    //釋放內存
    bmp = null;
    squareBitmap = null;
    scaledSrcBmp = null;

    return output;

}

onDraw繪制圓形圖片

@Override
protected void onDraw(Canvas canvas) {
    ...
    
    Bitmap roundBitmap = getCroppedRoundBitmap(bitmap, radius);

    canvas.drawBitmap(roundBitmap, defaultWidth / 2
            - radius, defaultHeight / 2
            - radius, null);

}

AutoSquareImageView

這是我做Android項目遇到的第一個難題,四個ImageView是要通過weight屬性來實現水平方向上的寬度均等,但是UI要求是讓這個圖形按鈕正方形顯示,不同手機水平寬度不同,需要自適應高度,為了達到正方形效果需要自定義AutoSquareImageView,修改思路其實很簡單,將ImageView寬度通過weight去獲取,高度設置為wrap_content,重寫onMeasure方法,計算正常流程下的寬與高,然后取大值達到滿足正方形圖的最小邊長。

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);

    int measuredWidth = getMeasuredWidth();
    int measuredHeight = getMeasuredHeight();
    //保持長度大的一邊
    if (measuredWidth > measuredHeight) {
        setMeasuredDimension(measuredWidth, measuredWidth);
    } else {
        setMeasuredDimension(measuredHeight, measuredHeight);
    }
}

總結

  • ImageView顯示的圖片資源可以是通過XML屬性或者Java代碼指定顯示的圖片內容,可以通過設置Resource、Uri 、Bitmap對象、Drawable對象來指定。
  • ScaleType屬性的修改本質上根據設定的枚舉將變換賦值給mMatrix,最終通過onDraw中的矩陣操作實現。
  • cropToPadding屬性可以讓圖片內容是在padding基礎上進行變換(縮放,移動),根據實踐如果使用了mScrollX mScrollY屬性或者scaleType設置為MATRIX類型時會配合用到這個屬性。
  • adjustViewBounds屬性主要是讓ImageView根據圖片原有比例的約束下將調整大小,注意必須要限定住寬或者高,即將其中一個屬性設置為match_parent或者確定尺寸。

筆記就寫到這里了,這也是我第一次在網上寫文章,寫的也是基礎知識,如果有什么不對的地方歡迎大家批評指正!寫文章也是為了能把學習的知識點進行梳理,然后也會有學習的動力,內容可能不高級,但希望能一點點慢慢提高。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,606評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,582評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,540評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,028評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,801評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,223評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,294評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,442評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,976評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,800評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,996評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,543評論 5 360
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,233評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,662評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,926評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,702評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,991評論 2 374

推薦閱讀更多精彩內容