Android实现粒子中心扩散动画效果
作者:时光少年
前言
粒子动画效果相比其他动画来说是非常复杂了的,主要涉及三个方面,粒子初始化、粒子位移、粒子回收等问题,其中特别是粒子位移是最复杂的,涉及到的数学逻辑非常多,主要是各种三角函数、物理学公式等。
本篇将实现两种动画效果,代码基本相同,只是旋转速度不一样,因此,本篇其实可以看作一篇模板文章,具体效果可以通过调节参数生成各种动画
第一种动画
第二种动画
实现步骤
其实和以往的粒子效果一样,粒子需要被管理起来,因此我们需要有容器、也需要粒子对象
粒子对象定义
下面是创建粒子对象的逻辑,基本属性在注释中了
static class Circle { int maxLength; //最大运行距离 float speed; //外扩速度 float rotate; // 角速度 private float degree; //起始角度 private int y; //y坐标 private int x; //x坐标 private int color; //颜色 private float radius; //小圆半径 private float drawRadius; //绘制时的小圆半径 public Circle(int color, int maxLength, float radius, float degree) { this.color = color; this.radius = radius; this.maxLength = maxLength; this.degree = degree; this.x = (int) (radius * Math.cos(degree)); this.y = (int) (radius * Math.sin(degree)); this.rotate = 0.35f; //触角效果 this.speed = 0.2f; } }
粒子更新
在任何动画中,粒子运动必须具备时间属性,任何符合物理学的位移运动,速度和时间的关系是位移计算的方法。下面,我们继续给Circle类添加更新方法。
这里一个重要的知识点是
- Math.hypot(x, y) :平方根计算
- Math.atan2(y, x): 斜率计算,注意,此角度具备方向
public boolean update(long timeline) { float length = (float) Math.hypot(x, y); //计算当前移动的距离(距离中心点) float center = length + this.speed * timeline; //计算即将到达的距离 float ratio = center / maxLength; //计算与最远距离的比值 this.drawRadius = (1f - ratio) * radius; //距离越远,圆的半径越小 double degree = Math.atan2(y, x) + rotate; //即将旋转的角度 this.x = (int) (center * Math.cos(degree)); //新的x this.y = (int) (center * Math.sin(degree)); //新的y if (drawRadius <= 0) { return false; //如果半径为0时,意味着圆看不见了,因此要坐下标记 } return true; }
粒子绘制方法
绘制自身其实很简单,只需要简单的调用Canvas相关逻辑即可
public void draw(Canvas canvas, TextPaint paint) { paint.setColor(color); canvas.drawCircle(x, y, drawRadius, paint); }
粒子回收
为了减少内存申请频率,我们对跑出边界的粒子进行重置
public void reset() { this.x = (int) (radius * Math.cos(degree)); this.y = (int) (radius * Math.sin(degree)); }
View逻辑
以上是完整的粒子对象逻辑,接下来我们实现一个View,用来管理和绘制粒子。
int maxCircleRadius = 20; //粒子初始半径 List<Circle> circleList = new ArrayList<>(); //容器 int maxCircleNum = 300; //最大数量
绘制逻辑
首先是初始化,我们这里设置了3种粒子,因此间隔角度是120度,而我们每次增加三种,防止出现混乱的问题。
final float rotateDegree = (float) Math.toRadians(120f); //间隔角度 if (circleList.size() < maxCircleNum) { //每次增加三种 circleList.add(new Circle(Color.RED, (int) maxRadius, maxCircleRadius, 0 * rotateDegree)); circleList.add(new Circle(Color.GREEN, (int) maxRadius, maxCircleRadius, 1 * rotateDegree)); circleList.add(new Circle(Color.CYAN, (int) maxRadius, maxCircleRadius, 2 * rotateDegree)); }
下面是每个粒子的绘制逻辑
for (int i = 0; i < circleList.size(); i++) { Circle circle = circleList.get(i); circle.draw(canvas, mPaint); //绘制方法 }
更新粒子
下面有个重要的逻辑,其实前面也提到过,就是重置跑出边界的粒子
for (int i = 0; i < circleList.size(); i++) { Circle circle = circleList.get(i); if(!circle.update(16)){ circle.reset(); //如果不能更新,则进行重置 } } postInvalidate(); //刷新绘制逻辑
以上就是整体核心逻辑
效果调节
我们开头的两种效果其实是同一个View实现的,这其中一个重要的点就是速度调整,文章开头是调整出的两种效果,当然染还可以调整出其他效果 第一种
this.rotate = 0.2f; this.speed = 0.2f; //外扩效果
第二种
this.rotate = 0.35f; //触角效果 this.speed = 0.2f;
第三种
this.rotate = 0.8f; this.speed = 0.1f;
当然,还有更多,篇幅原因就不深入了。
总结
本篇到这里就结束了,其实我们的核心代码并不多,但是简单的逻辑就能衍生出很多动画效果。其实,学习粒子动画是非常有意思的事,很多时候,你在实现某些效果的途中,就能突然开发出一种新的动画效果。
本篇代码
下面是本篇内容的完整逻辑,基本就在100行左右。
public class CircleParticleView extends View { private TextPaint mPaint; private DisplayMetrics mDM; public CircleParticleView(Context context) { this(context, null); } public CircleParticleView(Context context, AttributeSet attrs) { super(context, attrs); mDM = getResources().getDisplayMetrics(); initPaint(); } private void initPaint() { //否则提供给外部纹理绘制 mPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); mPaint.setAntiAlias(true); mPaint.setStyle(Paint.Style.FILL_AND_STROKE); mPaint.setStrokeCap(Paint.Cap.ROUND); PaintCompat.setBlendMode(mPaint, BlendModeCompat.PLUS); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int widthMode = MeasureSpec.getMode(widthMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec); if (widthMode != MeasureSpec.EXACTLY) { widthSize = mDM.widthPixels / 2; } int heightMode = MeasureSpec.getMode(heightMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); if (heightMode != MeasureSpec.EXACTLY) { heightSize = widthSize / 2; } setMeasuredDimension(widthSize, heightSize); } int maxCircleRadius = 20; List<Circle> circleList = new ArrayList<>(); int maxCircleNum = 300; long time = 0; @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); int width = getWidth(); int height = getHeight(); float maxRadius = Math.min(width, height) / 2f; int save = canvas.save(); canvas.translate(width / 2f, height / 2f); final float rotateDegree = (float) Math.toRadians(120f); if (circleList.size() < maxCircleNum) { circleList.add(new Circle(Color.RED, (int) maxRadius, maxCircleRadius, 0 * rotateDegree)); circleList.add(new Circle(Color.GREEN, (int) maxRadius, maxCircleRadius, 1 * rotateDegree)); circleList.add(new Circle(Color.CYAN, (int) maxRadius, maxCircleRadius, 2 * rotateDegree)); } mPaint.setStyle(Paint.Style.FILL); for (int i = 0; i < circleList.size(); i++) { Circle circle = circleList.get(i); circle.draw(canvas, mPaint); } canvas.restoreToCount(save); for (int i = 0; i < circleList.size(); i++) { Circle circle = circleList.get(i); if (!circle.update(16)) { circle.reset(); } } postInvalidate(); time += 16; } static class Circle { int maxLength; //最大运行距离 float speed; //外扩速度 float rotate; // 角速度 private float degree; //起始角度 private int y; //y坐标 private int x; //x坐标 private int color; //颜色 private float radius; //小圆半径 private float drawRadius; //绘制时的小圆半径 public Circle(int color, int maxLength, float radius, float degree) { this.color = color; this.radius = radius; this.maxLength = maxLength; this.degree = degree; this.x = (int) (radius * Math.cos(degree)); this.y = (int) (radius * Math.sin(degree)); this.rotate = 0.35f; //触角效果 this.speed = 0.2f; } public boolean update(long timeline) { float length = (float) Math.hypot(x, y); float center = length + this.speed * timeline; //距离增加 float ratio = center / maxLength; this.drawRadius = (1f - ratio) * radius; double degree = Math.atan2(y, x) + rotate; //角度增加 this.x = (int) (center * Math.cos(degree)); this.y = (int) (center * Math.sin(degree)); if (drawRadius <= 0) { return false; } return true; } public void draw(Canvas canvas, TextPaint paint) { paint.setColor(color); canvas.drawCircle(x, y, drawRadius, paint); } public void reset() { this.x = (int) (radius * Math.cos(degree)); this.y = (int) (radius * Math.sin(degree)); } } }
以上就是Android实现粒子中心扩散动画效果的详细内容,更多关于Android粒子中心扩散的资料请关注脚本之家其它相关文章!