Android粒子破碎效果(1)——开源项目ExplosionField代码分析

来源:互联网 发布:上海unity3d培训 编辑:程序博客网 时间:2024/04/29 13:57

使用过MIUI的同学应该遇到过MIUI的app卸载动画,作为多年的米粉,当我尝试去实现这个动画的时候,第一时间就是在网上看有没有类似的效果,果然我找到了这个:

【Android效果集】学习ExplosionField之粒子破碎效果

可这个动画使用起来并不理想,其粒子在爆炸后,其运动方向左右摇摆,当我仔细阅读代码之后,发现其中 advance方法(即动画进行过程中,用于改变粒子参数的方法)如图:

image

可以看到,随着动画的进行,粒子的圆心x坐标,每次都会加一个随机正负的随机数;圆心的y坐标会加一个正随机数;因此粒子的左右移动是不确定的,这并不符合自然规律。

那么什么才是自然规律呢?
- 粒子在x轴上:爆炸的那一刻,就决定了是往左还是往右,之后只能朝着这个方向继续移动。
- 粒子的y轴上:可以看到MIUI的效果,是粒子先向上运动,然后下落。

于是,我又找了开源项目:

ExplosionField

该项目效果如图:

explosionfield.gif

可以看到效果几乎与MIUI的效果相同,但是该项目没有一句注释,且其对粒子的参数进行的大量数学计算,因此我费了好大劲,终于像解方程一样,理清了开发者的思路。下面先分析该项目代码:

代码分析

使用方法:

实例化:

mExplosionField = ExplosionField.attach2Window(this);

给View添加爆炸效果:

mExplosionField.explode(view);

分析

该项目总共有四个类:
- ExplosionAnimator,继承自ValueAnimator,负责产生具有动画规律的数字,还有负责生成粒子、绘制粒子的方法。
- ExplosionField,继承自View,用于将动画生成的粒子绘制在界面上,包含执行动画、将自身添加到ContentView中的方法。
- Particle,粒子的实体类,同时也是ExplosionAnimator的内部类,包含粒子绘制的参数,以及最重要的粒子随着动画进程,改变自身参数的advance方法。
- Utils,工具类,包含dp转px、根据View创建Bitmap方法。

其思路流程不在赘述,了解过自定义View和属性动画的同学应该都能看的懂,这里贴两个思维导图(原谅我做的图太丑了 o(╥﹏╥)o):

ExplosionField

ExplosionAnimator

我们重点来讲讲粒子的生成方法和变化方法:

