這幾天看了項目框架里面的圓形頭像,發(fā)現(xiàn)其實這個東西并不是很難的東西,學會了原理,無論圓形頭像,五角星頭像都可以實現(xiàn)。
目前我上傳的Demo里用了兩種實現(xiàn)方式,那么我們分別來講講這兩種實現(xiàn)方式:
BitmapShader
Shader其實是遮罩的意思,能幫助我們在表層對圖像進行簡單處理,而無需那些深層的opengl,關于Shader,如需深入了解請參考sunqunsunqun的CSDN博客
基礎知識準備Shader的實現(xiàn):
- BitmapShader 圖片填充某一區(qū)域(三種模式,拉伸,重疊,鏡像),下面詳講
- ComposeShader 這個是用來混合其他Shader的
- LinearGradient 可實現(xiàn)某區(qū)域的線性漸變效果
- RadialShaderGradient 某一區(qū)域?qū)崿F(xiàn)環(huán)形漸變
- SweepGradient 是指在某一中心以x軸正方向逆時針旋轉(zhuǎn)一周而形成的掃描效果的渲染形式(不深入研究)
接下來詳細講講BitmapShader:
BitmapShader(Bitmap bitmap, Shader.TileMode tileX, Shader.TileMode tileY)
這是BitmapShader的構造函數(shù),bitmap參數(shù)就是我們要處理的圖片,TileMode有三個
- CLAMP 拉伸
- REPEAT 重復
- MIRROR 鏡像重復
一般情況下如果繪制區(qū)域大于我們的bitmap時這些參數(shù)會起效,反之則無效果
CLAMP參數(shù)起效的情況下,是按照邊緣像素進行拉伸的,這一點如果使用過.9圖的應該比較清楚
所以我們實現(xiàn)圓形頭像的關鍵就是對源圖的合理縮放與拉伸,因為drawXXX已經(jīng)決定了圖形的形狀
BitmapShader實現(xiàn)圓形圖像的過程
首先為了簡便,我們繼承ImageView,省略一些測量函數(shù)的重寫,其次因為圓形就會帶有半徑的參數(shù),圓角就會帶角的半徑參數(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>
接下來在構造函數(shù)內(nèi)獲得這兩個屬性的值
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();
}
這個不多說了,自定義View內(nèi)常用方法
然后,如果是圓的話,我們就要截取當前繪制區(qū)域,既然是截取所以只能去寬度,高度中小的作為直徑
if (type == TYPE_CIRCLE) {
viewWidth = Math.min(getMeasuredWidth(), getMeasuredHeight());
circleRadius = viewWidth / 2;
setMeasuredDimension(viewWidth, viewWidth);}
接下來就是重點的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);}
此處的關鍵就在于獲取拉伸縮放比,為了顯示完全肯定要取消的比例,最后可能由于圖片的比例與繪圖區(qū)域不一致,導致我的圖片主要內(nèi)容不再正中,所以默認將圖片移到中間
最后,就是畫出形狀
if (type == TYPE_CIRCLE) {
canvas.drawCircle(circleRadius, circleRadius, circleRadius, mPaint);
} else {
canvas.drawRoundRect(roundRec, roundRadius, roundRadius, mPaint);
}
關于Xfermode實現(xiàn)圓形,圓角
原理其實差不多,Xfermode解決了怎樣將兩張圖片畫到同一個區(qū)域的問題.其實上一個方法已經(jīng)很好的解決了問題,那么為什么還要講這種呢,因為Xfermode是個很強大的東西,可以實現(xiàn)很多效果。如果單獨使用xfermode我們就要對圖片源進行縮放拉伸,普通的方法就是我生成一張新的圖片,這樣其實是比較費內(nèi)存的,這邊就詳細講一下Xfermode
黃色為下層,藍色為上層圖片
那么我們的過程就簡單了,為了顯示下層我們首先將源圖繪制上去,當然是經(jīng)過拉伸縮放的,然后再把形狀貼上去,取到重疊的部分,顯示下層圖片也就是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)形最近也上傳了,結合了SweepGradient,如果單是圖形變換的話,推薦使用BitmapShader就夠了,Xfermode的話用于實現(xiàn)混合的,其他代碼就不貼了,送上我的github實現(xiàn)最后總結,Matrix是個好東西啊,實現(xiàn)3D圖形的時候也要用,可惜大學矩陣學的差,只知道怎么用,不太懂原理啊,最后感謝csdn的Hongyang大神,文章參考Android Xfermode 實戰(zhàn) 實現(xiàn)圓形、圓角圖片