原图
效果
一、clipPath自定义圆形头像
在res/values下新建attrs.xml文件
<declare-styleable name="CustomAvatarView">
<attr name="stroke_color" format="color|reference"/>
<attr name="stroke_width" format="dimension"/>
<attr name="src" format="reference"/>
</declare-styleable>
布局文件引用
<com.huangmiao.kotlintest.view.CustomAvatarView
android:id="@+id/customAvatarView"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:stroke_color = "@color/colorSilver"
app:stroke_width="5dp"
app:src="@drawable/test"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
自定义View
圆形头像的实现并不复制,只需将画布剪裁成圆形,核心代码是canvas.clipPath(path),根据圆形路径剪裁画布
这里做了几个优化,比如图片过大,会将图片剪裁至控件最短边距大小
限制控件大小,比如控件大小不能超过屏幕宽高,防止超出绘制区域
public class CustomAvatarView extends View {
public CustomAvatarView(Context context) {
this(context,null);
}
public CustomAvatarView(Context context, AttributeSet attrs) {
this(context, attrs,0);
}
public CustomAvatarView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CustomAvatarView);
init(typedArray);
}
//画笔
private Paint strokePaint;
public Paint getStrokePaint(){
return strokePaint;
}
//画笔宽度
private float strokeWidth = 5;
public void setStrokeWidth(float strokeWidth){
this.strokeWidth = strokeWidth;
strokePaint.setStrokeWidth(strokeWidth);
invalidate();
}
//画笔颜色
private int strokeColor = ContextCompat.getColor(getContext().getApplicationContext(), R.color.colorSilver);
public void setStrokeColor(@ColorRes int color){
this.strokeColor = color;
strokePaint.setColor(ContextCompat.getColor(getContext().getApplicationContext(), color));
invalidate();
}
//位图
private Bitmap bitmap;
public void setBitmap(Bitmap bitmap){
this.bitmap = bitmap;
invalidate();
}
public void setBitmap(int bitmapID){
bitmap = getBitmapResources(getResources(),bitmapID,false);
invalidate();
}
private Rect src;
private Rect dst;
//屏幕宽高
private int width;
private int height;
//剪切路径
private Path path;
private void init(TypedArray typedArray){
setLayerType(LAYER_TYPE_SOFTWARE,null);
strokeColor = typedArray.getColor(R.styleable.CustomAvatarView_stroke_color,strokeColor);
strokeWidth = typedArray.getDimension(R.styleable.CustomAvatarView_stroke_width,strokeWidth);
int bitmapID = typedArray.getResourceId(R.styleable.CustomAvatarView_src,0);
bitmap = getBitmapResources(getResources(),bitmapID,false);
strokePaint = new Paint();
strokePaint.setStrokeWidth(strokeWidth);
strokePaint.setColor(strokeColor);
strokePaint.setStyle(Paint.Style.STROKE);
strokePaint.setAntiAlias(true);
path = new Path();
src = new Rect();
dst = new Rect();
//获取屏幕宽高
screenWidth();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//控件最短边
int length = Math.min(getWidth(),getHeight());
//图片最短边
int bitmapLength = Math.min(bitmap.getWidth(),bitmap.getHeight());
//清空路径
path.reset();
//剪切圆形画布路径
path.addCircle(getWidth()/2f,getHeight()/2f,length/2f , Path.Direction.CW);
canvas.clipPath(path);
//如果图片大于控件大小则剪裁
src.set((bitmap.getWidth()-bitmapLength)/2,(bitmap.getHeight()-bitmapLength)/2,(bitmap.getWidth()+bitmapLength)/2,(bitmap.getHeight()+bitmapLength)/2);
//图片充满整个控件
dst.set((getWidth()-length)/2,(getHeight() -length)/2, (getWidth()+length)/2,(getHeight() +length)/2);
canvas.drawBitmap(bitmap,src,dst,strokePaint);
//绘制外圈圆
canvas.drawCircle(getWidth()/2f,getHeight()/2f,(length- strokeWidth)/2f ,strokePaint);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int measureWidth = MeasureSpec.getSize(widthMeasureSpec);
int measureHeight = MeasureSpec.getSize(heightMeasureSpec);
int measureWidthMode = MeasureSpec.getMode(widthMeasureSpec);
int measureHeightMode = MeasureSpec.getMode(heightMeasureSpec);
//限制控件大小不能大于屏幕宽高
setMeasuredDimension((measureWidthMode == MeasureSpec.EXACTLY) ? Math.min(measureWidth,width): Math.min(bitmap.getWidth(),width)
, (measureHeightMode == MeasureSpec.EXACTLY) ? Math.min(measureHeight,height): Math.min(bitmap.getHeight(),height));
}
/**
* 获取屏幕宽高
*/
private void screenWidth(){
WindowManager wm = (WindowManager) getContext().getApplicationContext().getSystemService(Context.WINDOW_SERVICE);
if (wm != null){
Display display = wm.getDefaultDisplay();
Point size = new Point();
display.getSize(size);
width = size.x;
height = size.y;
}
}
/**
* 读取资源文件夹下图片
*
* @param res getResources
* @param id 文件id
* @param isRGB 是否使用RGB_565压缩图片
* @return bitmap
*/
public Bitmap getBitmapResources(Resources res, int id,boolean isRGB) {
TypedValue value = new TypedValue();
InputStream is = res.openRawResource(id, value);
if (isRGB){
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.RGB_565;
return BitmapFactory.decodeStream(new BufferedInputStream(is),null,options);
}
return BitmapFactory.decodeStream(new BufferedInputStream(is));
}
}
使用
//设置外圈宽度
customAvatarView.setStrokeWidth(50f)
//设置外圈颜色
customAvatarView.setStrokeColor(R.color.colorBlue)
//替换图片
//方式一:
customAvatarView.setBitmap(R.drawable.wx,false)
//方式二:
customAvatarView.setBitmap(bitmap)
缺点
这样绘制的圆形头像会有锯齿,自定义头像最常用的是BitmapShader方法制作圆形头像
二、BitmapShader自定义圆形头像
public class CustomAvatarView extends View {
public CustomAvatarView(Context context) {
this(context, null);
}
public CustomAvatarView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CustomAvatarView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CustomAvatarView);
init(typedArray);
}
//位图画笔
private Paint bitmapPaint;
private BitmapShader bitmapShader;
//边框画笔
private Paint strokePaint;
public Paint getStrokePaint() {
return strokePaint;
}
//画笔宽度
private float strokeWidth = 5;
public void setStrokeWidth(float strokeWidth) {
this.strokeWidth = strokeWidth;
strokePaint.setStrokeWidth(strokeWidth);
invalidate();
}
//画笔颜色
private int strokeColor = ContextCompat.getColor(getContext().getApplicationContext(), R.color.colorSilver);
public void setStrokeColor(@ColorRes int color) {
this.strokeColor = color;
strokePaint.setColor(ContextCompat.getColor(getContext().getApplicationContext(), color));
invalidate();
}
//新建的空白位图
private Bitmap blankBitmap;
//新建的空白画布
private Canvas bitmapCanvas;
//位图
private Bitmap bitmap;
public void setBitmap(Bitmap bitmap) {
this.bitmap = bitmap;
invalidate();
}
public void setBitmap(int bitmapID, boolean isRGB) {
bitmap = getBitmapResources(getResources(), bitmapID, isRGB);
invalidate();
}
//截取位图矩形
private Rect src;
//显示坐标矩形
private Rect dst;
//屏幕宽高
private int width;
private int height;
private void init(TypedArray typedArray) {
setLayerType(LAYER_TYPE_SOFTWARE, null);
strokeColor = typedArray.getColor(R.styleable.CustomAvatarView_stroke_color, strokeColor);
strokeWidth = typedArray.getDimension(R.styleable.CustomAvatarView_stroke_width, strokeWidth);
int bitmapID = typedArray.getResourceId(R.styleable.CustomAvatarView_src, 0);
bitmap = getBitmapResources(getResources(), bitmapID, false);
typedArray.recycle();
//边框画笔
strokePaint = new Paint();
strokePaint.setStrokeWidth(strokeWidth);
strokePaint.setColor(strokeColor);
strokePaint.setStyle(Paint.Style.STROKE);
strokePaint.setAntiAlias(true);
//位图画笔
bitmapPaint = new Paint();
bitmapPaint.setAntiAlias(true);
src = new Rect();
dst = new Rect();
//获取屏幕宽高
screenWidth();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//控件最短边
int length = Math.min(getWidth(), getHeight());
//图片最短边
int bitmapLength = Math.min(bitmap.getWidth(), bitmap.getHeight());
if (blankBitmap == null) {
//创建一个充满控件的空白位图
blankBitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
//创建一个充满控件的空白画布
bitmapCanvas = new Canvas(blankBitmap);
bitmapShader = new BitmapShader(blankBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
}
//如果图片大于控件大小则剪裁
src.set((bitmap.getWidth() - bitmapLength) / 2, (bitmap.getHeight() - bitmapLength) / 2, (bitmap.getWidth() + bitmapLength) / 2, (bitmap.getHeight() + bitmapLength) / 2);
//图片充满整个控件
dst.set((getWidth() - length) / 2, (getHeight() - length) / 2, (getWidth() + length) / 2, (getHeight() + length) / 2);
//将图片绘制到画布上
bitmapCanvas.drawBitmap(bitmap, src, dst, bitmapPaint);
//将图形填充到画笔
bitmapPaint.setShader(bitmapShader);
//绘制圆显示填充好的图片
canvas.drawCircle(getWidth() / 2f, getHeight() / 2f, (length - 2 * strokeWidth) / 2f, bitmapPaint);
//绘制外层边框
canvas.drawCircle(getWidth() / 2f, getHeight() / 2f, (length - strokeWidth) / 2f, strokePaint);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int measureWidth = MeasureSpec.getSize(widthMeasureSpec);
int measureHeight = MeasureSpec.getSize(heightMeasureSpec);
int measureWidthMode = MeasureSpec.getMode(widthMeasureSpec);
int measureHeightMode = MeasureSpec.getMode(heightMeasureSpec);
//限制控件大小不能大于屏幕宽高
setMeasuredDimension((measureWidthMode == MeasureSpec.EXACTLY) ? Math.min(measureWidth, width) : Math.min(bitmap.getWidth(), width)
, (measureHeightMode == MeasureSpec.EXACTLY) ? Math.min(measureHeight, height) : Math.min(bitmap.getHeight(), height));
}
/**
* 获取屏幕宽高
*/
private void screenWidth() {
WindowManager wm = (WindowManager) getContext().getApplicationContext().getSystemService(Context.WINDOW_SERVICE);
if (wm != null) {
Display display = wm.getDefaultDisplay();
Point size = new Point();
display.getSize(size);
width = size.x;
height = size.y;
}
}
/**
* 读取资源文件夹下图片
*
* @param res getResources
* @param id 文件id
* @param isRGB 是否使用RGB_565压缩图片
* @return bitmap
*/
public Bitmap getBitmapResources(Resources res, int id, boolean isRGB) {
TypedValue value = new TypedValue();
InputStream is = res.openRawResource(id, value);
if (isRGB) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.RGB_565;
return BitmapFactory.decodeStream(new BufferedInputStream(is), null, options);
}
return BitmapFactory.decodeStream(new BufferedInputStream(is));
}
}