CircleImageView用法及源碼解析(雷驚風)

   首先這篇文章我們從以下四個方面進行一一講解1.CircleImageViewAS中集成及用法;2.CircleImageView中定義的對外的方法;3.源碼解析;4.用到的知識點的總計。希望通過本篇文章的學習,您會對自定義控件及自定義控件中用到的一些類有一定的瞭解,最後我會把我添加好詳細註釋的Demo下載地址附上,你也可以一邊看我的Demo,一邊看這篇文章,效果應該會更好,那麼,我們這就開始吧!

1.CircleImageViewAS中集成及用法

   我們想在AS下使用CircleImageView只需要在我們app下的build.gradle中添加一行配置即可,很簡單,如下最後一行:

dependencies {
    compile fileTree(include: ['*.jar'], dir: 'libs')
    compile 'com.android.support:appcompat-v7:25.0.0'
    compile 'de.hdodenhof:circleimageview:2.1.0'
}

配置完了以後,我們就可以在我們的xml佈局文件中引入或者.java文件中創建我們的CircleImageView並且使用了,到目前爲止它的最新版本爲2.1.0,它在github上的下載地址如下:https://github.com/hdodenhof/CircleImageView 好吧,集成就講這麼點,很簡單。

   下面我們講一下CircleImageView的具體用法,在將具體用法之前,我們先新建一個最新的android項目按照上述集成方法,集成我們的CircleImageView,集成完成後我們就可以在我們的XML佈局文件中引入我們的控件了,如下:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center">

    <de.hdodenhof.circleimageview.CircleImageView
        android:id="@+id/circleImageView"
        android:layout_width="150dp"
        android:layout_height="150dp"
        android:src="@mipmap/psb"
        app:civ_border_color="@android:color/holo_red_dark"
        app:civ_border_width="5dp" />
</RelativeLayout>

然後將XML文件與Acitivity進行綁定,運行項目就會看到如下結果:


   我們還可以在代碼中對CircleImageView進行一些設置如禁用圖片圓形屬性等,這些在下一節CircleImageView中定義的對外的方法中講解。

2.CircleImageView中定義的對外的方法

我們打開CircleImageView.java文件查看他的public方法,下邊我一一說明:

@Override
public ScaleType getScaleType() {
    return SCALE_TYPE;
}

解釋:外部類獲取CircleImageView的ScaleType屬性。

@Override
public void setScaleType(ScaleType scaleType) {
    if (scaleType != SCALE_TYPE) {
        throw new IllegalArgumentException(String.format("ScaleType %s not supported.", scaleType));
    }
}

解釋:重寫父類方法,,CircleImageView設置ScaleType屬性,這裏需要注意如果設置的scaleType不是ScaleType.CENTER_CROP,拋出異常,只支持ScaleType.CENTER_CROP;

@Override
public void setAdjustViewBounds(boolean adjustViewBounds) {
    if (adjustViewBounds) {
        throw new IllegalArgumentException("adjustViewBounds not supported.");
    }
}

解釋:重寫父類方法,adjustViewBounds屬性爲是否保持寬高比。需要與maxWidth、MaxHeight一起使用,否則單獨使用沒有效果。當前控件不支持設置保持寬高比;起到禁止設置的作用。

@Override
public void setPadding(int left, int top, int right, int bottom) {
    super.setPadding(left, top, right, bottom);
    setup();
}

解釋:設置padding屬性,該控件兼容設置padding屬性。

@Override
public void setPaddingRelative(int start, int top, int end, int bottom) {
    super.setPaddingRelative(start, top, end, bottom);
    setup();
}

解釋:setPadding的話不管方向如何都按照左上右下的順序來配置Padding,setPaddingRelative的話則會按照配置的LayoutDirection來進行設置從左到右的話爲左上右下,從右到左的話爲右上左下的順序Android4.0以後添加)。

public int getBorderColor() {
    return mBorderColor;
}

解釋:獲取外邊框圓環顏色。

