实现方法如下:
通过新建涂层,先将外部的边框(半径为图片一半减去描边的宽度)画出来,参考 PorterDuff.Mode.DST_OUT 模式
然后将新建的图层和 自身图片合并之后,再通过 外层实现 mCircleShape
将多余的边框裁减掉,参考 PorterDuff.Mode.DST_IN 模式
自定义属性参考如下:
自定义属性 | 默认值 | 描述 |
---|---|---|
shapeMode | circle | 边框形状 |
roundRadius | 0 | 描边弧度 |
strokeWidth | 0 | 描边宽度 |
strokeColor | 0x000000 | 描边颜色 |
最后附上整体代码:
/**
*
* PackageName: com.example.kotlin.utils
* Description: 带边框的 imageView
* Created by : Liu
* date: 2017/12/27
*
*/
public class StrokeImageView extends AppCompatImageView {
public static final int SHAPE_MODE_ROUND_RECT = 1;
public static final int SHAPE_MODE_CIRCLE = 0;
private int mShapeMode = 0; // 描边的形状
private float mRadius = 0;
private int mStrokeColor = 0x000000;
private float mStrokeWidth = 0;
private boolean mShapeChanged;
private Shape mMainShape;// 内部的圆形
private Shape mCircleShape;// 描边的圆形
private Paint mMainPaint, mStrokePaint;
private Bitmap mStrokeBitmap;
public StrokeImageView(Context context) {
this(context, null);
}
public StrokeImageView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public StrokeImageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(attrs);
}
private void init(AttributeSet attrs) {
setLayerType(LAYER_TYPE_HARDWARE, null);// 硬件加速建议关闭
if (attrs != null) {
TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.StrokeImageView);
mShapeMode = a.getInt(R.styleable.StrokeImageView_shapeMode, 0);
mRadius = a.getDimension(R.styleable.StrokeImageView_roundRadius, 0);
mStrokeWidth = a.getDimension(R.styleable.StrokeImageView_strokeWidth, 0);
mStrokeColor = a.getColor(R.styleable.StrokeImageView_strokeColor, mStrokeColor);
a.recycle();
}
mMainPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mMainPaint.setFilterBitmap(true);
mMainPaint.setColor(Color.GREEN);
mMainPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));// PorterDuff.Mode.DST_IN 显示相交的 DST部分,将多余的边框裁剪掉
mStrokePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mStrokePaint.setFilterBitmap(true);
mStrokePaint.setColor(Color.RED);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
if (changed || mShapeChanged) {
mShapeChanged = false;
int width = getMeasuredWidth();
int height = getMeasuredHeight();
switch (mShapeMode) {
case SHAPE_MODE_ROUND_RECT:
break;
case SHAPE_MODE_CIRCLE:
int min = Math.min(width, height);
mRadius = (float) min / 2;
break;
}
if (mMainShape == null || mRadius != 0) {
float[] radius = new float[8];
Arrays.fill(radius, mRadius);
mMainShape = new RoundRectShape(radius, null, null);
mCircleShape = new RoundRectShape(radius, null, null);
}
mMainShape.resize(width, height);
mCircleShape.resize(width - mStrokeWidth * 2, height - mStrokeWidth * 2);
createStrokeBitmap();
}
}
@SuppressLint("DrawAllocation")
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mStrokeWidth > 0 && mCircleShape != null && mStrokeBitmap != null) {
int i = canvas.saveLayer(0, 0, getMeasuredWidth(), getMeasuredHeight(), null, Canvas.ALL_SAVE_FLAG);
mStrokePaint.setXfermode(null);
canvas.drawBitmap(mStrokeBitmap, 0, 0, mStrokePaint);
canvas.translate(mStrokeWidth, mStrokeWidth);
mStrokePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));// 以显示的中心圆向外画出要描边的矩形边框
mCircleShape.draw(canvas, mStrokePaint);
canvas.restoreToCount(i);
}
switch (mShapeMode) {
case SHAPE_MODE_ROUND_RECT:
case SHAPE_MODE_CIRCLE:
if (mMainShape != null) {
mMainShape.draw(canvas, mMainPaint);
}
break;
}
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
if (mStrokeBitmap == null) createStrokeBitmap();
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
releaseStrokeBitmap();
}
private Bitmap createStrokeBitmap() {
if (mStrokeWidth <= 0) return null;
int w = getMeasuredWidth();
int h = getMeasuredHeight();
if (w == 0 || h == 0) return null;
releaseStrokeBitmap();
mStrokeBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(mStrokeBitmap);
Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);
p.setColor(mStrokeColor);
c.drawRect(new RectF(0, 0, w, h), p);
return mStrokeBitmap;
}
private void releaseStrokeBitmap() {
if (mStrokeBitmap != null) {
mStrokeBitmap.recycle();
mStrokeBitmap = null;
}
}
public void setStroke(int strokeColor, float strokeWidth) {
if (mStrokeWidth <= 0) return;
if (mStrokeWidth != strokeWidth) {
mStrokeWidth = strokeWidth;
int width = getMeasuredWidth();
int height = getMeasuredHeight();
if(mCircleShape != null) {
mCircleShape.resize(width - mStrokeWidth * 2, height - mStrokeWidth * 2);
}
postInvalidate();
}
if (mStrokeColor != strokeColor) {
mStrokeColor = strokeColor;
createStrokeBitmap();
postInvalidate();
}
}
public void setStrokeColor(int strokeColor) {
setStroke(strokeColor, mStrokeWidth);
}
public void setStrokeWidth(float strokeWidth) {
setStroke(mStrokeColor, strokeWidth);
}
public void setShape(int shapeMode, float radius) {
mShapeChanged = mShapeMode != shapeMode || mRadius != radius;
if (mShapeChanged) {
mShapeMode = shapeMode;
mRadius = radius;
mMainShape = null;
mCircleShape = null;
requestLayout();
}
}
public void setShapeMode(int shapeMode) {
setShape(shapeMode, mRadius);
}
public void setShapeRadius(float radius) {
setShape(mShapeMode, radius);
}
}
自定义属性 :
"StrokeImageView">
"shapeMode" format="enum"> // 描边形状,圆形 or 矩形
"circle" value="0" />
"roundRect" value="1" />
"roundRadius" format="dimension" /> // 描边弧度
"strokeWidth" format="dimension" /> // 描边宽度
"strokeColor" format="color" /> // 描边的颜色
XML 中使用方法
<com.example.kotlin.utils.StrokeImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@mipmap/ball"
app:shapeMode="circle"
app:strokeWidth="2dp"
app:strokeColor="@android:color/holo_red_light"/>