首先是粒子的各项参数(加注释版):

 private class Particle {        float alpha;        // 透明度        int color;          // 颜色        float cx;          // 粒子圆心 x        float cy;          // 粒子圆心 y        float radius;      // 粒子半径        float baseCx;      // 粒子圆心 x的基础值,后续cx的取值就由baseCx为基准        float baseCy;      // 粒子圆心 y的基础值,后续cy的取值就由baseCy为基准        float baseRadius;  // 粒子的基础半径,后续radius的取值就由baseRadius为基准        float top;         // 负责cy变化的因素        float bottom;      // 负责cx变化的因素        float mag;         // 负责cy变化的因素(因为是基于上面两个值计算而来,通过修改计算公式可以修改粒子变化幅度        float neg;         // 同上        float life;        // 决定了粒子在动画开始多久之后,开始显示        float overflow;    // 决定了粒子动画结束前多少时间开始隐藏        }

当我刚开始看到一大堆bottom、top、mag等参数时,一脸懵逼,后来通过分析其粒子生成方法和粒子变化方法,才推测出这些参数的用处。

然后,我们来看看粒子生成方法 generateParticle(int color, Random random):

private Particle generateParticle(int color, Random random) {        Particle particle = new Particle();        particle.color = color;        particle.radius = V;        if (random.nextFloat() < 0.2f) {            particle.baseRadius = V + ((X - V) * random.nextFloat());        } else {            particle.baseRadius = W + ((V - W) * random.nextFloat());        }        float nextFloat = random.nextFloat();        particle.top = mBound.height() * ((0.18f * random.nextFloat()) + 0.2f);        particle.top = nextFloat < 0.2f ? particle.top : particle.top + ((particle.top * 0.2f) * random.nextFloat());        particle.bottom = (mBound.height() * (random.nextFloat() - 0.5f)) * 1.8f;        float f = nextFloat < 0.2f ? particle.bottom : nextFloat < 0.8f ? particle.bottom * 0.6f : particle.bottom * 0.3f;        particle.bottom = f;        particle.mag = 4.0f * particle.top / particle.bottom;        particle.neg = (-particle.mag) / particle.bottom;        f = mBound.centerX() + (Y * (random.nextFloat() - 0.5f));        particle.baseCx = f;        particle.cx = f;        f = mBound.centerY() + (Y * (random.nextFloat() - 0.5f));        particle.baseCy = f;        particle.cy = f;        particle.life = END_VALUE / 10 * random.nextFloat();        particle.overflow = 0.4f * random.nextFloat();        particle.alpha = 1f;        return particle;    }

恩…配合下面的思维导图食用更佳:

生成粒子

红色参数:粒子在生成时,就固定下来的参数,随着动画进程而不改变的值。

请注意绿色部分的正负取值

总之,上面的一系列计算,都是以为了让每一个粒子都有不一样的参数,以及后续在动画进程中不一样的运动轨迹。值得注意的是,上面的top和bottom在计算中,使用了同一个变量–nextFloat,因此bottom与top的规律在于:top越大,bottom的相对值就越小,反之亦然。表现在运动轨迹上,就是粒子横向运动的越远,竖直方向运动的就越近(相对来说).这里就不得不佩服开发者的细心了,这种规律都能考虑到 Orz。

我们继续来看粒子的变化方法 advance(float factor):

public void advance(float factor) {            float f = 0f;            float normalization = factor / END_VALUE;            if (normalization < life || normalization > 1f - overflow) {                alpha = 0f;                return;            }            normalization = (normalization - life) / (1f - life - overflow);            float f2 = normalization * END_VALUE;            if (normalization >= 0.7f) {                f = (normalization - 0.7f) / 0.3f;            }            alpha = 1f - f;            f = bottom * f2;            cx = baseCx + f;            cy = (float) (baseCy - this.neg * Math.pow(f, 2.0)) - f * mag;            radius = V + (baseRadius - V) * f2;        }

添加注释后:

 public void advance(float factor) {            float f = 0f;            // normal= 粒子在可显示的范围内,动画进行到了几分之几            float normalization = factor / END_VALUE;            // 动画开始前和结束前的一段时间内是透明(不进行绘制)的。            if (normalization < life || normalization > 1f - overflow) {                alpha = 0f;                return;            }            // normal= 粒子在可显示的范围内,动画实际进行到了几分之几            normalization = (normalization - life) / (1f - life - overflow);            // f2= 实际进行到的数值            float f2 = normalization * END_VALUE;            // 动画实际进程超过7/10,则开始逐渐透明。            if (normalization >= 0.7f) {                f = (normalization - 0.7f) / 0.3f;            }            alpha = 1f - f;            // cx 在baseCx的基础上增长f2个bottom(bottom可能是负数,这里就表现了粒子是往左移动还是往右移动            f = bottom * f2;            cx = baseCx + f;            // 可以把这个计算视为一个方程,然后,我们一步步简化:            // 已知:mag=4*top/bottom; neg=-mag / bottom; f=bottom*f2;            // 则:cy = (float) (baseCy - this.neg * Math.pow(f, 2.0)) - f * mag;            // 则:cy= (float)(baseCy-(-(4*top/bottom)/bottom)*bottom*bottom*f2*f2)-bottom*f2*4*top/bottom;            // 则:cy= baseCy+(4*top*(f2*(f2-1)));            // 那么,我们就可以的出cy的变化曲线函数: y=baseCy+4*top*(x*(x-1),再简化: y=j+k*(x*(x-1),j、k都是常数,x为 0~1.4;            // 那么,粒子的变化因素只有一个x*(x-1)            cy = (float) (baseCy - this.neg * Math.pow(f, 2.0)) - f * mag;            // 可以简化为:y=k*x,k是常数,x为 0~1.4;因此radius是不断增长的。            radius = V + (baseRadius - V) * f2;        }

注释里基本都写的很清楚了,关键是Cy的取值,我们可以看到,cy的变化因素为y=x*(x-1),那么,我们在函数曲线中看一下:

Cy的变化曲线

可以看到,y是先下降再上升,且当x小于1时,y是负值。动画的结束值是1.4,那么当动画进程在0.5之前时,baseCy是加一个不断变小的负值,表现到View坐标系中,则是粒子向上运动。之后,便是baseCy加一个不断增加的值,表现为粒子向下运动。

我们可以测试一下,先打印第一个粒子的baseCy和top值:

if(ttt==0){    tt=bottom;    Log.d("ExplosionAnimator","baseCy="+baseCy+";top="+top);    } else{        if(ttt==bottom){            Log.d("ExplosionAnimator","baseCy="+baseCy+";top="+top);            }    }

日志:

D/ExplosionAnimator: baseCy=299.99106;top=147.68047

我们将其应用到函数曲线中:

Cy变化曲线2
因为View坐标系y轴是向下的,与数学坐标系相反,我们可以修改一下方程,达到类似View坐标系的效果:

Cy变化曲线3

总结

代码分析的差不多了,我们基本上可以看出开发者的思路:粒子的生成的时候,通过大量的随机运算,给粒子赋予尽量区别于其他粒子的参数。

其中:
- cx,初始位置为view中心点左右随机偏移一定值,根据bottom值,又可以分为向左运动(bottom为负数)的粒子、向右运动(bottom为正数)的粒子;
- cy,初始位置为view中心点上下随机偏移一定值,粒子在y轴上沿y=x*(x-1)曲线运动;
- radius,初始为大半径(1/5概率)、小半径(4/5概率),之后开始逐渐变大;
- alpha,初始为1,动画实际进程超过7/10时,开始逐渐变透明;
- 每一个粒子都有一个经过随机运算得出的life和overflow,取值差不多为0.0x~0.1x之间,用于控制粒子在开始的前多少时间、动画结束前的多少时间,是不显示的,这样就有了一个错落出现、消失的层次感。

在这里,再次为开发者献上自己的膝盖~~~

一般当我们读懂了别人的代码后,自己去实现的时候,总是会遇到这样那样的问题,因此,我们这里可以尝试自己去顺着大牛的思路来实现这个效果,同时,加入自己的想法,进行部分功能的改进。这些东西就留给下一篇博客了!

Android粒子破碎效果(2)——实现多种破碎效果之ParticleSmasher

阅读全文
0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 驾驶证过期5个月怎么办 驾驶证过期3年了怎么办 c驾驶证换证过期怎么办 驾驶证过了两年怎么办 驾驶证过期8个月怎么办 学驾照快过期了怎么办 朋友开我车撞了怎么办 b2驾照扣分没审怎么办 驾驶证脱审2个月怎么办 驾驶证脱审1月怎么办 驾照过期了没换怎么办 驾照过期1年多了怎么办 考驾照科目一考不过怎么办 我考驾照过期了怎么办 驾照过了审验期怎么办 驾照过期3年了怎么办 如果学车过期了怎么办 行驶证过期了3年怎么办 行驶证过期没审怎么办 行驶证过期两年怎么办 驾驶证过期7年了怎么办 换驾照过一个月怎么办? 行驶证正本掉了怎么办 车的行驶证丢了怎么办 三星s6 屏幕坏了怎么办 手机摔成黑屏了怎么办 三星屏幕漏液了怎么办 三星s8屏幕漏液怎么办 屏幕紫色漏液了怎么办 华为手机屏碎了怎么办 小米2s按键失灵怎么办 魅蓝屏幕摔花了怎么办 小米手机屏碎了怎么办 厦门医保卡坏了怎么办 医保卡丢了北京怎么办 重庆社保卡丢了怎么办 沈阳医保卡丢了怎么办 小孩医保卡丢了怎么办 少儿医保卡丢了怎么办 孩子医保卡丢了怎么办 医保卡存折丢了怎么办