注册

炸裂的点赞动画

前言

之前偶然间看到某APP点赞有个炸裂的效果,觉得有点意思,就尝试了下,轻微还原,效果图如下

d8969e36318442fcbfe0be92fb0feea3~tplv-k3u1fbpfcp-zoom-in-crop-mark:4536:0:0:0.image?

封装粒子

从动画效果中我们可以看到,当动画开始的时候,会有一组粒子从四面八方散射出去,然后逐渐消失,于是可以定义一个粒子类包含以下属性

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

0 个评论

要回复文章请先登录注册