Android使用SurfaceView实现飘赞动画

最近做直播项目,需要实现点赞动画,一提起动画就想到了使用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);
   }
  });
 }
}

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

(0)

相关推荐

  • 利用SurfaceView实现下雨与下雪动画效果详解(Kotlin语法)

    前言 最近打算做一波东西巩固一下自己近期所学所得.话不多说,先看一下最终完成的效果图: 下雨.gif 这里比较懒--第二个图片中还是降雨--不过这不是关键点-- 下雪.gif 录制的mp4,转成了gif.第一个gif设置了帧率,所以看起来可能掉帧比较严重,但是实际上并不会,因为这里我也注意了1s要绘制60帧的问题.阅读本文需要一些基本的View知识和会一些基础Kotlin语法.说实话,就知识点来说,跟Kotlin是没多大关系的,只要懂基本的语法就可以了. 理清思路 在动手前先要理一下思路,从以下

  • Android利用SurfaceView实现下雨的天气动画效果

    首先是最终实现的效果图: 先分析一下雨滴的实现: 每个雨滴其实就是一条线,通过 canvas.drawLine() 绘制 线(雨滴)的长度.宽度.下落速度.透明度以及位置都是在一定范围内随机生成 每 draw 一次然后改变雨滴的位置然后重绘即可实现雨滴的下落效果 分析完了,那么可以直接写一个类直接继承 View ,然后重写 onDraw() 吗?可以看到效果图中的雨滴的下落速度很快,那么意味着每一帧都要调用 onDraw() 一次使其重新绘制一次,假如你的 onDraw() 方法里面的渲染代码稍

  • Android仿微信清理内存图表动画(解决surfaceView屏幕闪烁问题)demo实例详解

    最近接了一个项目其中有功能要实现一个清理内存,要求和微信的效果一样.于是想到用surfaceView而不是继承view.下面小编给大家解析下实现思路. surfaceView是为了解决频繁绘制动画产生了闪烁,而采用了双缓冲机制,即A.B两个缓冲轮流显示在画布上,同时,使用不当,同样容易产生闪烁,这是由于A.B中有一个缓冲没有改变. 在我写这个view的时候就遇到了这个问题,研究了好久终于解决. 首先说一下思路: 微信清理缓存的动画是: 一个圆环不停的转动,同时中间有文字显示-->加载完成后,出现

  • Android App中使用SurfaceView制作多线程动画的实例讲解

    1. SurfaceView的定义 通常情况程序的View和用户响应都是在同一个线程中处理的,这也是为什么处理长时间事件(例如访问网络)需要放到另外的线程中去(防止阻塞当前UI线程的操作和绘制).但是在其他线程中却不能修改UI元素,例如用后台线程更新自定义View(调用View的在自定义View中的onDraw函数)是不允许的. 如果需要在另外的线程绘制界面.需要迅速的更新界面或则渲染UI界面需要较长的时间,这种情况就要使用SurfaceView了.SurfaceView中包含一个Surface

  • 如何使用SurfaceView实现鱼儿游动动画

    本文实例为大家分享了使用SurfaceView实现动画的具体代码,供大家参考,具体内容如下 与自定义view绘图进行对比: 1.view绘图没有双缓冲机制,而surfaceview有 2.view绘图更新时,要全部更新整张图片,而surfaceview可以更新部分区域 3.新线程无法直接更新view绘图,需要handler配合. 鱼儿游动的动画: <?xml version="1.0" encoding="utf-8"?> <LinearLayou

  • Android使用SurfaceView实现飘赞动画

    最近做直播项目,需要实现点赞动画,一提起动画就想到了使用View的属性动画,后来想了一下,那么多用户点赞,会导致屏幕上出现很多View,开销太大,一定会很卡,所以看主流主播软件用什么方案解决的. 于是反编译了映客apk,大概看了一下,它的点赞只用了一个SurfaceView,每个心都是实时画到画布上去的,这样效率确实很高,再多的心也不怕了.思路有了,但是自己从头到尾写毕竟麻烦,后来上网查了是否有其他人已经做好了呢?果然有现成的,思路很清晰,很简洁,根据自己的需求改一改就好了. 前面说了一堆,主要

  • android实现直播点赞飘心动画效果

    前段时间在写直播的时候,需要观众在看直播的时候点赞的效果,在此参照了腾讯大神写的点赞(飘心动画效果).下面是效果图: 1.自定义飘心动画的属性 在attrs.xml 中增加自定义的属性 <!-- 飘心动画自定义的属性 --> <declare-styleable name="HeartLayout"> <attr name="initX" format="dimension"/> <attr name=&

  • Android控件实现直播App特效之点赞飘心动画

    现在市面上直播类的应用可以说是一抓一大把,随随便便就以什么主题来开发个直播App,说白了就想在这领域分杯羹.在使用这些应用过程中其实不难发现,在所有的直播界面,少不了的就是各种打赏.各种点赞.今天自己就针对点赞功能敲了一下,代码不多,主要是涉及到动画运动轨迹运算,这里需借助 贝塞尔曲线 相关知识,我使用三阶贝塞尔曲线来实现轨迹动画. 运行效果 一.具体实现流程 仔细分析整个点赞过程可以发现,首先是"爱心"的出现动画,然后是"爱心"以类似气泡的形式向上运动. &quo

  • Android控件实现直播App点赞飘心动画

    现在市面上直播类的应用可以说是一抓一大把,随随便便就以什么主题来开发个直播App,说白了就想在这领域分杯羹.在使用这些应用过程中其实不难发现,在所有的直播界面,少不了的就是各种打赏.各种点赞.今天自己就针对点赞功能敲了一下,代码不多,主要是涉及到动画运动轨迹运算,这里需借助 贝塞尔曲线 相关知识,我使用三阶贝塞尔曲线来实现轨迹动画. 运行效果 一.具体实现流程 仔细分析整个点赞过程可以发现,首先是"爱心"的出现动画,然后是"爱心"以类似气泡的形式向上运动. &quo

  • Android中SurfaceView和view画出触摸轨迹

    一.引言          想实现一个空白的画板,上面可以画出手滑动的轨迹,就这么一个小需求.一般就来讲就两种实现方式,view或者surfaceview.下面看看两种是如何实现的. 二.实现原理          先简单说一下实现原理:        (1)用一张白色的Bitmap作为画板        (2)用canvas在bitmap上画线        (3)为了画出平滑的曲线,要用canvas的drawPath(Path,Paint)方法.        (4)同时使用贝塞尔曲线来使曲

  • Android中SurfaceView和普通view的区别及使用

    1 SurfaceView介绍 SurfaceView第一印象它是一个view,因为它继承了View,有两个直接子类GLSurfaceView,VideoView.但根据SDK文档SurfaceView和普通的view又有较大区别. 最显著的区别就是普通view和它的宿主窗口共享一个绘图表面(Surface),SurfaceView虽然也在View的树形结构中,但是它有属于自己的绘图表面,Surface 内部持有一个Canvas,可以利用这个Canvas绘制. SurfaceView提供一个直接

  • Android实现仿今日头条点赞动画效果实例

    目录 一.前言 二.需求拆分 三.实现方案 1.点赞控件触摸事件处理 2.点赞动画的实现 2.1.点赞效果图片的获取和存储管理 2.2.点赞表情图标动画实现 2.3.点赞次数和点赞文案的绘制 3.存放点赞动画的容器 4.启动动画 四.遇到的问题 五.实现效果 六.完整代码获取 七.参考和感谢 总结 一.前言 我们在今日头条APP上会看到点赞动画效果,感觉非常不错,正好公司有点赞动画的需求,所以有了接下来的对此功能的实现的探索. 二.需求拆分 仔细观察点赞交互,看出大概以下几个步骤: 1:点赞控件

  • Android开发之图形图像与动画(三)Animation效果的XML实现

    使用XML来定义Tween Animation 动画的XML文件在工程中res/anim目录,这个文件必须包含一个根元素,可以使<alpha><scale> <translate> <rotate>插值元素或者是把上面的元素都放入<set>元素组中,默认情况下,所以的动画指令都是同时发生的,为了让他们按序列发生,需要设置一个特殊的属性startOffset.动画的指令定义了你想要发生什么样的转换,当他们发生了,应该执行多长时间,转换可以是连续的也

随机推荐