首先這篇文章我們從以下四個方面進行一一講解1.CircleImageView在AS中集成及用法;2.CircleImageView中定義的對外的方法;3.源碼解析;4.用到的知識點的總計。希望通過本篇文章的學習,您會對自定義控件及自定義控件中用到的一些類有一定的瞭解,最後我會把我添加好詳細註釋的Demo下載地址附上,你也可以一邊看我的Demo,一邊看這篇文章,效果應該會更好,那麼,我們這就開始吧!
1.CircleImageView在AS中集成及用法
我們想在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。接着纔會執行我們的構造函數,在構造函數裏邊同樣有一個關於mReady與mSetuppending的操作,在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進行縮放,並進行相應的平移,最後賦給mBitmapShader,mBitmapShader在setup()方法中已經付給了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
好了,到現在我們本篇文章就該結束了,希望對您有所幫助,謝謝,如果哪裏寫的不對,希望留言指正!