前言
这个View其实非常的简单,之所以写这个博客,一方面是记录下所学,另一方面是开启我的博客之旅。
效果示例
效果分析
这个效果还是非常简单的,就是画圆、画字外加一个属性动画的结果
既然是自定义View,那我们就按自定义View的流程来介绍:
1.继承合适的View,复写构造方法,初始化一些变量
2.自定义view的宽高测量 onMeasure 方法
3.自定义view的绘制 onDraw 方法
4.属性动画效果
开发流程
自定义View之继承
因为本控件层级不复杂,就是一个单独的View,不需要继承ViewGroup,直接继承View就行。
在这一步,除了继承之外,还需要做一些成员变量的创建和初始化
比如控件的宽高、内圆和外圆的画笔及其画笔的颜色、线宽、比率文字的大小和颜色、动画时长等等,核心代码如下:
/**
* 上下文
*/
private Context mContext;
/**
* 控件的宽高
*/
private int viewHeight, viewWidth;
/**
* 内圆和外圆的画笔
*/
private Paint outCirclePaint, innerCirclePaint;
/**
* 内圆和外圆的颜色
*/
private int outCircleColor, innerCircleColor;
/**
* 内圆和外圆的画笔宽度
*/
private int outCircleStrokeWidth, innerCircleStrokeWidth;
/**
* 比率数字画笔和其他文字的画笔
*/
private Paint ratePaint, expressPaint, percentPaint;
/**
* 比率数字画笔和其他文字的画笔的颜色
*/
private int rateColor, expressColor, percentColor;
/**
* 比率数字和其他文字的大小
*/
private int rateSize, expressSize, percentSize;
/**
* 比率数字 默认45
*/
private String rateText;
/**
* 比率代表的含义 比如 正确率、签到率等等
*/
private String expressText;
/**
* 比率对应的弧度
*/
private float sweep;
/**
* 动画进行的时间
*/
private int duration;
public CircleRateView(Context context) {
super(context);
initView(context, null, -1);
}
public CircleRateView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
initView(context, attrs, -1);
}
public CircleRateView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView(context, attrs, defStyleAttr);
}
private void initView(Context context, AttributeSet attrs, int defStyleAttr) {
……
//初始化相关画笔
outCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
outCirclePaint.setStrokeWidth(dip2px(outCircleStrokeWidth));
outCirclePaint.setStyle(Paint.Style.STROKE);
outCirclePaint.setColor(outCircleColor);
innerCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
innerCirclePaint.setStrokeWidth(dip2px(innerCircleStrokeWidth));
innerCirclePaint.setStyle(Paint.Style.STROKE);
innerCirclePaint.setColor(innerCircleColor);
ratePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
ratePaint.setStrokeWidth(dip2px(1));
ratePaint.setStyle(Paint.Style.FILL);
ratePaint.setTextSize(rateSize);
ratePaint.setTextAlign(Paint.Align.CENTER);
ratePaint.setColor(rateColor);
expressPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
expressPaint.setStrokeWidth(dip2px(1));
expressPaint.setStyle(Paint.Style.FILL);
expressPaint.setTextSize(expressSize);
expressPaint.setTextAlign(Paint.Align.CENTER);
expressPaint.setColor(expressColor);
percentPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
percentPaint.setStrokeWidth(dip2px(1));
percentPaint.setStyle(Paint.Style.FILL);
percentPaint.setTextSize(percentSize);
percentPaint.setTextAlign(Paint.Align.LEFT);
percentPaint.setColor(percentColor);
……
}
自定义View之 onMeasure
这个方法的详细介绍可以自己搜搜看,这里就不做叙述,主要是通过 MeasureSpec 这个类来测量宽高,并设置给View
核心代码如下:
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthValue = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightValue = MeasureSpec.getSize(heightMeasureSpec);
if (widthMode == MeasureSpec.EXACTLY) {
viewWidth = widthValue;
} else {
viewWidth = dip2px(162);
}
if (heightMode == MeasureSpec.EXACTLY) {
viewHeight = heightValue;
} else {
viewHeight = dip2px(162);
}
//将View的宽高设置为正方形,使其宽高一致,等于最小的那个
if (viewWidth <= viewHeight) {
viewHeight = viewWidth;
} else {
viewWidth = viewHeight;
}
setMeasuredDimension(viewWidth, viewHeight);
自定义View之 onDraw
这个方法就是自定义View的核心了,文字和圆的绘制都在这个方法里。
1.绘制外圆:
外圆的半径等于控件宽的一半,中心为宽高的各一半,根据API直接画圆即可。值得一提,因为后续想要将外圆的线宽度可控制,所以圆的半径减去了线宽,至于减1dp的值,是为了美观性,外圆不至于擦边。
//画外圆
canvas.drawCircle(viewWidth / 2, viewHeight / 2, viewWidth / 2 - outCircleStrokeWidth - dip2px(1), outCirclePaint);
2.绘制内圆:
内圆是具有动画效果的,但是在动画之前,其本质就是一段圆弧,所以先根据API画一段圆弧。这里的sweep是百分比对应的弧度,后续动画的时候会用到
//画内圆
RectF rectF = new RectF(outCircleStrokeWidth + innerCircleStrokeWidth + dip2px(8), outCircleStrokeWidth + innerCircleStrokeWidth + dip2px(8), viewWidth - dip2px(8) - outCircleStrokeWidth - innerCircleStrokeWidth, viewHeight - dip2px(8) - outCircleStrokeWidth - innerCircleStrokeWidth);
canvas.drawArc(rectF, 90, sweep, false, innerCirclePaint);
3.画文字和比率:
绘制文字的时候,就涉及到绘制的中心点和基线的问题了,中心点的对其方式不同,绘制效果就不同,有时候根据位置来选择不同的对其方式,会使绘制更加的简单。关于文字的需要使用到 FontMetrics 这个相关的类,具体不做复述,不了解自己去搜下,后续我也会开一篇新章去讲一下
//获得比率数字的FontMetricsInt
Paint.FontMetricsInt rateFont = ratePaint.getFontMetricsInt();
ratePaint.setTextAlign(Paint.Align.CENTER);
int rateX = viewWidth / 2;
int rateY = viewHeight / 2 + (rateFont.descent / 2 - rateFont.ascent / 2 - rateFont.descent);
canvas.drawText(rateText, rateX, rateY, ratePaint);
//获取表达意思文字的FontMetricsInt
Paint.FontMetricsInt textFout = expressPaint.getFontMetricsInt();
expressPaint.setTextAlign(Paint.Align.CENTER);
int expressX = viewWidth / 2;
int expressY = (int) (viewHeight * 0.26 + (textFout.descent / 2 - textFout.ascent / 2 - textFout.descent));
canvas.drawText(expressText, expressX, expressY, expressPaint);
//根据比率文字的宽度来计算百分比的x坐标,y坐标则和比率保持一致
percentPaint.setTextAlign(Paint.Align.LEFT);
float rateWidth = ratePaint.measureText(rateText);
int percentX = (int) (rateWidth / 2 + viewWidth / 2 + dip2px(11));
int percentY = viewHeight / 2 + (rateFont.descent / 2 - rateFont.ascent / 2 - rateFont.descent);
canvas.drawText("%", percentX, percentY, percentPaint);
至此,静态的绘制过程就完成了,效果如下:
属性动画
前文讲到sweep是动画的弧度,这个动画的效果其实很简单,就是sweep从0到45的过程,只需要对sweep进行属性动画即可,在sweep增加的过程中不断的进行重绘就可以达到效果。
ValueAnimator valueAnimator = new ValueAnimator().ofFloat(0, sweep);
valueAnimator.setDuration(duration);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
sweep = (float) animation.getAnimatedValue();
invalidate();
}
});
valueAnimator.start();
ValueAnimator numAnimator = new ValueAnimator().ofFloat(0, Float.parseFloat(rateText));
numAnimator.setDuration(duration);
numAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
Float rate = (Float) animation.getAnimatedValue();
BigDecimal bigDecimal = new BigDecimal(rate);
//四舍五入,不要小数
bigDecimal = bigDecimal.setScale(0, BigDecimal.ROUND_HALF_UP);
rateText = bigDecimal.toString();
invalidate();
}
});
numAnimator.start();
自定义属性
为了方便使用,自定义了一些属性,可在xml中直接使用属性进行设置。
1.定义属性
2.赋值属性
//通过读取attr文件获取属性值,无配置即默认值
TypedArray typedArray = mContext.obtainStyledAttributes(attrs, R.styleable.CircleRateView);
outCircleColor = typedArray.getColor(R.styleable.CircleRateView_outCircleColor, Color.BLACK);
innerCircleColor = typedArray.getColor(R.styleable.CircleRateView_innerCircleColor, Color.BLACK);
rateColor = typedArray.getColor(R.styleable.CircleRateView_rateColor, Color.BLACK);
expressColor = typedArray.getColor(R.styleable.CircleRateView_expressColor, Color.BLACK);
percentColor = typedArray.getColor(R.styleable.CircleRateView_percentColor, Color.BLACK);
rateSize = typedArray.getDimensionPixelSize(R.styleable.CircleRateView_rateSize, dip2px(50));
expressSize = typedArray.getDimensionPixelSize(R.styleable.CircleRateView_expressSize, dip2px(16));
percentSize = typedArray.getDimensionPixelSize(R.styleable.CircleRateView_percentSize, dip2px(16));
outCircleStrokeWidth = typedArray.getDimensionPixelSize(R.styleable.CircleRateView_outCircleStrokeWidth, dip2px(0.5f));
innerCircleStrokeWidth = typedArray.getDimensionPixelSize(R.styleable.CircleRateView_innerCircleStrokeWidth, dip2px(0.5f));
rateText = typedArray.getString(R.styleable.CircleRateView_rateText);
if (TextUtils.isEmpty(rateText)) {
rateText = "45";
}
expressText = typedArray.getString(R.styleable.CircleRateView_expressText);
if (TextUtils.isEmpty(expressText)) {
expressText = "正确率";
}
duration = typedArray.getInt(R.styleable.CircleRateView_duration, 2000);
//记得回收对象
typedArray.recycle();
3.使用属性
分享
本文至此结束,本着分享和重复使用的初衷,本控件源码已经上传至github,可直接在项目中添加依赖,进行使用。
源码及详细的使用方法和相关API介绍请点击:
GitHub: https://github.com/feyolny/CircleRateView
交流
我的GitHub:https://github.com/feyolny
我的:https://www.jianshu.com/u/79cb1fd42ea9
我的CSDN:https://blog.csdn.net/feyolny
欢迎大家一起交流!