public void setBorderColor(@ColorInt int borderColor) {
    if (borderColor == mBorderColor) {
        return;
    }
    mBorderColor = borderColor;
    mBorderPaint.setColor(mBorderColor);
    invalidate();
}

解釋:設置外邊框圓環顏色。

public int getBorderWidth() {
    return mBorderWidth;
}

解釋:獲取外邊框寬度。

public void setBorderWidth(int borderWidth) {
    if (borderWidth == mBorderWidth) {
        return;
    }
    mBorderWidth = borderWidth;
    setup();
}

解釋:設置外邊框寬度。

public boolean isBorderOverlay() {
    return mBorderOverlay;
}

解釋:外邊圓環是否壓住圓形圖片。

public void setBorderOverlay(boolean borderOverlay) {
    if (borderOverlay == mBorderOverlay) {
        return;
    }
    mBorderOverlay = borderOverlay;
    setup();
}

解釋:設置外邊圓環是否壓住內部圓形圖片。

public boolean isDisableCircularTransformation() {
    return mDisableCircularTransformation;
}

解釋:是否禁用圖片圓形屬性。如果爲true,則就是普通方形圖片。

public void setDisableCircularTransformation(boolean disableCircularTransformation) {
    if (mDisableCircularTransformation == disableCircularTransformation) {
        return;
    }
    mDisableCircularTransformation = disableCircularTransformation;
    initializeBitmap();
}

解釋:設置是否禁用圖片圓形屬性。

@Override
public void setImageBitmap(Bitmap bm) {
    super.setImageBitmap(bm);
    initializeBitmap();
}
@Override
public void setImageDrawable(Drawable drawable) {
    super.setImageDrawable(drawable);
    System.out.println("Log_setImageDrawable()");
    initializeBitmap();
}
@Override
public void setImageResource(@DrawableRes int resId) {
    super.setImageResource(resId);
    System.out.println("Log_setImageResource()");
    initializeBitmap();
}
@Override
public void setImageURI(Uri uri) {
    super.setImageURI(uri);
    initializeBitmap();
}

解釋:四種重寫父類設置圖片方法。

PS:如果我們在XML中設置了android:src屬性,會執行我們的第一個方法(setImageBitmap)該方法會先於構造函數調用之前調用。後邊在源碼講解中詳細說明。

@Override
public void setColorFilter(ColorFilter cf) {
    if (cf == mColorFilter) {
        return;
    }
    mColorFilter = cf;
    applyColorFilter();
    invalidate();
}

解釋:重寫父類方法,設置ColorFilter,查看ColorFilter文檔你會發現,ColorFilter有三個子類:ColorMatrixColorFilter:顏色矩陣過濾器;LightingColorFilter:“光照色彩過濾器”,模擬一個光照照過圖像所產生的效果;PorterDuffColorFilter:PorterDuff混合模式的色彩過濾器。如果你想了解相關知識可以查相關文檔,這裏就不詳細講了,超出本文範圍。

@Override
public ColorFilter getColorFilter() {
    return mColorFilter;
}

解釋:獲取着色器。

   到目前位置,整個CircleImageView中的建議使用的公共方法差不多就上述這麼些,還有一些現在已經不建議使用了,我就沒有拿出來,比如設置圖片背景顏色啊等等,已經用註解@Deprecated進行了標註,如下:

@Deprecated
public void setFillColor(@ColorInt int fillColor) {
    if (fillColor == mFillColor) {
        return;
    }
    System.out.println("Log_setFillColor()");
    mFillColor = fillColor;
    mFillPaint.setColor(fillColor);
    invalidate();
}

我們CircleImageView的所有public方法都進行了說明,那我們的控件您肯定就會用了,再不會用,我相信你已經沒救了,趕緊騎上大母豬飛奔吧!

3.源碼解析

   這個小結我們開始進入本篇的重點,就是了解它是如何實現的,在我們講解以前,爲了添加註釋方便,我們先在我們的項目下新建一個名稱一模一樣的.java文件,將原CircleImageView文件中代碼複製一份,粘進去,可以看到我們的控件是繼承了ImageView的,在ImageView的基礎上進行擴展。這樣就可以了。因爲我們之前已經在app下的build.gradle中引入過CircleImageView了,所以不用去拷貝如下代碼:

