我們使用開源控件CircleImageView來實現(xiàn)該效果雳窟。
CircleImageView項目下載地址:
https://github.com/hdodenhof/CircleImageView
(1).CircleImageView的使用
首先我們將CircleImageView添加到gradle躏救。
[html]view plaincopy
dependencies?{??
????compile?'de.hdodenhof:circleimageview:2.1.0'??
}??
然后看一下自定義屬性attrs:
[html]view plaincopy
??
??
??
??
??
屬性介紹:civ_border_width: ? 設(shè)置邊框的寬度颖杏,默認(rèn)為0,即無邊框政溃。
civ_border_color: ? ?設(shè)置邊框的顏色趾访,默認(rèn)為黑色。
civ_border_overlay:設(shè)置邊框是否覆蓋在圖片上董虱,默認(rèn)為false扼鞋,即邊框在圖片外圈。
civ_fill_color: ? ? ? ? ? 設(shè)置圖片的底色愤诱,默認(rèn)透明云头。
接下來在布局文件中引入CircleImageView。
[html]view plaincopy
xmlns:circleimageview="http://schemas.android.com/apk/res-auto"??
android:id="@+id/imageview"??
android:layout_width="wrap_content"??
android:layout_height="wrap_content"??
android:src="@drawable/profile"??
circleimageview:civ_border_color="@android:color/holo_red_light"??
circleimageview:civ_border_overlay="false"??
circleimageview:civ_border_width="2dp"??
circleimageview:civ_fill_color="@android:color/holo_blue_light"/>??
注意:CircleImageView的默認(rèn)ScaleType為CENTER_CROP淫半,且只能為CENTER_CROP溃槐。
下面來詳細(xì)分析一下源碼:
1.?首先從項目里調(diào)用CircleImageView開始分析,我們要么在XML里面用src科吭,要么調(diào)用CircleImageView的setImageXXX()方法設(shè)置圖片昏滴。那我們的源碼的運(yùn)行入口在哪里呢,是從構(gòu)造函數(shù)CircleImageView()開始呢還是從setImageXXX()開始砌溺?一開始就卡殼了影涉。一開始我以為是從構(gòu)造函數(shù)CircleImageView()開始跑,結(jié)果分析下來發(fā)現(xiàn)并不會進(jìn)入setup();所以這是行不通的规伐,那接下來就要論證是不是從setImageXXX()開始呢蟹倾?我的方法是分別在兩者進(jìn)行System.out.println測試,看看誰先執(zhí)行猖闪。測試結(jié)果會發(fā)現(xiàn)是從setImageXXX()開始鲜棠。
那我們看下源碼:
[js]?view plain?copy
/**
?????*?以下四個函數(shù)都是
?????*?復(fù)寫ImageView的setImageXxx()方法?
?????*?注意這個函數(shù)先于構(gòu)造函數(shù)調(diào)用之前調(diào)用
?????*?@param?bm
?????*/
????@Override
????public?void?setImageBitmap(Bitmap?bm)?{
????????super.setImageBitmap(bm);
????????mBitmap?=?bm;
????????setup();
????}
????@Override
????public?void?setImageDrawable(Drawable?drawable)?{
????????super.setImageDrawable(drawable);
????????mBitmap?=?getBitmapFromDrawable(drawable);
????????System.out.println("setImageDrawable?--?setup");??
????????setup();
????}
????@Override
????public?void?setImageResource(@DrawableRes?int?resId)?{
????????super.setImageResource(resId);
????????mBitmap?=?getBitmapFromDrawable(getDrawable());
????????setup();
????}
????@Override
????public?void?setImageURI(Uri?uri)?{
????????super.setImageURI(uri);
????????mBitmap?=?getBitmapFromDrawable(getDrawable());
????????setup();
????}
看上面代碼會發(fā)現(xiàn),四個函數(shù)都是獲取圖片Bitmap培慌,調(diào)用setup()豁陆。
接下來那我們查看setup()源碼:
[js]?view plain?copy
//因為mReady默認(rèn)值為false,所以第一次進(jìn)這個函數(shù)的時候if語句為真進(jìn)入括號體內(nèi)
//設(shè)置mSetupPending為true然后直接返回,后面的代碼并沒有執(zhí)行吵护。
if?(!mReady)?{
????mSetupPending?=?true;
????return;
}
會發(fā)現(xiàn)第一次進(jìn)入時盒音,第一個if語句為真,進(jìn)入括號體內(nèi)設(shè)置mSetupPending為true然后直接返回馅而,后面的代碼并沒有執(zhí)行祥诽。為什么要這樣設(shè)置mReady和mSetupPending呢不執(zhí)行下面的代碼呢,不要急瓮恭,后面再解釋雄坪。
2.?接下來我們會進(jìn)入構(gòu)造函數(shù)CircleImageView()。查看源碼:
[js]?view plain?copy
????/**
?????*?構(gòu)造函數(shù)
?????*/
????public?CircleImageView(Context?context,?AttributeSet?attrs,?int?defStyle)?{
????????super(context,?attrs,?defStyle);
????????//通過obtainStyledAttributes?獲得一組值賦給?TypedArray(數(shù)組)?,?這一組值來自于res/values/attrs.xml中的name="CircleImageView"的declare-styleable中屯蹦。
????????TypedArray?a?=?context.obtainStyledAttributes(attrs,?R.styleable.CircleImageView,?defStyle,?0);
????????//通過TypedArray提供的一系列方法getXXXX取得我們在xml里定義的參數(shù)值维哈;
????????//?獲取邊界的寬度
????????mBorderWidth?=?a.getDimensionPixelSize(R.styleable.CircleImageView_border_width,?DEFAULT_BORDER_WIDTH);
????????//?獲取邊界的顏色
????????mBorderColor?=?a.getColor(R.styleable.CircleImageView_border_color,?DEFAULT_BORDER_COLOR);
????????mBorderOverlay?=?a.getBoolean(R.styleable.CircleImageView_border_overlay,?DEFAULT_BORDER_OVERLAY);
????????//調(diào)用?recycle()?回收TypedArray,以便后面重用
????????a.recycle();
????????System.out.println("CircleImageView?--?構(gòu)造函數(shù)");??
????????init();
????}
分析上面代碼會發(fā)現(xiàn)很簡單绳姨,就是首先通過TypedArray獲取自定義參數(shù),再調(diào)用init()函數(shù):
[js]?view plain?copy
????/**
?????*?作用就是保證第一次執(zhí)行setup函數(shù)里下面代碼要在構(gòu)造函數(shù)執(zhí)行完畢時調(diào)用?
?????*/
????private?void?init()?{
????????//在這里ScaleType被強(qiáng)制設(shè)定為CENTER_CROP阔挠,就是將圖片水平垂直居中飘庄,進(jìn)行縮放。
????????super.setScaleType(SCALE_TYPE);
????????mReady?=?true;
????????if?(mSetupPending)?{
????????????setup();
????????????mSetupPending?=?false;
????????}
????}
因為在前面mSetupPending為真谒亦,則執(zhí)行setup()竭宰,而且這里mReady 也為true,所以會執(zhí)行setup()下面的代碼份招。為什么要這樣設(shè)置mSetupPending和mReady呢切揭?
這是因為第一次進(jìn)入setImageXXX()時候,我們無法獲取自定義參數(shù)值锁摔,所以setup()下面的代碼無法繪制我們想要的樣式而是默認(rèn)樣式廓旬。而獲取自定義參數(shù)只能在構(gòu)造函數(shù)里,這樣我們就能明白大神作者的意圖了谐腰,通過設(shè)置mSetupPending和mReady控制第一次執(zhí)行setup函數(shù)里下面代碼要在構(gòu)造函數(shù)執(zhí)行完畢時孕豹。再者如果用戶再進(jìn)行setImageXXX()設(shè)置圖片的話,就直接會執(zhí)行setup()下面的代碼十气,因為這之后mReady一直為true励背。
3?說了這么多,我們來看看setup()下面的代碼到底是啥砸西?沒錯叶眉,它就是這么酷炫,很關(guān)鍵:
[js]?view plain?copy
????/**
?????*?這個函數(shù)很關(guān)鍵芹枷,進(jìn)行圖片畫筆邊界畫筆(Paint)一些重繪參數(shù)初始化:
?????*?構(gòu)建渲染器BitmapShader用Bitmap來填充繪制區(qū)域,設(shè)置樣式以及內(nèi)外圓半徑計算等衅疙,
?????*?以及調(diào)用updateShaderMatrix()函數(shù)和?invalidate()函數(shù);
?????*/
????private?void?setup()?{
????????//因為mReady默認(rèn)值為false,所以第一次進(jìn)這個函數(shù)的時候if語句為真進(jìn)入括號體內(nèi)
????????//設(shè)置mSetupPending為true然后直接返回鸳慈,后面的代碼并沒有執(zhí)行饱溢。
????????if?(!mReady)?{
????????????mSetupPending?=?true;
????????????return;
????????}
????????//防止空指針異常
????????if?(mBitmap?==?null)?{
????????????return;
????????}
????????//?構(gòu)建渲染器,用mBitmap來填充繪制區(qū)域?走芋,參數(shù)值代表如果圖片太小的話?就直接拉伸
????????mBitmapShader?=?new?BitmapShader(mBitmap,?Shader.TileMode.CLAMP,?Shader.TileMode.CLAMP);
????????//?設(shè)置圖片畫筆反鋸齒
????????mBitmapPaint.setAntiAlias(true);
????????//?設(shè)置圖片畫筆渲染器
????????mBitmapPaint.setShader(mBitmapShader);
????????//?設(shè)置邊界畫筆樣式
????????mBorderPaint.setStyle(Paint.Style.STROKE);
????????mBorderPaint.setAntiAlias(true);
????????mBorderPaint.setColor(mBorderColor);????//畫筆顏色
????????mBorderPaint.setStrokeWidth(mBorderWidth);//畫筆邊界寬度
????????//這個地方是取的原圖片的寬高
????????mBitmapHeight?=?mBitmap.getHeight();
????????mBitmapWidth?=?mBitmap.getWidth();
????????//?設(shè)置含邊界顯示區(qū)域绩郎,取的是CircleImageView的布局實際大小,為方形翁逞,查看xml也就是160dp(240px)??getWidth得到是某個view的實際尺寸
????????mBorderRect.set(0,?0,?getWidth(),?getHeight());
????????//計算?圓形帶邊界部分(外圓)的最小半徑嗽上,取mBorderRect的寬高減去一個邊緣大小的一半的較小值(這個地方我比較納悶為什么求外圓半徑需要先減去一個邊緣大小)
????????mBorderRadius?=?Math.min((mBorderRect.height()?-?mBorderWidth)?/?2,?(mBorderRect.width()?-?mBorderWidth)?/?2);
????????//?初始圖片顯示區(qū)域為mBorderRect(CircleImageView的布局實際大邢ㄈ痢)
????????mDrawableRect.set(mBorderRect);
????????if?(!mBorderOverlay)?{
????????????//demo里始終執(zhí)行
????????????//通過inset方法??使得圖片顯示的區(qū)域從mBorderRect大小上下左右內(nèi)移邊界的寬度形成區(qū)域,查看xml邊界寬度為2dp(3px),所以方形邊長為就是160-4=156dp(234px)
????????????mDrawableRect.inset(mBorderWidth,?mBorderWidth);
????????}
????????//這里計算的是內(nèi)圓的最小半徑彼念,也即去除邊界寬度的半徑
????????mDrawableRadius?=?Math.min(mDrawableRect.height()?/?2,?mDrawableRect.width()?/?2);
????????//設(shè)置渲染器的變換矩陣也即是mBitmap用何種縮放形式填充
????????updateShaderMatrix();
????????//手動觸發(fā)ondraw()函數(shù)?完成最終的繪制
????????invalidate();
????}
上面代碼注釋我寫的很詳細(xì)不再一步步解釋了挪圾,進(jìn)行圖片畫筆邊界畫筆(Paint)一些重繪參數(shù)初始化:構(gòu)建渲染器BitmapShader用Bitmap來填充繪制區(qū)域,設(shè)置樣式以及內(nèi)外圓半徑計算等浅萧,以及調(diào)用updateShaderMatrix()函數(shù)和 invalidate()函數(shù)。
這里關(guān)于半徑的計算哲思,我畫圖舉個例子:CircleImageView的布局寬高度均為160洼畅,邊界的寬度為10如圖所示:
那么去除邊界寬度的內(nèi)圓半徑為70,帶邊界部分的外圓半徑為75棚赔。
4?根據(jù)上面帝簇,接下來進(jìn)入updateShaderMatrix()函數(shù),查看源碼:
[js]?view plain?copy
????/**
????*?這個函數(shù)為設(shè)置BitmapShader的Matrix參數(shù)靠益,設(shè)置最小縮放比例丧肴,平移參數(shù)。
????*?作用:保證圖片損失度最小和始終繪制圖片正中央的那部分
????*/
????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)?{
????????????//y軸縮放?x軸平移?使得圖片的y軸方向的邊的尺寸縮放到圖片顯示區(qū)域(mDrawableRect)一樣)
????????????scale?=?mDrawableRect.height()?/?(float)?mBitmapHeight;
????????????dx?=?(mDrawableRect.width()?-?mBitmapWidth?*?scale)?*?0.5f;
????????}?else?{
????????????//x軸縮放?y軸平移?使得圖片的x軸方向的邊的尺寸縮放到圖片顯示區(qū)域(mDrawableRect)一樣)
????????????scale?=?mDrawableRect.width()?/?(float)?mBitmapWidth;
????????????dy?=?(mDrawableRect.height()?-?mBitmapHeight?*?scale)?*?0.5f;
????????}
????????//?shaeder的變換矩陣胧后,我們這里主要用于放大或者縮小芋浮。
????????mShaderMatrix.setScale(scale,?scale);
????????//?平移
????????mShaderMatrix.postTranslate((int)?(dx?+?0.5f)?+?mDrawableRect.left,?(int)?(dy?+?0.5f)?+?mDrawableRect.top);
????????//?設(shè)置變換矩陣
????????mBitmapShader.setLocalMatrix(mShaderMatrix);
????}
通過updateShaderMatrix函數(shù)設(shè)置BitmapShader的Matrix參數(shù),對圖片mBitmap位置用何種縮放平移形式填充壳快。
[js]?view plain?copy
if判斷語句里
mBitmapWidth?*?mDrawableRect.height()?>?mDrawableRect.width()?*?mBitmapHeight
寫成
(mBitmapWidth?/?mDrawableRect.width())?>?(mBitmapHeight?/?mDrawableRect.height())
更好理解纸巷。
目的是用最小的縮放比例,使得圖片的某個方向的邊的尺寸縮放到圖片顯示區(qū)域(mDrawableRect)一樣眶痰。做到了圖片損失度最小瘤旨。同時scale保證Bitmap的寬或高和目標(biāo)區(qū)域一致,那么高或?qū)捑托枰M(jìn)行位移竖伯,使得Bitmap居中存哲。這里寫得很nice。
5?現(xiàn)在萬事俱備黔夭,只欠ondraw()了宏胯。接著上面再setup()最后會調(diào)用invalidate()函數(shù)觸發(fā)ondraw()函數(shù)完成最終的繪制。查看源碼:
[js]?view plain?copy
????@Override
????protected?void?onDraw(Canvas?canvas)?{
????????//如果圖片不存在就不畫
????????if?(getDrawable()?==?null)?{
????????????return;
????????}
????????//繪制內(nèi)圓形本姥,參數(shù)內(nèi)圓半徑肩袍,圖片畫筆為mBitmapPaint
????????canvas.drawCircle(getWidth()?/?2,?getHeight()?/?2,?mDrawableRadius,?mBitmapPaint);
??????//如果圓形邊緣的寬度不為0?我們還要繪制帶邊界的外圓形?參數(shù)外圓半徑,邊界畫筆為mBorderPaint
????????if?(mBorderWidth?!=?0)?{
????????????canvas.drawCircle(getWidth()?/?2,?getHeight()?/?2,?mBorderRadius,?mBorderPaint);
????????}
????}
使用配置好的mBitmapPaint和mBorderPaint先畫出繪制內(nèi)圓形來以后再畫邊界圓形婚惫。源碼還有一些自定義設(shè)置樣式函數(shù)氛赐,很簡單。
大功告成先舷,是不是覺得思路比較簡單艰管,精致干練。
流程控制的比較嚴(yán)謹(jǐn)蒋川,比如setup函數(shù)的使用
updateShaderMatrix保證圖片損失度最小和始終繪制圖片正中央的那部分
作者思路是畫圓用渲染器位圖填充牲芋,而不是把Bitmap重繪切割成一個圓形圖片。
附上完整源碼:
[js]?view plain?copy
package?com.example.library;
import?com.example.circleimageviewdemo_1.R;
import?android.content.Context;
import?android.content.res.TypedArray;
import?android.graphics.Bitmap;
import?android.graphics.BitmapShader;
import?android.graphics.Canvas;
import?android.graphics.Color;
import?android.graphics.ColorFilter;
import?android.graphics.Matrix;
import?android.graphics.Paint;
import?android.graphics.RectF;
import?android.graphics.Shader;
import?android.graphics.drawable.BitmapDrawable;
import?android.graphics.drawable.ColorDrawable;
import?android.graphics.drawable.Drawable;
import?android.net.Uri;
import?android.support.annotation.ColorRes;
import?android.support.annotation.DrawableRes;
import?android.util.AttributeSet;
import?android.widget.ImageView;
/**
??*?流程控制的比較嚴(yán)謹(jǐn),比如setup函數(shù)的使用
??*?updateShaderMatrix保證圖片損失度最小和始終繪制圖片正中央的那部分
??*?作者思路是畫圓用渲染器位圖填充缸浦,而不是把Bitmap重繪切割成一個圓形圖片夕冲。
??*/
public?class?CircleImageView?extends?ImageView?{
????//縮放類型
????private?static?final?ScaleType?SCALE_TYPE?=?ScaleType.CENTER_CROP;
????private?static?final?Bitmap.Config?BITMAP_CONFIG?=?Bitmap.Config.ARGB_8888;
????private?static?final?int?COLORDRAWABLE_DIMENSION?=?2;
????//?默認(rèn)邊界寬度
????private?static?final?int?DEFAULT_BORDER_WIDTH?=?0;
????//?默認(rèn)邊界顏色
????private?static?final?int?DEFAULT_BORDER_COLOR?=?Color.BLACK;
????private?static?final?boolean?DEFAULT_BORDER_OVERLAY?=?false;
????private?final?RectF?mDrawableRect?=?new?RectF();
????private?final?RectF?mBorderRect?=?new?RectF();
????private?final?Matrix?mShaderMatrix?=?new?Matrix();
????//這個畫筆最重要的是關(guān)聯(lián)了mBitmapShader?使canvas在執(zhí)行的時候可以切割原圖片(mBitmapShader是關(guān)聯(lián)了原圖的bitmap的)
????private?final?Paint?mBitmapPaint?=?new?Paint();
????//這個描邊,則與本身的原圖bitmap沒有任何關(guān)聯(lián)裂逐,
????private?final?Paint?mBorderPaint?=?new?Paint();
????//這里定義了?圓形邊緣的默認(rèn)寬度和顏色
????private?int?mBorderColor?=?DEFAULT_BORDER_COLOR;
????private?int?mBorderWidth?=?DEFAULT_BORDER_WIDTH;
????private?Bitmap?mBitmap;
????private?BitmapShader?mBitmapShader;?//?位圖渲染
????private?int?mBitmapWidth;???//?位圖寬度
????private?int?mBitmapHeight;??//?位圖高度
????private?float?mDrawableRadius;//?圖片半徑
????private?float?mBorderRadius;//?帶邊框的的圖片半徑
????private?ColorFilter?mColorFilter;
????//初始false
????private?boolean?mReady;
????private?boolean?mSetupPending;
????private?boolean?mBorderOverlay;
????//構(gòu)造函數(shù)
????public?CircleImageView(Context?context)?{
????????super(context);
????????init();
????}
????//構(gòu)造函數(shù)
????public?CircleImageView(Context?context,?AttributeSet?attrs)?{
????????this(context,?attrs,?0);
????}
????/**
?????*?構(gòu)造函數(shù)
?????*/
????public?CircleImageView(Context?context,?AttributeSet?attrs,?int?defStyle)?{
????????super(context,?attrs,?defStyle);
????????//通過obtainStyledAttributes?獲得一組值賦給?TypedArray(數(shù)組)?,?這一組值來自于res/values/attrs.xml中的name="CircleImageView"的declare-styleable中歹鱼。
????????TypedArray?a?=?context.obtainStyledAttributes(attrs,?R.styleable.CircleImageView,?defStyle,?0);
????????//通過TypedArray提供的一系列方法getXXXX取得我們在xml里定義的參數(shù)值;
????????//?獲取邊界的寬度
????????mBorderWidth?=?a.getDimensionPixelSize(R.styleable.CircleImageView_border_width,?DEFAULT_BORDER_WIDTH);
????????//?獲取邊界的顏色
????????mBorderColor?=?a.getColor(R.styleable.CircleImageView_border_color,?DEFAULT_BORDER_COLOR);
????????mBorderOverlay?=?a.getBoolean(R.styleable.CircleImageView_border_overlay,?DEFAULT_BORDER_OVERLAY);
????????//調(diào)用?recycle()?回收TypedArray,以便后面重用
????????a.recycle();
????????System.out.println("CircleImageView?--?構(gòu)造函數(shù)");??
????????init();
????}
????/**
?????*?作用就是保證第一次執(zhí)行setup函數(shù)里下面代碼要在構(gòu)造函數(shù)執(zhí)行完畢時調(diào)用?
?????*/
????private?void?init()?{
????????//在這里ScaleType被強(qiáng)制設(shè)定為CENTER_CROP卜高,就是將圖片水平垂直居中弥姻,進(jìn)行縮放。
????????super.setScaleType(SCALE_TYPE);
????????mReady?=?true;
????????if?(mSetupPending)?{
????????????setup();
????????????mSetupPending?=?false;
????????}
????}
????@Override
????public?ScaleType?getScaleType()?{
????????return?SCALE_TYPE;
????}
????/**
??????????*?這里明確指出?此種imageview?只支持CENTER_CROP?這一種屬性
??????????*
??????????*?@param?scaleType
??????????*/
????@Override
????public?void?setScaleType(ScaleType?scaleType)?{
????????if?(scaleType?!=?SCALE_TYPE)?{
????????????throw?new?IllegalArgumentException(String.format("ScaleType?%s?not?supported.",?scaleType));
????????}
????}
????@Override
????public?void?setAdjustViewBounds(boolean?adjustViewBounds)?{
????????if?(adjustViewBounds)?{
????????????throw?new?IllegalArgumentException("adjustViewBounds?not?supported.");
????????}
????}
????@Override
????protected?void?onDraw(Canvas?canvas)?{
????????//如果圖片不存在就不畫
????????if?(getDrawable()?==?null)?{
????????????return;
????????}
????????//繪制內(nèi)圓形?圖片?畫筆為mBitmapPaint
????????canvas.drawCircle(getWidth()?/?2,?getHeight()?/?2,?mDrawableRadius,?mBitmapPaint);
??????//如果圓形邊緣的寬度不為0?我們還要繪制帶邊界的外圓形?邊界畫筆為mBorderPaint
????????if?(mBorderWidth?!=?0)?{
????????????canvas.drawCircle(getWidth()?/?2,?getHeight()?/?2,?mBorderRadius,?mBorderPaint);
????????}
????}
????@Override
????protected?void?onSizeChanged(int?w,?int?h,?int?oldw,?int?oldh)?{
????????super.onSizeChanged(w,?h,?oldw,?oldh);
????????setup();
????}
????public?int?getBorderColor()?{
????????return?mBorderColor;
????}
????public?void?setBorderColor(int?borderColor)?{
????????if?(borderColor?==?mBorderColor)?{
????????????return;
????????}
????????mBorderColor?=?borderColor;
????????mBorderPaint.setColor(mBorderColor);
????????invalidate();
????}
????public?void?setBorderColorResource(@ColorRes?int?borderColorRes)?{
????????setBorderColor(getContext().getResources().getColor(borderColorRes));
????}
????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();
????}
????/**
?????*?以下四個函數(shù)都是
?????*?復(fù)寫ImageView的setImageXxx()方法?
?????*?注意這個函數(shù)先于構(gòu)造函數(shù)調(diào)用之前調(diào)用
?????*?@param?bm
?????*/
????@Override
????public?void?setImageBitmap(Bitmap?bm)?{
????????super.setImageBitmap(bm);
????????mBitmap?=?bm;
????????setup();
????}
????@Override
????public?void?setImageDrawable(Drawable?drawable)?{
????????super.setImageDrawable(drawable);
????????mBitmap?=?getBitmapFromDrawable(drawable);
????????System.out.println("setImageDrawable?--?setup");??
????????setup();
????}
????@Override
????public?void?setImageResource(@DrawableRes?int?resId)?{
????????super.setImageResource(resId);
????????mBitmap?=?getBitmapFromDrawable(getDrawable());
????????setup();
????}
????@Override
????public?void?setImageURI(Uri?uri)?{
????????super.setImageURI(uri);
????????mBitmap?=?getBitmapFromDrawable(getDrawable());
????????setup();
????}
????@Override
????public?void?setColorFilter(ColorFilter?cf)?{
????????if?(cf?==?mColorFilter)?{
????????????return;
????????}
????????mColorFilter?=?cf;
????????mBitmapPaint.setColorFilter(mColorFilter);
????????invalidate();
????}
????/**
?????*?Drawable轉(zhuǎn)Bitmap
?????*?@param?drawable
?????*?@return
?????*/
????private?Bitmap?getBitmapFromDrawable(Drawable?drawable)?{
????????if?(drawable?==?null)?{
????????????return?null;
????????}
????????if?(drawable?instanceof?BitmapDrawable)?{
?????????????//通常來說?我們的代碼就是執(zhí)行到這里就返回了掺涛。返回的就是我們最原始的bitmap
????????????return?((BitmapDrawable)?drawable).getBitmap();
????????}
????????try?{
????????????Bitmap?bitmap;
????????????if?(drawable?instanceof?ColorDrawable)?{
????????????????bitmap?=?Bitmap.createBitmap(COLORDRAWABLE_DIMENSION,?COLORDRAWABLE_DIMENSION,?BITMAP_CONFIG);
????????????}?else?{
????????????????bitmap?=?Bitmap.createBitmap(drawable.getIntrinsicWidth(),?drawable.getIntrinsicHeight(),?BITMAP_CONFIG);
????????????}
????????????Canvas?canvas?=?new?Canvas(bitmap);
????????????drawable.setBounds(0,?0,?canvas.getWidth(),?canvas.getHeight());
????????????drawable.draw(canvas);
????????????return?bitmap;
????????}?catch?(OutOfMemoryError?e)?{
????????????return?null;
????????}
????}
????/**
?????*?這個函數(shù)很關(guān)鍵庭敦,進(jìn)行圖片畫筆邊界畫筆(Paint)一些重繪參數(shù)初始化:
?????*?構(gòu)建渲染器BitmapShader用Bitmap來填充繪制區(qū)域,設(shè)置樣式以及內(nèi)外圓半徑計算等,
?????*?以及調(diào)用updateShaderMatrix()函數(shù)和?invalidate()函數(shù)鸽照;
?????*/
????private?void?setup()?{
????????//因為mReady默認(rèn)值為false,所以第一次進(jìn)這個函數(shù)的時候if語句為真進(jìn)入括號體內(nèi)
????????//設(shè)置mSetupPending為true然后直接返回螺捐,后面的代碼并沒有執(zhí)行。
????????if?(!mReady)?{
????????????mSetupPending?=?true;
????????????return;
????????}
????????//防止空指針異常
????????if?(mBitmap?==?null)?{
????????????return;
????????}
????????//?構(gòu)建渲染器矮燎,用mBitmap位圖來填充繪制區(qū)域?定血,參數(shù)值代表如果圖片太小的話?就直接拉伸
????????mBitmapShader?=?new?BitmapShader(mBitmap,?Shader.TileMode.CLAMP,?Shader.TileMode.CLAMP);
????????//?設(shè)置圖片畫筆反鋸齒
????????mBitmapPaint.setAntiAlias(true);
????????//?設(shè)置圖片畫筆渲染器
????????mBitmapPaint.setShader(mBitmapShader);
????????//?設(shè)置邊界畫筆樣式
????????mBorderPaint.setStyle(Paint.Style.STROKE);//設(shè)畫筆為空心
????????mBorderPaint.setAntiAlias(true);
????????mBorderPaint.setColor(mBorderColor);????//畫筆顏色
????????mBorderPaint.setStrokeWidth(mBorderWidth);//畫筆邊界寬度
????????//這個地方是取的原圖片的寬高
????????mBitmapHeight?=?mBitmap.getHeight();
????????mBitmapWidth?=?mBitmap.getWidth();
????????//?設(shè)置含邊界顯示區(qū)域,取的是CircleImageView的布局實際大小诞外,為方形澜沟,查看xml也就是160dp(240px)??getWidth得到是某個view的實際尺寸
????????mBorderRect.set(0,?0,?getWidth(),?getHeight());
????????//計算?圓形帶邊界部分(外圓)的最小半徑,取mBorderRect的寬高減去一個邊緣大小的一半的較小值(這個地方我比較納悶為什么求外圓半徑需要先減去一個邊緣大邢恳辍)
????????mBorderRadius?=?Math.min((mBorderRect.height()?-?mBorderWidth)?/?2,?(mBorderRect.width()?-?mBorderWidth)?/?2);
????????//?初始圖片顯示區(qū)域為mBorderRect(CircleImageView的布局實際大忻K洹)
????????mDrawableRect.set(mBorderRect);
????????if?(!mBorderOverlay)?{
????????????//demo里始終執(zhí)行
????????????//通過inset方法??使得圖片顯示的區(qū)域從mBorderRect大小上下左右內(nèi)移邊界的寬度形成區(qū)域,查看xml邊界寬度為2dp(3px),所以方形邊長為就是160-4=156dp(234px)
????????????mDrawableRect.inset(mBorderWidth,?mBorderWidth);
????????}
????????//這里計算的是內(nèi)圓的最小半徑既们,也即去除邊界寬度的半徑
????????mDrawableRadius?=?Math.min(mDrawableRect.height()?/?2,?mDrawableRect.width()?/?2);
????????//設(shè)置渲染器的變換矩陣也即是mBitmap用何種縮放形式填充
????????updateShaderMatrix();
????????//手動觸發(fā)ondraw()函數(shù)?完成最終的繪制
????????invalidate();
????}
????/**
????*?這個函數(shù)為設(shè)置BitmapShader的Matrix參數(shù)濒析,設(shè)置最小縮放比例,平移參數(shù)啥纸。
????*?作用:保證圖片損失度最小和始終繪制圖片正中央的那部分
????*/
????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)?{
????????????//y軸縮放?x軸平移?使得圖片的y軸方向的邊的尺寸縮放到圖片顯示區(qū)域(mDrawableRect)一樣)
????????????scale?=?mDrawableRect.height()?/?(float)?mBitmapHeight;
????????????dx?=?(mDrawableRect.width()?-?mBitmapWidth?*?scale)?*?0.5f;
????????}?else?{
????????????//x軸縮放?y軸平移?使得圖片的x軸方向的邊的尺寸縮放到圖片顯示區(qū)域(mDrawableRect)一樣)
????????????scale?=?mDrawableRect.width()?/?(float)?mBitmapWidth;
????????????dy?=?(mDrawableRect.height()?-?mBitmapHeight?*?scale)?*?0.5f;
????????}
????????//?shaeder的變換矩陣号杏,我們這里主要用于放大或者縮小。
????????mShaderMatrix.setScale(scale,?scale);
????????//?平移
????????mShaderMatrix.postTranslate((int)?(dx?+?0.5f)?+?mDrawableRect.left,?(int)?(dy?+?0.5f)?+?mDrawableRect.top);
????????//?設(shè)置變換矩陣
????????mBitmapShader.setLocalMatrix(mShaderMatrix);
????}
}
Android自定義View總結(jié)
步驟
1. 自定義View的屬性
2. 繼承View斯棒,重寫構(gòu)造函數(shù)盾致,獲取我們自定義的屬性值
3. 重寫onMesure()方法
4. 重寫onDraw()方法