本文共 6775 字,大约阅读时间需要 22 分钟。
先看效果图:
我先来说下思路:首先我们需要将小圆球添加到Path中(Path.addCircle()),然后我们利用canvas.rotate旋转整个图层,从而实现小球绕图层中心做圆周运动。又因为每个圆球运动轨迹不同,所以我们需要五个Path对象来分别存放上图中的五个圆球。最后利用加速度公式来模拟小球加速和减速运动
一、圆球的绘制和匀速圆周运动
下面我们来做图分析:
由上图可知,以我们控件的宽度为准,假设控件的宽度为w,可以设计最大圆的半径为w/8,接下来从大到小其余四个圆的半径分别为w/8-i*w/8/circleNum(其中i为第i+1个圆)
每个圆的半径和坐标我们都知道了,接着就把他们添加到Path中,代码如下:
private int circleNum=5; //圆的数量private Paint circlePaint;private RadialGradient radialGradient;private Path[] circlePaths=new Path[circleNum]; //用来存储圆的路径public MyFiveCircleView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); init();}private void init(){ circlePaint=new Paint(); circlePaint.setAntiAlias(true);}@Overrideprotected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); if(radialGradient==null){ //设置圆的颜色为渐变色 radialGradient=new RadialGradient(getWidth()>>1,getWidth()>>1,getWidth()*2/3, new int[]{Color.parseColor("#E0EEE0"),Color.parseColor("#00CD66")},null, Shader.TileMode.CLAMP); circlePaint.setShader(radialGradient); } int width=(getWidth()>>3)/circleNum; for(int i=0;i>1, getWidth()>>3, (getWidth()>>3) - (i * width), Path.Direction.CW); }}
在onDraw中画出所有路径,并让每个小球做圆周运动:
private double[] moveAngles=new double[circleNum]; //圆移动的角度@Overrideprotected void onDraw(Canvas canvas) { super.onDraw(canvas); for(int i=0;i>1,getWidth()>>1); canvas.drawPath(circlePaths[i],circlePaint); canvas.restore(); } changeMoveAngles(); postInvalidateDelayed(5);}//改变小球运动的角度private void changeMoveAngles(){ for(int i=0;i =360){ moveAngles[i]=360; //如果最后一个球移动了360度,就开始重新移动 if(i==circlePaths.length-1)restartMove(); } moveAngles[i]+=1; }}//圆球重新移动private void restartMove(){ for(int i=0;i
moveAngles[i-1]<50-(i<<1):该判断语句是用来保证每个圆之间存在一定的距离
好了,到这里为止,一个匀速圆周运动的加载动画已经完成,效果如下:
显然,这种效果看上去就比较机械死板。我们需要进一步模拟每个小球加减速运动,让它更加的丝滑流畅
二、圆球的变速运动
那么怎么做呢,大家可能会想到可以用属性动画来实现。不过在这里,我直接通过计算来实现它的加减速。大家应该记得加速度中的运动距离公式:
- x=v0*t+(a*t^2)/2
我们给定一个加速度a,t随着每次的绘制进行累加。圆球开始运动时,处于加速状态,加速度a设为正值。当圆球运动过半(180度)时,处于减速状态,加速度a设为负值,并重新开始累加时间t。假设加速时运动的角度为θ1,减速时运动的角度为θ2,那么圆球一次圆周运动移动的角度θ=360=θ1+θ2
有了理论基础,下面开始设计算法:
根据距离公式得到计算距离的方法:
//speed:初速度;ac:加速度;moveTime:运动时间private double getNextAngle(double speed,float ac,float moveTime){ //x = v0*t+at/2 return speed*moveTime+(ac*moveTime*moveTime)/2;}
因为加速和减速的时间是独立的,需要分开累加,并且每个圆球的运动时间也不一致,所以加速和减速时间需要用数组的形式来存储:
private float[] accTimes=new float[circleNum]; //加速移动的时间private float[] redTimes=new float[circleNum]; //减速移动的时间
因为每个小球刚过半时,运动的角度并非正好为180度。为确保精度,每个圆球运动过半时所到的角度也需要用数组来存放:
private double[] halfAngle=new double[circleNum]; //圆球运动过半时的角度
我们重新设计changeMoveAngles()方法:
private float perTime=0.06f; //每次运动增加的时间private float ac=9.8f; //加速度//改变小球运动的角度private void changeMoveAngles(){ for(int i=0;i=360){ moveAngles[i]=360; //如果最后一个球移动了360度,就开始重新移动 if(i==circlePaths.length-1)restartMove(); } else{ //圆没转过一半,加速 if(moveAngles[i]<=180){ moveAngles[i]=getNextAngle(0,ac,accTimes[i]); accTimes[i]+=perTime; if(moveAngles[i]>180){ redTimes[i]+=perTime; halfAngle[i]=moveAngles[i]; } } //圆转过一半,减速 else{ moveAngles[i]=halfAngle[i]+getNextAngle(ac*accTimes[i],-ac,redTimes[i]); redTimes[i]+=perTime; } } }}//圆球重新移动private void restartMove(){ for(int i=0;i
moveAngles[i-1]<12-(i<<1):因为每个圆一开始做加速运动,与前一个圆的间隔会变大,判断时要减少小于号右侧的数值
之前我们分析过,θ=θ1+θ2=360,halfAngle即为θ1
当第一次判断到圆球运动过半时,需要先累加上一次时间(redTimes[i]+=perTime),否则下次计算t=0,带入公式x为0,即会有一次停顿
到这里,一个变速圆周运动的圆周运动加载动画就完成了。
下面完整的自定义View代码:
import android.content.Context;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.graphics.Path;import android.graphics.RadialGradient;import android.graphics.Shader;import android.util.AttributeSet;import android.view.View;import androidx.annotation.Nullable;public class MyFiveCircleView extends View { private Paint circlePaint; private RadialGradient radialGradient; private int circleNum=5; //圆的数量 private double[] moveAngles=new double[circleNum]; //圆移动的角度 private double[] halfAngle=new double[circleNum]; //圆球运动过半时的角度 private float[] accTimes=new float[circleNum]; //加速移动的时间 private float[] redTimes=new float[circleNum]; //减速移动的时间 private Path[] circlePaths=new Path[circleNum]; //用来存储圆的路径 private float perTime=0.06f; //每次运动增加的时间 private float ac=9.8f; //加速度 public MyFiveCircleView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); init(); } private void init(){ circlePaint=new Paint(); circlePaint.setAntiAlias(true); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); int width=(getWidth()>>3)/circleNum; if(radialGradient==null){ //设置圆的颜色为渐变色 radialGradient=new RadialGradient(getWidth()>>1,getWidth()>>1,getWidth()*2/3, new int[]{Color.parseColor("#E0EEE0"),Color.parseColor("#00CD66")},null, Shader.TileMode.CLAMP); circlePaint.setShader(radialGradient); } for(int i=0;i>1, getWidth()>>3, (getWidth()>>3) - (i * width), Path.Direction.CW); } } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); for(int i=0;i >1,getWidth()>>1); canvas.drawPath(circlePaths[i],circlePaint); canvas.restore(); } changeMoveAngles(); postInvalidateDelayed(5); } //改变小球运动的角度 private void changeMoveAngles(){ for(int i=0;i =360){ moveAngles[i]=360; //如果最后一个球移动了360度,就开始重新移动 if(i==circlePaths.length-1)restartMove(); } else{ //圆没转过一半,加速 if(moveAngles[i]<=180){ moveAngles[i]=getNextAngle(0,ac,accTimes[i]); accTimes[i]+=perTime; if(moveAngles[i]>180){ redTimes[i]+=perTime; halfAngle[i]=moveAngles[i]; } } //圆转过一半,减速 else{ moveAngles[i]=halfAngle[i]+getNextAngle(ac*accTimes[i],-ac,redTimes[i]); redTimes[i]+=perTime; } } } } //圆球重新移动 private void restartMove(){ for(int i=0;i
xml布局文件:
转载地址:https://blog.csdn.net/zz51233273/article/details/108252851 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!