這幾天看了項(xiàng)目框架里面的圓形頭像梧田,發(fā)現(xiàn)其實(shí)這個(gè)東西并不是很難的東西萌业,學(xué)會(huì)了原理,無(wú)論圓形頭像乡范,五角星頭像都可以實(shí)現(xiàn)配名。
目前我上傳的Demo里用了兩種實(shí)現(xiàn)方式,那么我們分別來(lái)講講這兩種實(shí)現(xiàn)方式:
BitmapShader
Shader其實(shí)是遮罩的意思晋辆,能幫助我們?cè)诒韺訉?duì)圖像進(jìn)行簡(jiǎn)單處理渠脉,而無(wú)需那些深層的opengl,關(guān)于Shader瓶佳,如需深入了解請(qǐng)參考sunqunsunqun的CSDN博客
基礎(chǔ)知識(shí)準(zhǔn)備Shader的實(shí)現(xiàn):
- BitmapShader 圖片填充某一區(qū)域(三種模式芋膘,拉伸,重疊,鏡像)为朋,下面詳講
- ComposeShader 這個(gè)是用來(lái)混合其他Shader的
- LinearGradient 可實(shí)現(xiàn)某區(qū)域的線性漸變效果
- RadialShaderGradient 某一區(qū)域?qū)崿F(xiàn)環(huán)形漸變
- SweepGradient 是指在某一中心以x軸正方向逆時(shí)針旋轉(zhuǎn)一周而形成的掃描效果的渲染形式(不深入研究)
接下來(lái)詳細(xì)講講BitmapShader:
BitmapShader(Bitmap bitmap, Shader.TileMode tileX, Shader.TileMode tileY)
這是BitmapShader的構(gòu)造函數(shù)臂拓,bitmap參數(shù)就是我們要處理的圖片,TileMode有三個(gè)
- CLAMP 拉伸
- REPEAT 重復(fù)
- MIRROR 鏡像重復(fù)
一般情況下如果繪制區(qū)域大于我們的bitmap時(shí)這些參數(shù)會(huì)起效习寸,反之則無(wú)效果
CLAMP參數(shù)起效的情況下胶惰,是按照邊緣像素進(jìn)行拉伸的,這一點(diǎn)如果使用過(guò).9圖的應(yīng)該比較清楚
所以我們實(shí)現(xiàn)圓形頭像的關(guān)鍵就是對(duì)源圖的合理縮放與拉伸霞溪,因?yàn)閐rawXXX已經(jīng)決定了圖形的形狀
BitmapShader實(shí)現(xiàn)圓形圖像的過(guò)程
首先為了簡(jiǎn)便孵滞,我們繼承ImageView,省略一些測(cè)量函數(shù)的重寫威鹿,其次因?yàn)閳A形就會(huì)帶有半徑的參數(shù)剃斧,圓角就會(huì)帶角的半徑參數(shù),因此我們要自定義兩個(gè)屬性忽你,一個(gè)是圖形類型幼东,這里只實(shí)現(xiàn)圓形與圓角矩形
<attr name="cornerRadius" format="dimension"/><attr name="type">
<enum name="circle" value="0"/>
<enum name="round" value="1"/></attr>
<declare-styleable name="RoundImageView">
<attr name="cornerRadius"/>r
<attr name="type"/></declare-styleable>
接下來(lái)在構(gòu)造函數(shù)內(nèi)獲得這兩個(gè)屬性的值
private void init(Context context, AttributeSet attrs) {
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.RoundImageView);
type = ta.getInt(R.styleable.RoundImageView_type, TYPE_CIRCLE);
roundRadius = ta.getDimensionPixelSize(R.styleable.RoundImageView_cornerRadius, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10, getResources().getDisplayMetrics())); ta.recycle();
}
這個(gè)不多說(shuō)了,自定義View內(nèi)常用方法
然后科雳,如果是圓的話根蟹,我們就要截取當(dāng)前繪制區(qū)域,既然是截取所以只能去寬度糟秘,高度中小的作為直徑
if (type == TYPE_CIRCLE) {
viewWidth = Math.min(getMeasuredWidth(), getMeasuredHeight());
circleRadius = viewWidth / 2;
setMeasuredDimension(viewWidth, viewWidth);}
接下來(lái)就是重點(diǎn)的shader處理了
private void setUpShader() {
Drawable drawable = getDrawable();
if (drawable == null)
return;
Bitmap bitmap = drawable2Bitmap(drawable);
mBitmapShader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
float scale = 1.0f; //bitmap:canvas ratio
Log.i("------", "bitmap original width:" + bitmap.getWidth() + "height" + bitmap.getHeight());
if (type == TYPE_CIRCLE) {
int minWidth = Math.min(bitmap.getWidth(), bitmap.getHeight());
scale = viewWidth * 1.0f / minWidth;
} else {
scale = Math.max(getWidth() * 1.0f / bitmap.getWidth(), getHeight() * 1.0f / bitmap.getHeight());
}
mMatrix.setScale(scale, scale); //顯示圖片中心
if (bitmap.getWidth() * 1.0 / bitmap.getHeight() < 1) {
mMatrix.postTranslate(0, -getHeight() / 2);
} else {
mMatrix.postTranslate(-getWidth() / 2, 0); }
mBitmapShader.setLocalMatrix(mMatrix);
Log.i("------", "bitmap new width:" + bitmap.getWidth() + "height" + bitmap.getHeight());
mPaint.setShader(mBitmapShader);}
此處的關(guān)鍵就在于獲取拉伸縮放比简逮,為了顯示完全肯定要取消的比例,最后可能由于圖片的比例與繪圖區(qū)域不一致尿赚,導(dǎo)致我的圖片主要內(nèi)容不再正中散庶,所以默認(rèn)將圖片移到中間
最后,就是畫出形狀
if (type == TYPE_CIRCLE) {
canvas.drawCircle(circleRadius, circleRadius, circleRadius, mPaint);
} else {
canvas.drawRoundRect(roundRec, roundRadius, roundRadius, mPaint);
}
關(guān)于Xfermode實(shí)現(xiàn)圓形凌净,圓角
原理其實(shí)差不多悲龟,Xfermode解決了怎樣將兩張圖片畫到同一個(gè)區(qū)域的問(wèn)題.其實(shí)上一個(gè)方法已經(jīng)很好的解決了問(wèn)題,那么為什么還要講這種呢冰寻,因?yàn)閄fermode是個(gè)很強(qiáng)大的東西须教,可以實(shí)現(xiàn)很多效果。如果單獨(dú)使用xfermode我們就要對(duì)圖片源進(jìn)行縮放拉伸斩芭,普通的方法就是我生成一張新的圖片轻腺,這樣其實(shí)是比較費(fèi)內(nèi)存的,這邊就詳細(xì)講一下Xfermode
黃色為下層划乖,藍(lán)色為上層圖片
那么我們的過(guò)程就簡(jiǎn)單了贬养,為了顯示下層我們首先將源圖繪制上去,當(dāng)然是經(jīng)過(guò)拉伸縮放的迁筛,然后再把形狀貼上去煤蚌,取到重疊的部分耕挨,顯示下層圖片也就是DstIn模式
@Overrideprotected void onDraw(Canvas canvas) {
Drawable drawable = getDrawable();
if (drawable == null)
return;
circleRadius = Math.min(getWidth() / 2, getHeight() / 2);
float scale=1.0f;
int actWidth=drawable.getIntrinsicWidth();
int actHeight=drawable.getIntrinsicHeight();
Bitmap bmp=Bitmap.createBitmap(getWidth(),getHeight(), Bitmap.Config.ARGB_8888);
Canvas temp=new Canvas(bmp);
scale= Math.min(actWidth/getWidth(),actHeight/getHeight());
drawable.setBounds(0,0, (int) (getHeight()*scale), (int) (getWidth()*scale));
drawable.draw(temp);
Bitmap maskBmp = getMaskBitMap();
mPaint.reset();
mPaint.setFilterBitmap(false);
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
temp.drawBitmap(maskBmp, 0, 0, mPaint);
mPaint.setXfermode(null);
canvas.drawBitmap(bmp,0,0,mPaint);}
Bitmap getMaskBitMap() {
Bitmap result = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(result);
Paint paint = new Paint();
paint.setAntiAlias(true);
canvas.drawCircle(circleRadius, circleRadius, circleRadius, paint);
return result;}
環(huán)形最近也上傳了,結(jié)合了SweepGradient尉桩,如果單是圖形變換的話筒占,推薦使用BitmapShader就夠了,Xfermode的話用于實(shí)現(xiàn)混合的蜘犁,其他代碼就不貼了翰苫,送上我的github實(shí)現(xiàn)最后總結(jié),Matrix是個(gè)好東西啊这橙,實(shí)現(xiàn)3D圖形的時(shí)候也要用奏窑,可惜大學(xué)矩陣學(xué)的差,只知道怎么用屈扎,不太懂原理啊埃唯,最后感謝csdn的Hongyang大神,文章參考Android Xfermode 實(shí)戰(zhàn) 實(shí)現(xiàn)圓形、圓角圖片