炸裂的点赞动画
前言
之前偶然间看到某APP点赞有个炸裂的效果,觉得有点意思,就尝试了下,轻微还原,效果图如下
封装粒子
从动画效果中我们可以看到,当动画开始的时候,会有一组粒子从四面八方散射出去,然后逐渐消失,于是可以定义一个粒子类包含以下属性
public class Particle {
public float x, y;
public float startXV;
public float startYV;
public float angle;
public float alpha;
public Bitmap bitmap;
public int width, height;
}
x,y是粒子的位置信息
startXV,startYV是X方向和Y方向的速度
angle是发散出去的角度
alpha是粒子的透明度
bitmap, width, height即粒子图片信息 我们在构造函数中初始化这些信息,给定一些默认值
public Particle(Bitmap originalBitmap) {
alpha = 1;
float scale = (float) Math.random() * 0.3f + 0.7f;
width = (int) (originalBitmap.getWidth() * scale);
height = (int) (originalBitmap.getHeight() * scale);
bitmap = Bitmap.createScaledBitmap(originalBitmap, width, height, true);
startXV = new Random().nextInt(150) * (new Random().nextBoolean() ? 1 : -1);
startYV = new Random().nextInt(170) * (new Random().nextBoolean() ? 1 : -1);
int i = new Random().nextInt(360);
angle = (float) (i * Math.PI / 180);
float rotate = (float) Math.random() * 180 - 90;
Matrix matrix = new Matrix();
matrix.setRotate(rotate);
bitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, false);
originalBitmap.recycle();
}
仔细看效果动画,会发现同一个图片每次出来的旋转角度会有不同,于是,在创建bitmap的时候我们随机旋转下图片。
绘制粒子
有了粒子之后,我们需要将其绘制在View上,定义一个ParticleView,重写onDraw()方法,完成绘制
public class ParticleView extends View {
Paint paint;
List<Particle> particles = new ArrayList<>();
//.....省略构造函数
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
for (Particle particle : particles) {
paint.setAlpha((int) (particle.alpha * 255));
canvas.drawBitmap(particle.bitmap, particle.x - particle.width / 2, particle.y - particle.height / 2, paint);
}
}
public void setParticles(List<Particle> particles) {
this.particles = particles;
}
}
管理粒子
绘制的时候我们发现需要不断改变粒子的x,y值,才能使它动起来,所以我们需要一个ValueAnimator,然后通过监听动画执行情况,不断绘制粒子。
private void startAnimator(View emiter) {
ValueAnimator valueAnimator = ObjectAnimator.ofInt(0, 1).setDuration(1000);
valueAnimator.addUpdateListener(animation -> {
for (Particle particle : particles) {
particle.alpha = 1 - animation.getAnimatedFraction();
float time = animation.getAnimatedFraction();
time *= 10;
particle.x = startX - (float) (particle.startXV * time * Math.cos(particle.angle));
particle.y = startY - (float) (particle.startYV * time * Math.sin(particle.angle) - 9.8 * time * time / 2);
}
particleView.invalidate();
});
valueAnimator.start();
}
由于我们的点赞按钮经常出现在RecyclerView的item里面,而点赞动画又是全屏的,所以不可能写在item的xml文件里面,而且我们需要做到0侵入,在不改变原来的逻辑下添加动画效果。
我们可以通过activity.findViewById(android.R.id.content)
获取FrameLayout
然后向他添加子View
public ParticleManager(Activity activity, int[] drawableIds) {
particles = new ArrayList<>();
for (int drawableId : drawableIds) {
particles.add(new Particle(BitmapFactory.decodeResource(activity.getResources(), drawableId)));
}
topView = activity.findViewById(android.R.id.content);
topView.getLocationInWindow(parentLocation);
}
首先我们通过构造函数传入当前Activity
以及我们需要的图片资源,然后将图片资源都解析成Particle
对象,保存在particles
中,然后获取topView
以及他的位置信息。
然后需要知道动画从什么位置开始,传入一个view作为锚点
public void start(View emiter) {
int[] location = new int[2];
emiter.getLocationInWindow(location);
startX = location[0] + emiter.getWidth() / 2 - parentLocation[0];
startY = location[1] - parentLocation[1];
particleView = new ParticleView(topView.getContext());
topView.addView(particleView);
particleView.setParticles(particles);
startAnimator(emiter);
}
通过传入一个emiter
,计算出起始位置信息并初始化particleView中的粒子信息,最后开启动画。
使用
val ids = ArrayList<Int>()
for (index in 1..18) {
val id = resources.getIdentifier("img_like_$index", "mipmap", packageName);
ids.add(id)
}
collectImage.setOnClickListener {
ParticleManager(this, ids.toIntArray())
.start(collectImage)
}
运行之后会发现基本和效果图一致,但是其实有个潜在的问题,我们只是向topView添加了view,并没有移除,虽然界面上看不到,其实只是因为我们的粒子在最后透明度都是0了,将粒子透明度最小值设置为0.1后运行会发现,动画结束之后粒子没有消失,会越堆积越多,所以我们还需要移除view。
valueAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation, boolean isReverse) {
}
@Override
public void onAnimationEnd(Animator animation) {
topView.removeView(particleView);
topView.postInvalidate();
}
@Override
public void onAnimationCancel(Animator animation) {
topView.removeView(particleView);
topView.postInvalidate();
}
});
移除的时机放在动画执行完成,所以继续使用之前的valueAnimator,监听他的完成事件,移除view,当然,如果动画取消了也需要移除。
作者:晚来天欲雪_
来源:juejin.cn/post/7086471790502871054