本文共 5779 字,大约阅读时间需要 19 分钟。
先看效果图:
这是我在某软件上看到的加载动画,感觉挺不错,就自己研究了一下。下面给大家分享一下该动画的实现过程
一、三个圆环的绘制和运动分析
来看下面这张解析图:
假设每个圆环的初始位置如上图,那么我们可以设定每一个圆环的位置以及它离控件边界的距离(w/6)
为了方便,我们定义控件宽度getWidth()为w,那么左上,右上,正下方圆环的圆心坐标依次为:
(w/4, w/4),(w*3/4, w/4),(w/2, w*3/4)
我们再来看下面这个动画:
发现了吧,实际上每个圆环都在做直线运动,三个圆环组合在一起就呈现出了相互靠近和远离的效果。
所以我们的最终目的是让这三个圆环延三条线段做匀速运动,当某一个圆环移动到了线段的端点时,就要改变它的运动状态,让它延另一条线段移动。
我们作图解析:
梳理一下:
- 处在左上角的圆环会延着编号1的线段移动,右上角的圆环会延着编号2的线段移动,正下方的圆环会延着编号3的线段移动。
- 彼此到达所在线段端点时(保证到达线段端点耗费的时间都相同),改变自身的运动状态,延着下一条线段继续运动。
二、圆环绘制和运动的代码解释
我们先创建一个MovePoint类,保存每个圆环的圆心坐标,所在直线的斜率与截距和每次移动的步长:
public class MovePoint { private float x; private float y; private float k,b; //直线函数式中的斜率与截距 private float moveStep; //移动步长 public float getX() { return x; } public void setX(float x) { this.x = x; setY(x*k+b); } public float getY() { return y; } public void setY(float y) { this.y = y; } public void setCalculate(float k,float b){ this.k=k; this.b=b; } public float getK() { return k; } public void setK(float k) { this.k = k; } public float getB() { return b; } public void setB(float b) { this.b = b; } public float getMoveStep() { return moveStep; } public void setMoveStep(float moveStep) { this.moveStep = moveStep; }}
初始化各属性:
private Paint circlePaint;private int circleRadius=0; //圆的半径private MovePoint[] movePoints=new MovePoint[3]; //记录三个圆环的位置,状态,和移动时的函数公式private float[] x; //三个圆环的x坐标private float[] k,b; //三个圆环所在直线的斜率与截距private int moveTime=70; //移动到线段端点需要的时间public MyThreeCircleLoadView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); init();}private void init(){ circlePaint=new Paint(); circlePaint.setColor(Color.RED); circlePaint.setStyle(Paint.Style.STROKE); circlePaint.setAntiAlias(true);}/** 在左上角向右边移动; y=getWidth()/4* 在右上角向左下移动; y=-2*x+getWidth()*7/4* 在正下方向左上移动; y=2*x-getWidth()/4* */private void setMovePoints(){ x=new float[]{getWidth()/4.0f,getWidth()*3/4.0f,getWidth()/2.0f}; k=new float[]{0,-2,2};b=new float[]{getWidth()/4.0f,getWidth()*7/4.0f,-getWidth()/4.0f}; int len=movePoints.length; for(int i=0;i
注意移动步长的计算,因为我这里每次改变的是每个圆环的x坐标,而不是实际的线段距离,所以在getMoveStep()中,只需要(x1-x2)获得线段端点间的水平距离。
我们要求每个圆环到达线段端点的时间相等,由匀速运动公式可得v=(x/t),我们给定相同的t,x又是已知的,就可以得到每个圆环在当前线段上的速度v。
每个点都应该用float变量,否则会产生速度计算上的误差
绘制圆环:
@Overrideprotected void onDraw(Canvas canvas) { super.onDraw(canvas); for (MovePoint movePoint : movePoints) { canvas.drawCircle(movePoint.getX(), movePoint.getY(), circleRadius, circlePaint); changeCircleState(movePoint); } move();}//改变圆环移动的状态private void changeCircleState(MovePoint movePoint){ //从左上角向右移并移动到右上方顶点时 if (movePoint.getX() >= x[1] && movePoint.getY() == movePoint.getX() * k[0] + b[0]) { movePoint.setCalculate(k[1], b[1]); movePoint.setMoveStep(getMoveStep(x[2], x[1], moveTime)); } //右上向坐下移动并移动到正下方顶点时 else if (movePoint.getX() <= x[2] && movePoint.getY() == movePoint.getX() * k[1] + b[1]) { movePoint.setCalculate(k[2], b[2]); movePoint.setMoveStep(getMoveStep(x[0], x[2], moveTime)); } //从正下方向左上移动并移动到左上方顶点时 else if (movePoint.getX() <= x[0]) { movePoint.setCalculate(k[0], b[0]); movePoint.setMoveStep(getMoveStep(x[1], x[0], moveTime)); }}
changeCircleState():用于判断圆环是否运动到线段的端点。若运动到了端点,就改变该圆环的运动状态和附属的线段
移动圆环:
//移动圆环private void move(){ for (MovePoint movePoint : movePoints) { movePoint.setX(movePoint.getX() + movePoint.getMoveStep()); } postInvalidateDelayed(5);}
下面是自定义View的完整代码:
import android.content.Context;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.graphics.Path;import android.util.AttributeSet;import android.util.Log;import android.view.View;import com.bean.MovePoint;import androidx.annotation.Nullable;public class MyThreeCircleLoadView extends View { private Paint circlePaint; private int circleRadius=0; //圆的半径 private MovePoint[] movePoints=new MovePoint[3]; //记录三个圆环的位置,状态,和移动时的函数公式 private float[] x; //三个圆环的x坐标 private float[] k,b; //三个圆环所在直线的斜率与截距 private int moveTime=70; //移动到线段端点需要的时间 public MyThreeCircleLoadView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); init(); } private void init(){ circlePaint=new Paint(); circlePaint.setColor(Color.RED); circlePaint.setStyle(Paint.Style.STROKE); circlePaint.setAntiAlias(true); } /* * 在左上角向右边移动; y=getWidth()/4 * 在右上角向左下移动; y=-2*x+getWidth()*7/4 * 在正下方向左上移动; y=2*x-getWidth()/4 * */ private void setMovePoints(){ x=new float[]{getWidth()/4.0f,getWidth()*3/4.0f,getWidth()/2.0f}; k=new float[]{0,-2,2};b=new float[]{getWidth()/4.0f,getWidth()*7/4.0f,-getWidth()/4.0f}; int len=movePoints.length; for(int i=0;i= x[1] && movePoint.getY() == movePoint.getX() * k[0] + b[0]) { movePoint.setCalculate(k[1], b[1]); movePoint.setMoveStep(getMoveStep(x[2], x[1], moveTime)); } //右上向坐下移动并移动到正下方顶点时 else if (movePoint.getX() <= x[2] && movePoint.getY() == movePoint.getX() * k[1] + b[1]) { movePoint.setCalculate(k[2], b[2]); movePoint.setMoveStep(getMoveStep(x[0], x[2], moveTime)); } //从正下方向左上移动并移动到左上方顶点时 else if (movePoint.getX() <= x[0]) { movePoint.setCalculate(k[0], b[0]); movePoint.setMoveStep(getMoveStep(x[1], x[0], moveTime)); } } //移动圆环 private void move(){ for (MovePoint movePoint : movePoints) { movePoint.setX(movePoint.getX() + movePoint.getMoveStep()); } postInvalidateDelayed(5); }}
xml布局文件:
转载地址:https://blog.csdn.net/zz51233273/article/details/108198230 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!