一般在项目中,我们用到的圆形头像都是采用自定义视图的方式,这种圆形头像一般分为普通的圆形头像、带边框的圆形图像、随机背景头像,在上一篇Android圆形图像的绘制(一)中,提到了圆形头像绘制的基本方法,这遍文章主要是用到上篇文章中SRC_IN这个方法,下面看一下效果图。
首先,创建一个类CircleImageView,让它继承ImageView,代码如下:
public class CircleImageView extends ImageView {
public CircleImageView(Context context) {
this(context, null);
}
public CircleImageView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CircleImageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
}
}
在创建自定义视图时,一般都会在res/values/attrs.xml创建declare-styleable,如果values下面没有attrs.xml这个文件,可以自己新建,将新建的declare-styleable命名为CircleImageView。为了得到上图中的效果,需要知道边缘宽度、边缘颜色、随机头像的背景色、随机背景上展示的文字,以及文字的大小,代码如下:
此时,需要在CircleImageView的构造函数里,获取attrs.xml名为CircleImageView的属性,在获取属性的时候,需要定位文本字体大小,和颜色的默认值,边缘宽度默认值为0,也就是没有边缘。代码如下:
public CircleImageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
//#5AC3B2
int defaultColor = getResources().getColor(R.color.colorGreen);
//14sp
int defaultTextSize = getResources().getDimensionPixelSize(R.dimen.dimen_default_text_size);
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CircleImageView, defStyleAttr, 0);
mStrokeWidth = typedArray.getDimensionPixelSize(R.styleable.CircleImageView_stroke_width, 0);
mStrokeColor = typedArray.getColor(R.styleable.CircleImageView_stroke_color, defaultColor);
mTextSize = typedArray.getDimensionPixelSize(R.styleable.CircleImageView_text_size, defaultTextSize);
mBackground = typedArray.getColor(R.styleable.CircleImageView_random_backgroud, defaultColor);
mText = typedArray.getString(R.styleable.CircleImageView_text);
//一定要记得回收
typedArray.recycle();
}
定义一个mBitmap的全局变量,并重写setImageResource、setImageDrawable、setImageBitmap这三个方法,获取位图信息,并进行重新的绘制,代码如下:
/**
* 绘制图片的位图
*/
private Bitmap mBitmap;
@Override
public void setImageResource(int resId) {
super.setImageResource(resId);
mBitmap = getBitmapFromDrawable(getDrawable());
invalidate();
}
@Override
public void setImageDrawable(Drawable drawable) {
super.setImageDrawable(drawable);
mBitmap = getBitmapFromDrawable(drawable);
invalidate();
}
@Override
public void setImageBitmap(Bitmap bm) {
super.setImageBitmap(bm);
mBitmap = bm;
invalidate();
}
/**
* 获取bitmap
*
* @param drawable
* @return
*/
private Bitmap getBitmapFromDrawable(Drawable drawable) {
if (drawable == null) {
return null;
}
if (drawable instanceof BitmapDrawable) {
return ((BitmapDrawable) drawable).getBitmap();
}
try {
Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
return bitmap;
} catch (OutOfMemoryError e) {
return null;
}
}
运行代码,效果图如下:
运行出来,什么效果都没有,是因为没有实现onDraw这个方法,在实现onDraw之前,需要重写onSizeChanged方法,获取到当前view的长宽,代码如下:
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mWidth = w;
mHeight = h;
}
下面看下onDraw的实现,代码如下:
@Override
protected void onDraw(Canvas canvas) {
//一定要注释掉,否则圆形图像没法生效
// super.onDraw(canvas);
if (mWidth > 0 && mHeight > 0) {
Bitmap bitmap = createCircleBitmapForSRC_IN(canvas);
if (bitmap != null) {
canvas.drawBitmap(bitmap, 0, 0, new Paint());
}
}
}
/**
* 创建圆形图像
* @param targetCanvs
* @return
*/
private Bitmap createCircleBitmapForSRC_IN(Canvas targetCanvs) {
//创建一个和图片大小差不多的正方形矩阵
int size = Math.min(mWidth, mHeight);
Bitmap newBitmap = null;
if (mBitmap != null) {
int width = mBitmap.getWidth();
int height = mBitmap.getHeight();
// 对bitmap进行缩放,缩放到指定view的大小
Matrix matrix = new Matrix();
matrix.postScale((float) mWidth / width, (float) mHeight / height);
newBitmap = Bitmap.createBitmap(mBitmap, 0, 0, width,
height, matrix, true);
} else {
newBitmap = createRandomMaskBitmap(size);
}
if (newBitmap == null) {
return null;
}
int center = size / 2;
Paint paint = new Paint();
boolean isDrawBorder = drawCircleBorder(targetCanvs, center, paint);
Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
if (isDrawBorder) {
paint.setColor(Color.WHITE);
paint.setStyle(Paint.Style.FILL);
canvas.scale(DEFAULT_SCALE, DEFAULT_SCALE, center, center);
}
//在矩阵中心画圆,与矩阵的四边相切
canvas.drawCircle(center, center, center, paint);
//设置Xfermode为SRC_IN
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
//绘制图片
canvas.drawBitmap(newBitmap, 0, 0, paint);
return bitmap;
}
/**
* 创建随机背景
* @param size
* @return
*/
private Bitmap createRandomMaskBitmap(int size) {
Bitmap output = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
final Paint paint = new Paint();
paint.setAntiAlias(true);// 抗锯齿
paint.setFilterBitmap(true);
paint.setColor(mBackground);
int center = size / 2;//获取画布的中心位置
//创建canvas对象,绘制随机背景
Canvas canvas = new Canvas(output);
canvas.drawCircle(center, center, center, paint);
//绘制随机背景上的文字
setText(canvas, size, paint);
return output;
}
/**
* 绘制文本
* @param canvas
* @param size
* @param paint
*/
private void setText(Canvas canvas, int size, Paint paint) {
Rect targetRect = new Rect(0, 0, size, size);
//设置绘制文本字体的颜色
paint.setColor(Color.WHITE);
//设置绘制文本的大小
paint.setTextSize(mTextSize);
//获取文本展示的居中位置
Paint.FontMetricsInt fontMetrics = paint.getFontMetricsInt();
int baseline = (targetRect.bottom + targetRect.top - fontMetrics.bottom - fontMetrics.top) / 2;
paint.setTextAlign(Paint.Align.CENTER);
canvas.drawText(mText, targetRect.centerX(), baseline, paint);
}
/**
* 绘制边界圆
*
* @param canvas
* @param size
* @param paint
* @return
*/
private boolean drawCircleBorder(Canvas canvas, int size, Paint paint) {
if (mStrokeWidth > 0) {
paint.setAntiAlias(true);
paint.setColor(mStrokeColor);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(mStrokeWidth);
canvas.drawCircle(size, size, size - mStrokeWidth, paint);
return true;
}
return false;
}
Activity的布局文件如下:
在Activity里需要进行如下初始化:
MainActivity代码:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
CircleImageView circleImageView = (CircleImageView) findViewById(R.id.random_icon);
circleImageView.drawRandomBackground();
CircleImageView circleImageView1 = (CircleImageView) findViewById(R.id.random_icon1);
circleImageView1.drawRandomBackground();
}
CircleImageView里的drawRandomBackground方法的实现如下:
/**
* 绘制随机背景
*/
public void drawRandomBackground() {
invalidate();
}
到这里,运行代码就可以得到文章最开始的那张效果图了。
我们需要注意的问题如下:
1:onDraw方法里的super.onDraw(canvas)一定要注释掉,否则后面的绘制无法生效;
2:对位图进行绘制的时候,需要对位图进行缩放,大小跟view的大小一致;
3:在绘制边缘的时候,圆的半径要剪出边缘宽度的大小,否则边缘线会有展示不全的效果;
4:在获取文本居中的时,应该通过Paint.FontMetricsInt计算获取