自定義圓形頭像CircleImageView的使用和源碼分析

我們使用開源控件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ù)氛赐,很簡單。

大功告成先舷,是不是覺得思路比較簡單艰管,精致干練。

我總結(jié)下源碼的精致之處:

流程控制的比較嚴(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()方法

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市荣暮,隨后出現(xiàn)的幾起案子庭惜,更是在濱河造成了極大的恐慌,老刑警劉巖穗酥,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件护赊,死亡現(xiàn)場離奇詭異惠遏,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)骏啰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進(jìn)店門爽哎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人器一,你說我怎么就攤上這事〕冢” “怎么了祈秕?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長雏胃。 經(jīng)常有香客問我请毛,道長,這世上最難降的妖魔是什么瞭亮? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任方仿,我火速辦了婚禮,結(jié)果婚禮上统翩,老公的妹妹穿的比我還像新娘仙蚜。我一直安慰自己,他們只是感情好厂汗,可當(dāng)我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布委粉。 她就那樣靜靜地躺著,像睡著了一般娶桦。 火紅的嫁衣襯著肌膚如雪贾节。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天衷畦,我揣著相機(jī)與錄音栗涂,去河邊找鬼。 笑死祈争,一個胖子當(dāng)著我的面吹牛斤程,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播铛嘱,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼暖释,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了墨吓?” 一聲冷哼從身側(cè)響起球匕,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎帖烘,沒想到半個月后亮曹,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年照卦,在試婚紗的時候發(fā)現(xiàn)自己被綠了式矫。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡役耕,死狀恐怖采转,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情瞬痘,我是刑警寧澤故慈,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站框全,受9級特大地震影響察绷,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜津辩,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一拆撼、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧喘沿,春花似錦闸度、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至晒哄,卻和暖如春睁宰,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背寝凌。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工柒傻, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人较木。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓红符,卻偏偏與公主長得像,于是被迫代替她去往敵國和親伐债。 傳聞我的和親對象是個殘疾皇子预侯,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,786評論 2 345

推薦閱讀更多精彩內(nèi)容