先看效果图:
这里,我没有添加打钩的图片,而是单纯的用canvas来实现动画效果
中间的钩,我用了路径Path来进行描绘并实现它的动画效果。首先,这个钩由两条线段,三个顶点组成的,其实将这三个顶点作为参数传入Path对象中的lineTo()方法,再调用一下canvas.drawPath(),我们就可以得到图中这个钩的样式了
然后说说动画效果的实现,postInvalidateDelay()这个方法就很重要了,它能让onDraw()方法每隔一段时间被调用一次
所以,外部的圆环我们可以用drawArc()绘制圆弧的方式实现,每次调用onDraw()方法时绘制一定的角度,直到绘制出完整的圆环,就产生以上图中的动画效果
绘制圆环代码如下:
private int perAngle=5,currentAngle=0; //每次绘制的角度;当前已经绘制的角度
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//......
//防止绘制的角度超过360度
if(currentAngle+perAngle>360)currentAngle=360;
else currentAngle+=perAngle;
canvas.drawArc(circleRectF,0,currentAngle,false,circlePaint);
//......
postInvalidateDelayed(5);
}
最后是钩的动画实现,我首先计算了两条线段的斜率和截距:
//提供打钩的三个坐标点(radius/2,radius),(radius,(radius*3)/2),((radius*3)/2,radius/2)
pathX=new int[]{radius/2,radius,(radius*3)/2};
pathY=new int[]{radius,(radius*3)/2,radius/2};
slopes=new float[pathX.length-1];
intercepts=new float[pathX.length-1];
//根据上面三个坐标点,得到两条线段,计算斜率和截距。
for(int i=0;i
pathX,pathY (int[]) 记录三个顶点的坐标
slopes (float[]) 记录两条线段的斜率
intercepts (float[]) 记录两条线段的截距
然后,我们设想一下,钩的移动方向一直是向右边的,也就是x轴的正方向,所以,我们从第一个顶点出发,每次调用onDraw()方法时,沿着上面的两个线段往右跑,即每次让x坐标增大一定的值,再通过y=ax+b,就可以计算出相应的y值。将(x,y)存入Path路径中,最后通过canvas.drawPath()将新保存的路径画出来,就可以实现动画效果
具体代码如下:
private int moveX=5,currentMoveX; //钩每次绘制的长度;目前移动到的位置
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//......
//绘制完圆环后,绘制打钩
//每次向x轴的正方向移动moveX的距离来实现动画效果
if(currentAngle>=360){
//防止绘制时出边界
if(currentMoveX+moveX>pathX[pathX.length-1])currentMoveX=pathX[pathX.length-1];
else currentMoveX+=moveX;
//判断目前已经绘制到哪一条线段上
for(int i=1;i
下面是自定义View的完整代码:
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.View;
import androidx.annotation.Nullable;
public class MyProgressFinishView extends View {
private Paint circlePaint; //环的画笔
private RectF circleRectF; //环的外切矩形
private Path path; //钩的路径
private Paint pathPaint; //钩的画笔
private int radius=200; //环的半径
private int perAngle=5,currentAngle=0; //每次绘制的角度;当前已经绘制的角度
private int[] pathX,pathY;
private float[] slopes; //每条直线的斜率
private float[] intercepts; //每条直线的截距
private int moveX=5,currentMoveX; //钩每次绘制的长度;目前移动到的位置
private boolean isFirstMeasure=true;
public MyProgressFinishView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
initPaint();
}
//初始化画笔
private void initPaint(){
circlePaint=new Paint();
circlePaint.setColor(Color.BLUE);
circlePaint.setStrokeWidth(8);
circlePaint.setStyle(Paint.Style.STROKE);
pathPaint=new Paint();
pathPaint.setColor(Color.BLUE);
pathPaint.setStrokeWidth(10);
pathPaint.setStyle(Paint.Style.STROKE);
}
//初始化钩的路径
private void initPath(){
circleRectF=new RectF(circlePaint.getStrokeWidth(),circlePaint.getStrokeWidth(),radius*2,radius*2);
path=new Path();
//提供打钩的三个坐标点(radius/2,radius),(radius,(radius*3)/2),((radius*3)/2,radius/2)
pathX=new int[]{radius/2,radius,(radius*3)/2};
pathY=new int[]{radius,(radius*3)/2,radius/2};
slopes=new float[pathX.length-1];
intercepts=new float[pathX.length-1];
//根据上面三个坐标点,得到两条线段,计算斜率和截距。
for(int i=0;i360)currentAngle=360;
else currentAngle+=perAngle;
canvas.drawArc(circleRectF,0,currentAngle,false,circlePaint);
//绘制完圆环后,绘制打钩
//每次向x轴的正方向移动moveX的距离来实现动画效果
if(currentAngle>=360){
//防止绘制时出边界
if(currentMoveX+moveX>pathX[pathX.length-1])currentMoveX=pathX[pathX.length-1];
else currentMoveX+=moveX;
//判断目前已经绘制到哪一条线段上
for(int i=1;i
xml布局文件