339 lines
12 KiB
Java
339 lines
12 KiB
Java
package com.shehuan.niv;
|
||
|
||
|
||
import android.content.Context;
|
||
import android.content.res.TypedArray;
|
||
import android.graphics.Canvas;
|
||
import android.graphics.Color;
|
||
import android.graphics.Paint;
|
||
import android.graphics.Path;
|
||
import android.graphics.PorterDuff;
|
||
import android.graphics.PorterDuffXfermode;
|
||
import android.graphics.RectF;
|
||
import android.graphics.Xfermode;
|
||
import android.os.Build;
|
||
import android.util.AttributeSet;
|
||
|
||
import androidx.annotation.ColorInt;
|
||
import androidx.annotation.Nullable;
|
||
import androidx.appcompat.widget.AppCompatImageView;
|
||
|
||
public class NiceImageView extends AppCompatImageView {
|
||
private Context context;
|
||
|
||
private boolean isCircle; // 是否显示为圆形,如果为圆形则设置的corner无效
|
||
private boolean isCoverSrc; // border、inner_border是否覆盖图片
|
||
private int borderWidth; // 边框宽度
|
||
private int borderColor = Color.WHITE; // 边框颜色
|
||
private int innerBorderWidth; // 内层边框宽度
|
||
private int innerBorderColor = Color.WHITE; // 内层边框充色
|
||
|
||
private int cornerRadius; // 统一设置圆角半径,优先级高于单独设置每个角的半径
|
||
private int cornerTopLeftRadius; // 左上角圆角半径
|
||
private int cornerTopRightRadius; // 右上角圆角半径
|
||
private int cornerBottomLeftRadius; // 左下角圆角半径
|
||
private int cornerBottomRightRadius; // 右下角圆角半径
|
||
|
||
private int maskColor; // 遮罩颜色
|
||
|
||
private Xfermode xfermode;
|
||
|
||
private int width;
|
||
private int height;
|
||
private float radius;
|
||
|
||
private float[] borderRadii;
|
||
private float[] srcRadii;
|
||
|
||
private RectF srcRectF; // 图片占的矩形区域
|
||
private RectF borderRectF; // 边框的矩形区域
|
||
|
||
private Paint paint;
|
||
private Path path; // 用来裁剪图片的ptah
|
||
private Path srcPath; // 图片区域大小的path
|
||
|
||
public NiceImageView(Context context) {
|
||
this(context, null);
|
||
}
|
||
|
||
public NiceImageView(Context context, @Nullable AttributeSet attrs) {
|
||
this(context, attrs, 0);
|
||
}
|
||
|
||
public NiceImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||
super(context, attrs, defStyleAttr);
|
||
|
||
this.context = context;
|
||
|
||
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.NiceImageView, 0, 0);
|
||
for (int i = 0; i < ta.getIndexCount(); i++) {
|
||
int attr = ta.getIndex(i);
|
||
if (attr == R.styleable.NiceImageView_is_cover_src) {
|
||
isCoverSrc = ta.getBoolean(attr, isCoverSrc);
|
||
} else if (attr == R.styleable.NiceImageView_is_circle) {
|
||
isCircle = ta.getBoolean(attr, isCircle);
|
||
} else if (attr == R.styleable.NiceImageView_border_width) {
|
||
borderWidth = ta.getDimensionPixelSize(attr, borderWidth);
|
||
} else if (attr == R.styleable.NiceImageView_border_color) {
|
||
borderColor = ta.getColor(attr, borderColor);
|
||
} else if (attr == R.styleable.NiceImageView_inner_border_width) {
|
||
innerBorderWidth = ta.getDimensionPixelSize(attr, innerBorderWidth);
|
||
} else if (attr == R.styleable.NiceImageView_inner_border_color) {
|
||
innerBorderColor = ta.getColor(attr, innerBorderColor);
|
||
} else if (attr == R.styleable.NiceImageView_corner_radius) {
|
||
cornerRadius = ta.getDimensionPixelSize(attr, cornerRadius);
|
||
} else if (attr == R.styleable.NiceImageView_corner_top_left_radius) {
|
||
cornerTopLeftRadius = ta.getDimensionPixelSize(attr, cornerTopLeftRadius);
|
||
} else if (attr == R.styleable.NiceImageView_corner_top_right_radius) {
|
||
cornerTopRightRadius = ta.getDimensionPixelSize(attr, cornerTopRightRadius);
|
||
} else if (attr == R.styleable.NiceImageView_corner_bottom_left_radius) {
|
||
cornerBottomLeftRadius = ta.getDimensionPixelSize(attr, cornerBottomLeftRadius);
|
||
} else if (attr == R.styleable.NiceImageView_corner_bottom_right_radius) {
|
||
cornerBottomRightRadius = ta.getDimensionPixelSize(attr, cornerBottomRightRadius);
|
||
} else if (attr == R.styleable.NiceImageView_mask_color) {
|
||
maskColor = ta.getColor(attr, maskColor);
|
||
}
|
||
}
|
||
ta.recycle();
|
||
|
||
borderRadii = new float[8];
|
||
srcRadii = new float[8];
|
||
|
||
borderRectF = new RectF();
|
||
srcRectF = new RectF();
|
||
|
||
paint = new Paint();
|
||
path = new Path();
|
||
|
||
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.O_MR1) {
|
||
xfermode = new PorterDuffXfermode(PorterDuff.Mode.DST_IN);
|
||
} else {
|
||
xfermode = new PorterDuffXfermode(PorterDuff.Mode.DST_OUT);
|
||
srcPath = new Path();
|
||
}
|
||
|
||
calculateRadii();
|
||
clearInnerBorderWidth();
|
||
}
|
||
|
||
@Override
|
||
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
||
super.onSizeChanged(w, h, oldw, oldh);
|
||
width = w;
|
||
height = h;
|
||
|
||
initBorderRectF();
|
||
initSrcRectF();
|
||
}
|
||
|
||
@Override
|
||
protected void onDraw(Canvas canvas) {
|
||
// 使用图形混合模式来显示指定区域的图片
|
||
canvas.saveLayer(srcRectF, null, Canvas.ALL_SAVE_FLAG);
|
||
if (!isCoverSrc) {
|
||
float sx = 1.0f * (width - 2 * borderWidth - 2 * innerBorderWidth) / width;
|
||
float sy = 1.0f * (height - 2 * borderWidth - 2 * innerBorderWidth) / height;
|
||
// 缩小画布,使图片内容不被borders覆盖
|
||
canvas.scale(sx, sy, width / 2.0f, height / 2.0f);
|
||
}
|
||
super.onDraw(canvas);
|
||
paint.reset();
|
||
path.reset();
|
||
if (isCircle) {
|
||
path.addCircle(width / 2.0f, height / 2.0f, radius, Path.Direction.CCW);
|
||
} else {
|
||
path.addRoundRect(srcRectF, srcRadii, Path.Direction.CCW);
|
||
}
|
||
|
||
paint.setAntiAlias(true);
|
||
paint.setStyle(Paint.Style.FILL);
|
||
paint.setXfermode(xfermode);
|
||
//9.0及以上系统图片一次圆一次方的解决办法
|
||
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.O_MR1) {
|
||
canvas.drawPath(path, paint);
|
||
} else {
|
||
srcPath.addRect(srcRectF, Path.Direction.CCW);
|
||
// 计算tempPath和path的差集
|
||
srcPath.op(path, Path.Op.DIFFERENCE);
|
||
canvas.drawPath(srcPath, paint);
|
||
srcPath.reset();//1
|
||
}
|
||
paint.setXfermode(null);
|
||
|
||
// 绘制遮罩
|
||
if (maskColor != 0) {
|
||
paint.setColor(maskColor);
|
||
canvas.drawPath(path, paint);
|
||
}
|
||
// 恢复画布
|
||
canvas.restore();
|
||
// 绘制边框
|
||
drawBorders(canvas);
|
||
}
|
||
|
||
private void drawBorders(Canvas canvas) {
|
||
if (isCircle) {
|
||
if (borderWidth > 0) {
|
||
drawCircleBorder(canvas, borderWidth, borderColor, radius - borderWidth / 2.0f);
|
||
}
|
||
if (innerBorderWidth > 0) {
|
||
drawCircleBorder(canvas, innerBorderWidth, innerBorderColor, radius - borderWidth - innerBorderWidth / 2.0f);
|
||
}
|
||
} else {
|
||
if (borderWidth > 0) {
|
||
drawRectFBorder(canvas, borderWidth, borderColor, borderRectF, borderRadii);
|
||
}
|
||
}
|
||
}
|
||
|
||
private void drawCircleBorder(Canvas canvas, int borderWidth, int borderColor, float radius) {
|
||
initBorderPaint(borderWidth, borderColor);
|
||
path.addCircle(width / 2.0f, height / 2.0f, radius, Path.Direction.CCW);
|
||
canvas.drawPath(path, paint);
|
||
}
|
||
|
||
private void drawRectFBorder(Canvas canvas, int borderWidth, int borderColor, RectF rectF, float[] radii) {
|
||
initBorderPaint(borderWidth, borderColor);
|
||
path.addRoundRect(rectF, radii, Path.Direction.CCW);
|
||
canvas.drawPath(path, paint);
|
||
}
|
||
|
||
private void initBorderPaint(int borderWidth, int borderColor) {
|
||
path.reset();
|
||
paint.setStrokeWidth(borderWidth);
|
||
paint.setColor(borderColor);
|
||
paint.setStyle(Paint.Style.STROKE);
|
||
}
|
||
|
||
/**
|
||
* 计算外边框的RectF
|
||
*/
|
||
private void initBorderRectF() {
|
||
if (!isCircle) {
|
||
borderRectF.set(borderWidth / 2.0f, borderWidth / 2.0f, width - borderWidth / 2.0f, height - borderWidth / 2.0f);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 计算图片原始区域的RectF
|
||
*/
|
||
private void initSrcRectF() {
|
||
if (isCircle) {
|
||
radius = Math.min(width, height) / 2.0f;
|
||
srcRectF.set(width / 2.0f - radius, height / 2.0f - radius, width / 2.0f + radius, height / 2.0f + radius);
|
||
} else {
|
||
srcRectF.set(0, 0, width, height);
|
||
if (isCoverSrc) {
|
||
srcRectF = borderRectF;
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 计算RectF的圆角半径
|
||
*/
|
||
private void calculateRadii() {
|
||
if (isCircle) {
|
||
return;
|
||
}
|
||
if (cornerRadius > 0) {
|
||
for (int i = 0; i < borderRadii.length; i++) {
|
||
borderRadii[i] = cornerRadius;
|
||
srcRadii[i] = cornerRadius - borderWidth / 2.0f;
|
||
}
|
||
} else {
|
||
borderRadii[0] = borderRadii[1] = cornerTopLeftRadius;
|
||
borderRadii[2] = borderRadii[3] = cornerTopRightRadius;
|
||
borderRadii[4] = borderRadii[5] = cornerBottomRightRadius;
|
||
borderRadii[6] = borderRadii[7] = cornerBottomLeftRadius;
|
||
|
||
srcRadii[0] = srcRadii[1] = cornerTopLeftRadius - borderWidth / 2.0f;
|
||
srcRadii[2] = srcRadii[3] = cornerTopRightRadius - borderWidth / 2.0f;
|
||
srcRadii[4] = srcRadii[5] = cornerBottomRightRadius - borderWidth / 2.0f;
|
||
srcRadii[6] = srcRadii[7] = cornerBottomLeftRadius - borderWidth / 2.0f;
|
||
}
|
||
}
|
||
|
||
private void calculateRadiiAndRectF(boolean reset) {
|
||
if (reset) {
|
||
cornerRadius = 0;
|
||
}
|
||
calculateRadii();
|
||
initBorderRectF();
|
||
invalidate();
|
||
}
|
||
|
||
/**
|
||
* 目前圆角矩形情况下不支持inner_border,需要将其置0
|
||
*/
|
||
private void clearInnerBorderWidth() {
|
||
if (!isCircle) {
|
||
this.innerBorderWidth = 0;
|
||
}
|
||
}
|
||
|
||
public void isCoverSrc(boolean isCoverSrc) {
|
||
this.isCoverSrc = isCoverSrc;
|
||
initSrcRectF();
|
||
invalidate();
|
||
}
|
||
|
||
public void isCircle(boolean isCircle) {
|
||
this.isCircle = isCircle;
|
||
clearInnerBorderWidth();
|
||
initSrcRectF();
|
||
invalidate();
|
||
}
|
||
|
||
public void setBorderWidth(int borderWidth) {
|
||
this.borderWidth = Utils.dp2px(context, borderWidth);
|
||
calculateRadiiAndRectF(false);
|
||
}
|
||
|
||
public void setBorderColor(@ColorInt int borderColor) {
|
||
this.borderColor = borderColor;
|
||
invalidate();
|
||
}
|
||
|
||
public void setInnerBorderWidth(int innerBorderWidth) {
|
||
this.innerBorderWidth = Utils.dp2px(context, innerBorderWidth);
|
||
clearInnerBorderWidth();
|
||
invalidate();
|
||
}
|
||
|
||
public void setInnerBorderColor(@ColorInt int innerBorderColor) {
|
||
this.innerBorderColor = innerBorderColor;
|
||
invalidate();
|
||
}
|
||
|
||
public void setCornerRadius(int cornerRadius) {
|
||
this.cornerRadius = Utils.dp2px(context, cornerRadius);
|
||
calculateRadiiAndRectF(false);
|
||
}
|
||
|
||
public void setCornerTopLeftRadius(int cornerTopLeftRadius) {
|
||
this.cornerTopLeftRadius = Utils.dp2px(context, cornerTopLeftRadius);
|
||
calculateRadiiAndRectF(true);
|
||
}
|
||
|
||
public void setCornerTopRightRadius(int cornerTopRightRadius) {
|
||
this.cornerTopRightRadius = Utils.dp2px(context, cornerTopRightRadius);
|
||
calculateRadiiAndRectF(true);
|
||
}
|
||
|
||
public void setCornerBottomLeftRadius(int cornerBottomLeftRadius) {
|
||
this.cornerBottomLeftRadius = Utils.dp2px(context, cornerBottomLeftRadius);
|
||
calculateRadiiAndRectF(true);
|
||
}
|
||
|
||
public void setCornerBottomRightRadius(int cornerBottomRightRadius) {
|
||
this.cornerBottomRightRadius = Utils.dp2px(context, cornerBottomRightRadius);
|
||
calculateRadiiAndRectF(true);
|
||
}
|
||
|
||
public void setMaskColor(@ColorInt int maskColor) {
|
||
this.maskColor = maskColor;
|
||
invalidate();
|
||
}
|
||
}
|