对于自定义View直接继承View这种方式,主要是重写onDraw()方法,来实现不规则的图形效果。
需要注意处理的地方:
1、需要自己支持 wrap_content。
2、需要自己处理 padding。
本篇文章记录实现一个自定义的圆形进度条,支持wrap_content和padding。效果图如下:
1、自定义属性
在values文件夹下创建自定义属性的 attrs.xml 文件,内容如下:
2、创建 CircleProgressView 继承View
对于自定义 CircleProgressView ,实现的大致思路是:
1、在构造方法中读取自定义属性并设置。
2、重写 onMeasure()方法,处理 wrap_content 和 padding 。
3、重写 onDraw() 方式实现效果。
4、处理点击滑动事件(如果需要)。
代码如下:
public class CircleProgressView extends View {
private static final String TAG = "CircleProgressView";
private Paint paintRing; //圆环的画笔
private Paint paintArc; //圆弧的画笔
private Paint paintText;
private RectF rectFArc; //圆弧的Rect
private Rect rectRing; //圆环的Rect
private int[] gradientColorsArc; //弧的渐变颜色
private int ringColor = Color.GRAY;
private int arcColor = Color.BLUE;
private int textColor = Color.BLACK;
private int backgroundColor = Color.TRANSPARENT;
private int strokeWidth = 100;
private int startAngle = 0;
private int sweepAngle = 0;
private int progress = 0;
private int defaultSize = 600;
private int size;
private int paddingLeft;
private int paddingRight;
private int paddingTop;
private int paddingBottom;
private String textProgress = "0%";
private int textSize = 50;
private int textX, textY;
public CircleProgressView(Context context) {
this(context, null);
}
public CircleProgressView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public CircleProgressView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CircleProgressView, defStyleAttr, 0);
ringColor = a.getColor(R.styleable.CircleProgressView_ring_color, Color.GRAY);
arcColor = a.getColor(R.styleable.CircleProgressView_progress_color, Color.BLUE);
backgroundColor = a.getColor(R.styleable.CircleProgressView_background_color, Color.TRANSPARENT);
strokeWidth = a.getInt(R.styleable.CircleProgressView_progress_width, 100);
startAngle = a.getInt(R.styleable.CircleProgressView_start_angle, 0);
progress = a.getInt(R.styleable.CircleProgressView_progress, 0);
textSize = dip2px(context, a.getInt(R.styleable.CircleProgressView_progress_text_size, 50));
textColor = a.getColor(R.styleable.CircleProgressView_progress_text_color, Color.BLACK);
a.recycle();
init();
}
private void init() {
paintRing = new Paint();
paintRing.setAntiAlias(true);
paintRing.setColor(ringColor);
paintRing.setStrokeWidth(strokeWidth);
paintRing.setStyle(Paint.Style.STROKE);
paintArc = new Paint();
paintArc.setAntiAlias(true);
paintArc.setColor(arcColor);
paintArc.setStrokeWidth(strokeWidth);
paintArc.setStyle(Paint.Style.STROKE);
paintText = new Paint();
paintText.setAntiAlias(true);
paintText.setColor(textColor);
paintText.setStyle(Paint.Style.FILL);
paintText.setTextAlign(Paint.Align.CENTER);
paintText.setTextSize(textSize);
gradientColorsArc = new int[] {Color.GREEN, Color.RED};
sweepAngle = (int) ((progress / 100.0f) * 360);
textProgress = progress + "%";
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
size = Math.min(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec));
setMeasuredDimension(size, size);
paddingLeft = getPaddingLeft();
paddingRight = getPaddingRight();
paddingTop = getPaddingTop();
paddingBottom = getPaddingBottom();
//除去padding的可用大小
int rectWidth = size - paddingLeft - paddingRight;
int rectHeight = size - paddingTop - paddingBottom;
//真实可用大小(正方形)
int rectMinSize = Math.min(rectHeight, rectWidth);
Log.e(TAG, "onMeasure rectWidth:" + rectWidth + " rectHeight:" + rectHeight + " rectMinSize:" + rectMinSize);
//弧的rect
rectFArc = new RectF(paddingLeft + strokeWidth / 2, paddingTop + strokeWidth / 2,
paddingLeft + rectMinSize - strokeWidth / 2, paddingTop + rectMinSize - strokeWidth / 2);
//圆环的rect
rectRing = new Rect(paddingLeft, paddingTop, paddingLeft + rectMinSize, paddingTop + rectMinSize);
//progress字的位置
textX = paddingLeft + rectMinSize / 2;
textY = (int) (paddingTop + (rectMinSize - (paintText.descent() - paintText.ascent())) / 2 - paintText.ascent());
//设置弧的渐变颜色
LinearGradient linearGradient = new LinearGradient(0, 0, size, 0, gradientColorsArc, null, Shader.TileMode.CLAMP);
paintArc.setShader(linearGradient);
//gradient = new SweepGradient(size / 2, size / 2, gradientColors, null);
//paintArc.setShader(gradient);
Log.e(TAG, "onMeasure size:" + size);
}
@Override
protected void onDraw(Canvas canvas) {
canvas.drawColor(backgroundColor);
//画圆环
canvas.drawCircle(paddingLeft + rectRing.width() / 2, paddingTop + rectRing.width() / 2, (rectRing.width() - strokeWidth) / 2, paintRing);
//画弧
//drawArc(@NonNull RectF oval, float startAngle, float sweepAngle, boolean useCenter, @NonNull Paint paint)
canvas.drawArc(rectFArc, startAngle, sweepAngle, false, paintArc);
canvas.drawText(textProgress, textX, textY, paintText);
}
/**
* 以动画的形式改变progress
*
* @param progress
* @param duration
*/
public void setProgressWithAnimation(int progress, int duration) {
Animator anim = ObjectAnimator.ofFloat(this, "progress", progress);
anim.setDuration(duration);
anim.setInterpolator(new DecelerateInterpolator());
anim.start();
}
private int measureWidth(int widthMeasureSpec) {
int result = defaultSize;
int specMode = MeasureSpec.getMode(widthMeasureSpec);
int specSize = MeasureSpec.getSize(widthMeasureSpec);
Log.e(TAG, "measureWidth specMode:" + specMode + " specSize:" + specSize);
switch(specMode) {
case MeasureSpec.EXACTLY:
result = specSize;
break;
case MeasureSpec.AT_MOST:
result = Math.min(defaultSize, specSize);
break;
case MeasureSpec.UNSPECIFIED:
result = defaultSize;
break;
}
return result;
}
private int measureHeight(int heightMeasureSpec) {
int result = defaultSize;
int specMode = MeasureSpec.getMode(heightMeasureSpec);
int specSize = MeasureSpec.getSize(heightMeasureSpec);
Log.e(TAG, "measureHeight specMode:" + specMode + " specSize:" + specSize);
switch(specMode) {
case MeasureSpec.EXACTLY:
result = specSize;
break;
case MeasureSpec.AT_MOST:
result = Math.min(defaultSize, specSize);
break;
case MeasureSpec.UNSPECIFIED:
result = defaultSize;
break;
}
return result;
}
public void setRingColor(int ringColor) {
this.ringColor = ringColor;
invalidate();
}
public void setArcColor(int arcColor) {
this.arcColor = arcColor;
invalidate();
}
public void setStrokeWidth(int strokeWidth) {
this.strokeWidth = strokeWidth;
requestLayout();
}
public void setStartAngle(int startAngle) {
this.startAngle = (startAngle >= 0 && startAngle <= 360) ? startAngle : 0;
this.startAngle = startAngle;
invalidate();
}
public void setProgress(float progress) {
setProgress((int) progress);
}
public void setProgress(int progress) {
this.progress = (progress <= 100) ? progress : 100;
textProgress = this.progress + "%";
sweepAngle = (int) ((this.progress / 100.0f) * 360);
invalidate();
}
public float getProgress() {
return this.progress;
}
public void setTextColor(int textColor) {
this.textColor = textColor;
invalidate();
}
public void setTextSize(int textSize) {
this.textSize = dip2px(getContext(), textSize);
}
public static int dip2px(Context context, float dpValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
}
3、在 layout 中使用