<resources>
    <declare-styleable name="CircleImageView">
        <attr name="civ_border_width" format="dimension" />
        <attr name="civ_border_color" format="color" />
        <attr name="civ_border_overlay" format="boolean" />
        <attr name="civ_fill_color" format="color" />
    </declare-styleable>
</resources>

如果您沒有配置過build.gradle,就需要複製了,否則會報錯。下面我們將XML中的引用改成我們自己剛建的CircleImageview運行,結果依然可以顯示,沒有任何區別。好了下面我們進入主題吧!

打開我們的CircleimageView你會發現,它也有三個構造函數,如下:

public CircleImageView(Context context) {
    super(context);
    System.out.println("Log_單參構造");
    init();
}

public CircleImageView(Context context, AttributeSet attrs) {
    this(context, attrs, 0);

}

public CircleImageView(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
    System.out.println("Log_多參構造");
    TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CircleImageView, defStyle, 0);

    mBorderWidth = a.getDimensionPixelSize(R.styleable.CircleImageView_civ_border_width, DEFAULT_BORDER_WIDTH);
    mBorderColor = a.getColor(R.styleable.CircleImageView_civ_border_color, DEFAULT_BORDER_COLOR);
    mBorderOverlay = a.getBoolean(R.styleable.CircleImageView_civ_border_overlay, DEFAULT_BORDER_OVERLAY);
    mFillColor = a.getColor(R.styleable.CircleImageView_civ_fill_color, DEFAULT_FILL_COLOR);
    a.recycle();
    init();
}

第一個構造函數是在代碼中new對象的時候執行,第二個是在XML中引用的時候調用,這裏跟我們一般定義控件沒什麼區別。我們接下來就尋找程序入口,看他是如何運行的,按照我們一般使用View來說,首先看一種情況,在XML裏邊引用,並且不設置android:src屬性,我們知道,在XML裏邊引用程序會走我們的第二個構造函數,好吧,我們看一下第二個構造函數:

public CircleImageView(Context context, AttributeSet attrs) {
    this(context, attrs, 0);
}

可以看到,在我們的第二個構造函數中調用了我們第三個構造函數,三參構造函數如下:

public CircleImageView(Context context, AttributeSet attrs) {
    this(context, attrs, 0);
}
可以看到,在我們的第二個構造函數中調用了我們第三個構造函數,三參構造函數如下:
public CircleImageView(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
    System.out.println("Log_多參構造");
    TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CircleImageView, defStyle, 0);
    mBorderWidth = a.getDimensionPixelSize(R.styleable.CircleImageView_civ_border_width, DEFAULT_BORDER_WIDTH);
    mBorderColor = a.getColor(R.styleable.CircleImageView_civ_border_color, DEFAULT_BORDER_COLOR);
    mBorderOverlay = a.getBoolean(R.styleable.CircleImageView_civ_border_overlay, DEFAULT_BORDER_OVERLAY);
    mFillColor = a.getColor(R.styleable.CircleImageView_civ_fill_color, DEFAULT_FILL_COLOR);
    a.recycle();
    init();
}

這裏很簡單,就是通過TypedArray獲取我們在XML中設置的參數值並賦值給相應參數,外圓環寬度、外圓環顏色、圓環是否壓住圖片、圖片背景。然後調用了init()方法,下邊看一下init()方法:

private void init() {
    super.setScaleType(SCALE_TYPE);
    mReady = true;

    if (mSetupPending) {
        setup();
        mSetupPending = false;
    }
}

可以看見在這裏調用了父類的setScaleType()方法傳入了一個SCALE_TYPE變量,這是什麼東西呢?看一下它的定義,

private static final ScaleType SCALE_TYPE = ScaleType.CENTER_CROP;

