最近做直播项目,需要实现点赞动画,一提起动画就想到了使用View的属性动画,后来想了一下,那么多用户点赞,会导致屏幕上出现很多View,开销太大,一定会很卡,所以看主流主播软件用什么方案解决的。

于是反编译了映客apk,大概看了一下,它的点赞只用了一个SurfaceView,每个心都是实时画到画布上去的,这样效率确实很高,再多的心也不怕了。思路有了,但是自己从头到尾写毕竟麻烦,后来上网查了是否有其他人已经做好了呢?果然有现成的,思路很清晰,很简洁,根据自己的需求改一改就好了。

前面说了一堆,主要想说明有些效果自己虽然没做过,但是可以参考其他成熟产品是怎么做的,这样会少走弯路,试想如果自己只用view属性动画,也实现了,岂不是卡的要死,最后还是要推倒重做的。

先看一下效果:

ZanBean类,每个ZanBean都要负责实时更新自己的位置、透明度等数据

import android.animation.TypeEvaluator;
import android.animation.ValueAnimator;
import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Point;
import android.os.Build;

import java.util.Random;

public class ZanBean {

 /**
  * 心的当前坐标
  */
 public Point point;
 /**
  * 移动动画
  */
 private ValueAnimator moveAnim;
 /**
  * 放大动画
  */
 private ValueAnimator zoomAnim;
 /**
  * 透明度
  */
 public int alpha = 255;//
 /**
  * 心图
  */
 private Bitmap bitmap;
 /**
  * 绘制bitmap的矩阵 用来做缩放和移动的
  */
 private Matrix matrix = new Matrix();
 /**
  * 缩放系数
  */
 private float sf = 0;
 /**
  * 产生随机数
  */
 private Random random;
 public boolean isEnd = false;//是否结束

 public ZanBean(Context context, int resId, ZanView zanView) {
  random = new Random();
  bitmap = BitmapFactory.decodeResource(context.getResources(), resId);
  init(new Point(zanView.getWidth() / 2, zanView.getHeight()- bitmap.getHeight() / 2), new Point((random.nextInt(zanView.getWidth())), 0));
 }


 public ZanBean(Bitmap bitmap, ZanView zanView) {
  random = new Random();
  this.bitmap = bitmap;
  //为了让在起始坐标点时显示完整 需要减去bitmap.getHeight()/2
  init(new Point(zanView.getWidth() / 2, zanView.getHeight() - bitmap.getHeight() / 2), new Point((random.nextInt(zanView.getWidth())), 0));
 }

 @TargetApi(Build.VERSION_CODES.HONEYCOMB)
 private void init(final Point startPoint, Point endPoint) {
  moveAnim = ValueAnimator.ofObject(new BezierEvaluator(new Point(random.nextInt(startPoint.x * 2), Math.abs(endPoint.y - startPoint.y) / 2)), startPoint, endPoint);
  moveAnim.setDuration(1500);
  moveAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
   @Override
   public void onAnimationUpdate(ValueAnimator animation) {
    point = (Point) animation.getAnimatedValue();
    alpha = (int) ((float) point.y / (float) startPoint.y * 255);
   }
  });
  moveAnim.start();
  zoomAnim = ValueAnimator.ofFloat(0, 1f).setDuration(700);
  zoomAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
   @Override
   public void onAnimationUpdate(ValueAnimator animation) {
    Float f = (Float) animation.getAnimatedValue();
    sf = f.floatValue();
   }
  });
  zoomAnim.start();
 }

// public void pause(){
//  if(moveAnim !=null&& moveAnim.isRunning()){
//   moveAnim.pause();
//  }
//  if(zoomAnim !=null&& zoomAnim.isRunning()){
//   zoomAnim.pause();
//  }
// }
//
// public void resume(){
//  if(moveAnim !=null&& moveAnim.isPaused()){
//   moveAnim.resume();
//  }
//  if(zoomAnim !=null&& zoomAnim.isPaused()){
//   zoomAnim.resume();
//  }
// }

 public void stop() {
  if (moveAnim != null) {
   moveAnim.cancel();
   moveAnim = null;
  }
  if (zoomAnim != null) {
   zoomAnim.cancel();
   zoomAnim = null;
  }
 }

 /**
  * 主要绘制函数
  */
 public void draw(Canvas canvas, Paint p) {
  if (bitmap != null && alpha > 0) {
   p.setAlpha(alpha);
   matrix.setScale(sf, sf, bitmap.getWidth() / 2, bitmap.getHeight() / 2);
   matrix.postTranslate(point.x - bitmap.getWidth() / 2, point.y - bitmap.getHeight() / 2);
   canvas.drawBitmap(bitmap, matrix, p);
  } else {
   isEnd = true;
  }
 }

 /**
  * 二次贝塞尔曲线
  */
 @TargetApi(Build.VERSION_CODES.HONEYCOMB)
 private class BezierEvaluator implements TypeEvaluator<Point> {

  private Point centerPoint;

  public BezierEvaluator(Point centerPoint) {
   this.centerPoint = centerPoint;
  }

  @Override
  public Point evaluate(float t, Point startValue, Point endValue) {
   int x = (int) ((1 - t) * (1 - t) * startValue.x + 2 * t * (1 - t) * centerPoint.x + t * t * endValue.x);
   int y = (int) ((1 - t) * (1 - t) * startValue.y + 2 * t * (1 - t) * centerPoint.y + t * t * endValue.y);
   return new Point(x, y);
  }
 }
}

