Android 自定义View利用Path实现变速圆周运动的环绕加载动画
发布日期:2021-06-29 11:46:24 浏览次数:3 分类:技术文章

本文共 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 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:Android 自定义View一个酷炫又无厘头的动画
下一篇:自定义View实现圆环环绕的加载动画

发表评论

最新留言

感谢大佬
[***.8.128.20]2024年04月11日 16时57分45秒