可以看到它是final的,是不可以修改的,在本篇的第二小結設置ScaleType()屬性時,也可以看到,我們繼承ImageView後的CircleImageView只支持CENTER_CROP這一種設置,

關於ImageView.ScaleType()相關知識可以查看如下文章:

http://blog.csdn.net/buaaroid/article/details/49360779 接着它將mRead置爲true,這裏想不用管,只知道她在初始化的時候是false就行了。接着往下,判斷了一個mSetupPending屬性,這個屬性因爲在一開是false的,所以不會進入括號內,所以更不會調用我們的setup()方法。難道這樣就完了嗎?不會,因爲我們在XML中沒有設置圖片相關信息,那麼我們肯定要在代碼裏邊設置了,那麼我們在代碼裏就需要綁定xml中的View然後調用circleImageView.setImageResource(R.mipmap.psb);我們在上一節中說過在CircleImageView中用四個方法可以設置圖片,這個就是其中之一,好吧,我們接着看它的內部實現:

@Override
public void setImageResource(@DrawableRes int resId) {
    super.setImageResource(resId);
    initializeBitmap();
}

在代碼中調用父類的setImageResource()方法設置圖片,並調用initializeBitmap()方法,繼續看:

private void initializeBitmap() {
    if (mDisableCircularTransformation) {
        mBitmap = null;
    } else {
        mBitmap = getBitmapFromDrawable(getDrawable());
    }
    setup();
}

判斷是否禁止圓形屬性,禁止mBitmap爲null,不禁止獲取到我們設置的Drawable並通過getBitmapFromDrawable()方法轉換成mBitmap,然後調用setup()方法,看setup()方法:

