最终的效果:
思路就是在onDraw()中画一些内容,主要方法有这些:
/**
* 剪裁圆形区域
*/
clipCircle(canvas);
/**
* 画圆边线
*/
drawCircle(canvas);
/**
* 画波浪线
*/
drawWave(canvas);
/**
* 画进度文字
*/
drawText(canvas);
1.clipCircle(canvas)这个方法剪裁出一个圆形画布:
private void clipCircle(Canvas canvas) {
Path circlePath = new Path();
circlePath.addCircle(screenWidth / 2, screenHeignt / 2, screenHeignt / 2, Path.Direction.CW);
canvas.clipPath(circlePath);
}
2.drawCircle(canvas)画出圆的边线:
private void drawCircle(Canvas canvas) {
canvas.drawCircle(screenHeignt / 2, screenHeignt / 2, screenHeignt / 2, circlePaint);
}
3.drawWave(canvas)画波浪线:
private void drawWave(Canvas canvas) {
int height = (int) (progress / 100 * screenHeignt);
startPoint.y = -height;
canvas.translate(0, screenHeignt);
path = new Path();
wavePaint.setStyle(Paint.Style.FILL);
wavePaint.setColor(Color.GREEN);
int wave = screenWidth / 4;
path.moveTo(startPoint.x, startPoint.y);
for (int i = 0; i < 4; i++) {
int startX = startPoint.x + i * wave * 2;
int endX = startX + 2 * wave;
if (i % 2 == 0) {
path.quadTo((startX + endX) / 2, startPoint.y + amplitude, endX, startPoint.y);
} else {
path.quadTo((startX + endX) / 2, startPoint.y - amplitude, endX, startPoint.y);
}
}
path.lineTo(screenWidth, screenHeignt / 2);
path.lineTo(-screenWidth, screenHeignt / 2);
path.lineTo(-screenWidth, 0);
path.close();
canvas.drawPath(path, wavePaint);
startPoint.x += 10;
if (startPoint.x > 0) {
startPoint.x = -screenWidth;
}
path.reset();
}
这段代码说来有些复杂;
首先我们看到height,这个是波浪曲线所在的y轴的曲线,progress是传进来的进度为了计算我转化成百分比先除以100再乘以屏幕高度(这里的屏幕高度大家可以理解成直径)。
其次我把屏幕y轴移动到了控件的底部也就是如图这样的:
接下来我就开始创建path设置画笔开始画波浪线了, 我们设置的startpoint为x轴负一个屏幕的宽度,从这个点开始画两个完整的正弦曲线,wave的大小为screenWidth/4(在图中标注了),在for中改变startpoint并且画出曲线(每个人可能有不一样的实现方法,这段代码看起来可能费劲),最终把这个波浪与右下做下点连起来形成闭合图形,用于着色,那么画完了怎么让波浪冻起来呢?我们每次刷新ondraw()都让起始点的x+10,然后判断如果x大于0,也就是起始点来到了(0,screenWidth)这个点的时候,我们把startpoint的x坐标回到最初的位置即可,这样每次刷新就形成了一个动态波浪的效果了。波浪的高度通过刚开始的startPoint.y = -height来实现,加符号因为y轴复方向为进度的增长方向。
4.drawText()绘制文字:
private void drawText(Canvas canvas) {
Rect targetRect = new Rect(0, -screenHeignt, screenWidth, 0);
Paint.FontMetricsInt fontMetrics = textPaint.getFontMetricsInt();
int baseline = (targetRect.bottom + targetRect.top - fontMetrics.bottom - fontMetrics.top) / 2;
textPaint.setTextAlign(Paint.Align.CENTER);
canvas.drawText(String.valueOf((int) textProgress + "%"), targetRect.centerX(), baseline, textPaint);
}
绘制文字为了让文字居中我做了一些必要的计算
PS:细心的小伙伴在setProgress()这个方法中发现了一点问题为什么会这么写呢:
public void setProgress(float progress) {
this.textProgress = progress;
if (progress == 100) {
this.progress = progress + amplitude;
} else {
this.progress = progress;
}
}
原因是因为当我们的进度达到100%的时候,由于sin曲线和从左到右的波动还是会出现空白流动的区域,就像如图这样:
这样岂不是很蛋疼,进度满了不能出现这种情况,所以我手动判断如果进度在100%就把y坐标加上一个保险值,保证不出现以上这个情况。
书写本文旨在练习与理解自定义view、贝赛尔曲线等等知识,如有错误欢迎指正,源码链接GitHub传送门,欢迎clone&start