ZanView代码如下:SurfaceView,不断将ZanBean画到自己的画布上。

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.PorterDuff;
import android.util.AttributeSet;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

import java.util.ArrayList;

public class ZanView extends SurfaceView implements SurfaceHolder.Callback {

 private SurfaceHolder surfaceHolder;

 /**
  * 心的个数
  */
 private ArrayList<ZanBean> zanBeen = new ArrayList<>();
 private Paint p;
 /**
  * 负责绘制的工作线程
  */
 private DrawThread drawThread;

 public ZanView(Context context) {
  this(context, null);
 }

 public ZanView(Context context, AttributeSet attrs) {
  this(context, attrs, 0);
 }

 public ZanView(Context context, AttributeSet attrs, int defStyleAttr) {
  super(context, attrs, defStyleAttr);
  this.setZOrderOnTop(true);
  /**设置画布 背景透明*/
  this.getHolder().setFormat(PixelFormat.TRANSLUCENT);
  surfaceHolder = getHolder();
  surfaceHolder.addCallback(this);
  p = new Paint();
  p.setAntiAlias(true);
  drawThread = new DrawThread();
 }

 /**
  * 点赞动作 添加心的函数 控制画面最大心的个数
  */
 public void addZanXin(ZanBean zanBean) {
  zanBeen.add(zanBean);
  if (zanBeen.size() > 40) {
   zanBeen.remove(0);
  }
  start();
 }

 @Override
 public void surfaceCreated(SurfaceHolder holder) {
  if (drawThread == null) {
   drawThread = new DrawThread();
  }
  drawThread.start();
 }

 @Override
 public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

 }

 @Override
 public void surfaceDestroyed(SurfaceHolder holder) {
  if (drawThread != null) {
   drawThread.isRun = false;
   drawThread = null;
  }
 }

 class DrawThread extends Thread {
  boolean isRun = true;

  @Override
  public void run() {
   super.run();
   /**绘制的线程 死循环 不断的跑动*/
   while (isRun) {
    Canvas canvas = null;
    try {
     synchronized (surfaceHolder) {
      canvas = surfaceHolder.lockCanvas();
      /**清除画面*/
      canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
      boolean isEnd = true;

      /**对所有心进行遍历绘制*/
      for (int i = 0; i < zanBeen.size(); i++) {
       isEnd = zanBeen.get(i).isEnd;
       zanBeen.get(i).draw(canvas, p);
      }
      /**这里做一个性能优化的动作,由于线程是死循环的 在没有心需要的绘制的时候会结束线程*/
      if (isEnd) {
       isRun = false;
       drawThread = null;
      }
     }
    } catch (Exception e) {
     e.printStackTrace();
    } finally {
     if (canvas != null) {
      surfaceHolder.unlockCanvasAndPost(canvas);
     }
    }
    try {
     /**用于控制绘制帧率*/
     Thread.sleep(10);
    } catch (InterruptedException e) {
     e.printStackTrace();
    }
   }
  }
 }

 public void stop() {
  if (drawThread != null) {

//   for (int i = 0; i < zanBeen.size(); i++) {
//    zanBeen.get(i).pause();
//   }
   for (int i = 0; i < zanBeen.size(); i++) {
    zanBeen.get(i).stop();
   }

   drawThread.isRun = false;
   drawThread = null;
  }

 }

 public void start() {
  if (drawThread == null) {
//   for (int i = 0; i < zanBeen.size(); i++) {
//    zanBeen.get(i).resume();
//   }
   drawThread = new DrawThread();
   drawThread.start();
  }
 }
}

调用方式:

public class TestActivity extends BaseActivity {
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.test_zan);
  final ZanView zan = (ZanView) findViewById(R.id.zan_view);
  zan.start();
  findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
   @Override
   public void onClick(View v) {
    ZanBean zanBean = new ZanBean(BitmapFactory.decodeResource(getResources(), R.drawable.ic_default_avatar), zan);
    zan.addZanXin(zanBean);
   }
  });
 }
}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

02-06 09:29