private void setup() {
        if (!mReady) {
            mSetupPending = true;
            return;
        }

        if (getWidth() == 0 && getHeight() == 0) {
            return;
        }

        if (mBitmap == null) {
            invalidate();
            return;
        }
//        TileMode:(一共有三種)
//        CLAMP  :如果渲染器超出原始邊界範圍,會複製範圍內邊緣染色。
//        REPEAT :橫向和縱向的重複渲染器圖片,平鋪。
//        MIRROR :橫向和縱向的重複渲染器圖片,這個和REPEAT重複方式不一樣,他是以鏡像方式平鋪。
        mBitmapShader = new BitmapShader(mBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
        //抗鋸齒
        mBitmapPaint.setAntiAlias(true);

        mBitmapPaint.setShader(mBitmapShader);

        //Paint.Style.FILL:填充內部
        //Paint.Style.FILL_AND_STROKE  :填充內部和描邊
        //Paint.Style.STROKE  :描邊
        mBorderPaint.setStyle(Paint.Style.STROKE);
        mBorderPaint.setAntiAlias(true);
        mBorderPaint.setColor(mBorderColor);
        mBorderPaint.setStrokeWidth(mBorderWidth);

        mFillPaint.setStyle(Paint.Style.FILL);
        mFillPaint.setAntiAlias(true);
        mFillPaint.setColor(mFillColor);
        //取的原圖片的寬高
        mBitmapHeight = mBitmap.getHeight();
        mBitmapWidth = mBitmap.getWidth();
        mBorderRect.set(calculateBounds());
        //計算整個圓形帶Border部分的最小半徑,取mBorderRect的寬高減去一個邊緣大小的一半的較小值
        mBorderRadius = Math.min((mBorderRect.height() - mBorderWidth) / 2.0f, (mBorderRect.width() - mBorderWidth) / 2.0f);
        //初始圖片顯示區域爲mBorderRect(CircleImageView中圖片區域的實際大小)
        mDrawableRect.set(mBorderRect);
        if (!mBorderOverlay && mBorderWidth > 0) {
            //到現在圖片區域Rect(mDrawableRect)與整個View所用Rect(mBorderRadius)相同【mDrawableRect.set(mBorderRect)設置】,
            //如果在xml中設置app:civ_border_overlay="false"(邊框不覆蓋圖片)並且外框寬度大於0,將圖片顯示區域Rect向內(縮小)mBorderWidth-1.0f。
            // inset()方法參數爲正數表示縮小,爲複數表示擴大區域。
            mDrawableRect.inset(mBorderWidth - 1.0f, mBorderWidth - 1.0f);
        }
        //計算內圓最小半徑,即去除邊框後的Rect(內部圖片Rect->mDrawableRect)寬度的半徑
        mDrawableRadius = Math.min(mDrawableRect.height() / 2.0f, mDrawableRect.width() / 2.0f);

        applyColorFilter();
        updateShaderMatrix();
        invalidate();
    }

看一下,很長,沒錯,這個方法是當前類裏最主要的一個方法,先說一下它做了幾件事:

1.上來三個判斷,一會說。

2.設置三個重要的paint及mBitmapPaint(畫內部圓形圖片用到的Paint)、mBorderpaint(畫外部圓環用到的paint)、mFillPaint(畫圖片背景用到的paint)。

3.設置mBorderRect(外部圓環所佔矩形區域)mBorderRadius(外部圓環半徑)、mDrawableRect(內部圖片所佔矩形區域)、mDrawableRaduis(內部圓形圖片半徑)。

4.設置顏色過濾器。

5.設置BitmapShader的Matrix,設置縮放比,平移。

6.調用invaladate()刷新界面

這就是在setup()方法中乾的幾件事情,下面我們詳細說明,回到代碼,首先是三個判斷,第一個判斷mReady,因爲我們構造函數中已經將其變成了true,所以不會進入內部,而是繼續向下走。這裏進一段小插曲,到目前爲止,肯定很多人不明白,這個mReady及內部的mSetupPending 是幹什麼用的,這裏說明一下,回到前邊說的在XML中引入,但是沒有設置android:src屬性,以上都是它的執行順序,那麼,我們換另一種方式,及在XML文件中加入android:src屬性,運行代碼,你會發現,我們四個設置圖片方法的第一個方法(setImageBitmap()方法)會被執行,而且是在構造方法以前執行,我們知道,在它裏邊也間接的調用了我們的setup()方法,但是此時我們的構造函數還沒有執行,也就是說一些參數還沒有被初始化,所以現在肯定是不能進行後續操作的,所以在這種情況下,當執行到setup()方法的時候第一個mReady(初始化爲false)判斷是過不去的,只是把mSetupPending設置成了true,然後return。接着纔會執行我們的構造函數,在構造函數裏邊同樣有一個關於mReadymSetuppending的操作,在init()中,

private void init() {
    super.setScaleType(SCALE_TYPE);
    mReady = true;

    if (mSetupPending) {
        setup();
        mSetupPending = false;
    }
}

因爲我們前邊在setImageBitmap()中將mSetupPending設置爲了true,所以會進入setup()方法,說了這麼多,不知道你聽懂了沒,多想多看幾遍,相信你肯定能明白設計mReady、與mSetupPending的意義,就是在不同的情況下保證程序以正確的方式進行邏輯處理。多看幾遍,只能幫你到這了。

插曲還挺長,接着看我們setup()中的代碼,後續兩個判斷一個當前View寬高爲0退出,一個沒有獲取到mBitmap退出,沒什麼好說的。接着往下走:

TileMode:(一共有三種)
//        CLAMP  :如果渲染器超出原始邊界範圍,會複製範圍內邊緣染色。
//        REPEAT :橫向和縱向的重複渲染器圖片,平鋪。
//        MIRROR :橫向和縱向的重複渲染器圖片,這個和REPEAT重複方式不一樣,他是以鏡像方式平鋪。

mBitmapShader = new BitmapShader(mBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
//抗鋸齒
mBitmapPaint.setAntiAlias(true);

mBitmapPaint.setShader(mBitmapShader);

//Paint.Style.FILL:填充內部
//Paint.Style.FILL_AND_STROKE  :填充內部和描邊
//Paint.Style.STROKE  :描邊
mBorderPaint.setStyle(Paint.Style.STROKE);
mBorderPaint.setAntiAlias(true);
mBorderPaint.setColor(mBorderColor);
mBorderPaint.setStrokeWidth(mBorderWidth);

mFillPaint.setStyle(Paint.Style.FILL);
mFillPaint.setAntiAlias(true);
mFillPaint.setColor(mFillColor);

相信一些設置畫筆的沒什麼好說的吧。看一下設置矩形跟半徑相關的吧,如下:

//取的原圖片的寬高
mBitmapHeight = mBitmap.getHeight();
mBitmapWidth = mBitmap.getWidth();
mBorderRect.set(calculateBounds());
//計算整個圓形帶Border部分的最小半徑,取mBorderRect的寬高減去一個邊緣大小的一半的較小值
mBorderRadius = Math.min((mBorderRect.height() - mBorderWidth) / 2.0f, (mBorderRect.width() - mBorderWidth) / 2.0f);
//初始圖片顯示區域爲mBorderRect(CircleImageView中圖片區域的實際大小)
mDrawableRect.set(mBorderRect);
if (!mBorderOverlay && mBorderWidth > 0) {
    //到現在圖片區域Rect(mDrawableRect)與整個View所用Rect(mBorderRadius)相同【mDrawableRect.set(mBorderRect)設置】,
    //如果在xml中設置app:civ_border_overlay="false"(邊框不覆蓋圖片)並且外框寬度大於0,將圖片顯示區域Rect向內(縮小)mBorderWidth-1.0f。
    // inset()方法參數爲正數表示縮小,爲複數表示擴大區域。
    mDrawableRect.inset(mBorderWidth - 1.0f, mBorderWidth - 1.0f);
}
//計算內圓最小半徑,即去除邊框後的Rect(內部圖片Rect->mDrawableRect)寬度的半徑
mDrawableRadius = Math.min(mDrawableRect.height() / 2.0f, mDrawableRect.width() / 2.0f);

可以看到在設置外環矩形時調用了一個calculateBounds()方法看看裏邊的實現:

private RectF calculateBounds() {
    //獲取當前CircleImageView視圖除去PaddingLeft與PaddingRight後剩餘的可用寬度
    // (如果你設置的PaddingLeft+PaddingRight>+當前控件的寬度,當前控件會顯示不出來);
    int availableWidth = getWidth() - getPaddingLeft() - getPaddingRight();
    //獲取當前CircleImageView視圖除去PaddingTop與PaddingBottom後剩餘的可用高度;
    int availableHeight = getHeight() - getPaddingTop() - getPaddingBottom();
    //獲取除去Padding後寬高剩餘可用空間較小的一個值。
    int sideLength = Math.min(availableWidth, availableHeight);
    //如果最後得到的availableWidth與availableHeight不一樣(我們在代碼中設置的原因),大的要向小的靠齊,
    // 最終得到的RectF爲正方形。
    float left = getPaddingLeft() + (availableWidth - sideLength) / 2f;
    float top = getPaddingTop() + (availableHeight - sideLength) / 2f;
    return new RectF(left, top, left + sideLength, top + sideLength);
}

代碼我已經加好了註釋,多看幾遍。設置好圓環矩形後,計算整個圓形帶Border部分的最小半徑,注意這裏計算半徑時寬高需要減去mBorderWidth再除以2,取mBorderRect的寬高減去一個邊緣大小的一半的較小值做爲半徑。然後將mBorderRect設置給mDrawableRect,然後判斷我們是否設置了圓環壓住圓形圖片並且mBorderWidth>0,如下:

if (!mBorderOverlay && mBorderWidth > 0) {
    mDrawableRect.inset(mBorderWidth - 1.0f, mBorderWidth - 1.0f);
}

如果都滿足,則mDrawableRect的x,y都縮小(mBorderWidth-1.0f),然後計算出mDrawableRaduis。

然後調用了applyColorFilter()方法,看一下:

private void applyColorFilter() {
    if (mBitmapPaint != null) {
        mBitmapPaint.setColorFilter(mColorFilter);
    }
}

可以看到,就是給mBitmapPaint設置了mColorFiter,mColorFiter是通過上一節中的public方法設置的,如果我們沒有設置,mColorFilter爲null。

然後是我們的updateShaderMatrix()這個方法也很重要,看一下:

private void updateShaderMatrix() {
    float scale;
    float dx = 0;
    float dy = 0;

    mShaderMatrix.set(null);
    //比較圖片和所繪區域寬縮放比、高縮放比,那個小。取小的,作爲矩陣的縮放比。
    //代碼不太好理解,等價於(mBitmapWidth / mDrawableRect.width()) > (mBitmapHeight / mDrawableRect.height())
    if (mBitmapWidth * mDrawableRect.height() > mDrawableRect.width() * mBitmapHeight) {
        scale = mDrawableRect.height() / (float) mBitmapHeight;
        dx = (mDrawableRect.width() - mBitmapWidth * scale) * 0.5f;
    } else {
        scale = mDrawableRect.width() / (float) mBitmapWidth;
        dy = (mDrawableRect.height() - mBitmapHeight * scale) * 0.5f;
    }
    //設置縮放比
    mShaderMatrix.setScale(scale, scale);
    //平移操作,(dx + 0.5f)的處理,是四捨五入
    mShaderMatrix.postTranslate((int) (dx + 0.5f) + mDrawableRect.left, (int) (dy + 0.5f) + mDrawableRect.top);

    mBitmapShader.setLocalMatrix(mShaderMatrix);
}

東西不多,但是重要啊,在計算縮放比scale時有一行代碼不好理解,也轉換了一下,好理解一點了,這裏可以自己畫一個圖理解一下,下面是我舉得一個例子,你可以對着我畫的圖,理解一下:


mShaderMatrix按照算出來的scale進行縮放,並進行相應的平移,最後賦給mBitmapShadermBitmapShadersetup()方法中已經付給了mBitmapPaint。最後就是調用invaladate()刷新界面了,調用invaladate()會執行onDraw()方法,下邊看一下:

@Override
protected void onDraw(Canvas canvas) {
    // 是否允許轉換成圓形設置
    if (mDisableCircularTransformation) {
        super.onDraw(canvas);
        return;
    }

    if (mBitmap == null) {
        return;
    }

    //如果設置了圖片底色,繪製圖片底色。
    if (mFillColor != Color.TRANSPARENT) {
        canvas.drawCircle(mDrawableRect.centerX(), mDrawableRect.centerY(), mDrawableRadius, mFillPaint);
    }
    //畫內部圖片區域(我們給mBitmapPaint設置了Shader,給Shader設置了LocalMatrix,通過ShaderMatrix設置了縮放比,及平移操作完成功能);
    canvas.drawCircle(mDrawableRect.centerX(), mDrawableRect.centerY(), mDrawableRadius, mBitmapPaint);
    //如果設置了BorderWidth寬度,繪製;
    if (mBorderWidth > 0) {
        canvas.drawCircle(mBorderRect.centerX(), mBorderRect.centerY(), mBorderRadius, mBorderPaint);
    }
}

OnDraw()方法很簡單,就是用我們在setup()方法中設置的畫筆進行繪畫。在我們的上一節中的一些共有方法中會看到,其實很多方法都調用了invaladate()或者是setup()方法,對view進行了重新繪製。

 

到此,我們的代碼就講解完了,不知道你對CircleImageView的實現更加了解了沒,如果看一遍看不明白,多看幾遍。

 

4.用到的知識點的總計

通過源碼分析我們可以知道,代碼中作者用到了下邊一些東西輔助完成功能:

1.ImageView.ScaleType

2.RectF

3.Matrix

4.Paint

5.BitmapShader

6.ColorFilter

 

我已將我加好詳細註釋的整個Demo文檔上傳至CSDN,你可以在下邊連接進行下載:

http://download.csdn.net/detail/liuyonglei1314/9754395 

 

好了,到現在我們本篇文章就該結束了,希望對您有所幫助,謝謝,如果哪裏寫的不對,希望留言指正!


發佈了29 篇原創文章 · 獲贊 29 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章