欢迎大家前往腾讯云社区,获取更多腾讯海量技术实践干货哦~

前言

开门见山,先来看下效果吧。

看到这么酷炫的效果图,不得不赞叹一下我们的设计师。然而,站在程序员的角度上看,除了酷炫之外更多的是复杂。但是,上面我们所看到的还只是最简单的一种形态而已。更加复杂的情况是当存在多个人脸的时候进行主次脸动画的切换,摄像头移动的时候动画的追踪,多个动画的之间的时序控制等问题,总之,UI展示加上各种业务逻辑使得这个动画变得异常复杂。今天我们要讲解的是剔除业务逻辑之外的单纯UI上的实现。

为什么是SurfaceView

选择一种方案的同时要给出为什么不选择另一种的理由是什么。没错,为什么这里不用自定义Vew来完成绘图呢?既然自定义View也可以实现一般的动画效果,为什么还要引入SurfaceView呢?可以把View理解为一个经过系统优化的,可以用来高效执行一些帧数比较低动画的对象,但是对于灵活性更高的动画来说,View并不是最好的选择。同时,对于普通的View它们都是在应用程序的主线程中进行绘制的,我们知道在Android系统上我们不能够在主线程做一些耗时的操作,否则会引起ANR。对于一些游戏画面,或者摄像头预览、视频播放来说,它们的UI都比较复杂,而且要求能够进行高效的绘制,因此,它们的UI就不适合在应用程序的主线程中进行绘制。这时候就必须要给那些需要复杂而高效UI的视图生成一个独立的绘图表面,以及使用一个独立的线程来绘制这些视图的UI,所以SurfaceView华丽登场了。

SurfaceView,它拥有独立的绘图表面,即它不与其宿主窗口共享同一个绘图表面。由于拥有独立的绘图表面,因此SurfaceView的UI就可以在一个独立的线程中进行绘制。又由于不会占用主线程资源,SurfaceView一方面可以实现复杂而高效的UI,另一方面又不会导致用户输入得不到及时响应。SurfaceView 一般与SurfaceHolder.Callback配合使用,需要重写以下三个方法:

  1. @Override
  2. public void surfaceCreated(SurfaceHolder holder) {
  3. }
  4. @Override
  5. public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
  6. }
  7. @Override
  8. public void surfaceDestroyed(SurfaceHolder holder) {
  9. }

我们可以通过SurfaceView的getHolder()函数可以获取SurfaceHolder对象,然后在子线程中通过 mHolder.lockCanvas()来获得 canvas,绘制完毕之后调用mHolder.unlockCanvasAndPost(canvas)来释放canvas,并把绘制的内容显示出来。

你所知道的ObjectAnimator

所谓属性动画:改变一切能改变的对象的属性值。ObjectAnimator内部的工作机制是通过寻找特定属性的get和set方法,然后通过方法不断地对值进行改变,从而实现动画效果的。ObjectAnimator提供了ofInt、ofFloat、ofObject等几个方法来对属性进行插值操作,这几个方法都是设置动画作用的元素、作用的属性、动画开始、结束、以及中间的任意个属性值。动画更新的过程中,会不断调用setPropName更新元素的属性,所以使用ObjectAnimator更新某个属性,必须得有setter方法。ObjectAnimator经常配合AnimatorSet进行使用:

  1. if (null == mBCRotateAnimator1) {
  2. mBCRotateAnimator1 = ObjectAnimator.ofFloat(this, "bCRotate", 0f, 360f);
  3. mBCRotateAnimator1.setInterpolator(new LinearInterpolator());
  4. mBCRotateAnimator1.setDuration();
  5. }
  6. if (null == mBCRotateAnimator2) {
  7. mBCRotateAnimator2 = ObjectAnimator.ofFloat(this, "bCRotate", 360f, 720f);
  8. mBCRotateAnimator2.setInterpolator(new LinearInterpolator());
  9. mBCRotateAnimator2.setDuration();
  10. }
  11. if (null == mBCRotateAnimator3) {
  12. mBCRotateAnimator3 = ObjectAnimator.ofFloat(this, "bCRotate", 720f, 360f);
  13. mBCRotateAnimator3.setInterpolator(new LinearInterpolator());
  14. mBCRotateAnimator3.setDuration();
  15. mBCRotateAnimator3.setRepeatCount(ValueAnimator.INFINITE);
  16. }
  17. if (null == mBCRotateAnimatorSet) {
  18. mBCRotateAnimatorSet = new AnimatorSet();
  19. ArrayList<Animator> animatorArrayList = new ArrayList<Animator>();
  20. animatorArrayList.add(mBCRotateAnimator1);
  21. animatorArrayList.add(mBCRotateAnimator2);
  22. animatorArrayList.add(mBCRotateAnimator3);
  23. mBCRotateAnimatorSet.playSequentially(animatorArrayList);
  24. mBCRotateAnimatorSet.setStartDelay();
  25. }

实用又强大的三角函数

大家有没有注意到转圈动画里面有一对小三角形呢?再细心点你会发现,这是个正三角形,并且其中的一个顶点正对着大圆的圆心,然后随着大圆一起转动,有木有?标题也说了,这里所有的元素都是自绘的,所以这两个三角形它不是设计同学给的icon资源,而是在Canvas上面绘制出来的,那么这里问题就来了:

  • 如何确定三角形所在的位置?(总不能把它画到圆外面去吧)

  • 如何确定三角形三个顶点的位置?(总不能把它画歪了吧)

这里先抛一下数学思路,后面会进行更详细的讲解。

  1. //绘制三角形/**
  2. * 数学问题:已知圆心(a, b),半径r,和角度m,求圆周上点的坐标?
  3. * 解:假设圆心的坐标为(a, b),那么圆的方程是(x-a)^+(y-b)^=r^
  4. * 根据方程可以求出圆上的各点坐标
  5. * 又已知角度m,则圆上点的坐标分别是(r*cos(m*Math.PI/)+a, r*sin(m*Math.PI/)+b)
  6. *
  7. * 拓展:已知正三角形中心点的坐标和其外接圆的半径,求其他三个顶点的坐标?
  8. * 解:可画出该三角形的外接圆,然后可把问题转化为求圆上三个点的坐标,又因为是正三角形,所以每个点的角度已知。解法同上。
  9. */

人脸识别动画完全解析

所有的动画元素可以分解为以下几种,这里我们主要讲解第一种——扫描控件,因为这个是难度最大的

先来粗略看下扫描控件的设计稿(这还不是全部的,一共有好几张,看不清的同学可以放大来看)

转圈动画是整个动画的核心,我们先来看下,其实它是分了好几层圆圈,圆弧,圆环,圆点,然后组合在一起根据各自不同的方向和速率进行转动,同时还伴随着alpha值的淡入淡出以及scale值的放大缩小,还有颜色的改变。so,几乎平时接触到的动画里所有能改变的元素都掺和了进来。(alpha,scale,rotate,color,shadow,speed,direction)

重点来分析下那个大蓝圈(暂且叫它为大蓝圈):由两个半圆弧组成,颜色为渐变蓝(最后变为白色),中间夹着对称的两个三角形,然后在不同的时间段里以不同的速率进行旋转和缩放。嘿嘿,画圆弧是件很简单的事情,调用canvas.drawArc()方法就可以了,圆弧的起始结束角度设计稿都有给了。这里的难点是在圆弧的不同部位绘制出渐变蓝色以及阴影效果。对于渐变颜色的填充,我们可以使用API提供的 来实现,参数填入圆心的坐标,颜色数组,颜色的比例。最后调用Paint.setShader()方法即可。

  1. public SweepGradient(float cx, float cy, int colors[], float positions[]) {}
  2. int[] colors = new int[]{Color.argb(, 0x1E, 0xFF, 0xEC), Color.argb(, 0x00, 0xBF, 0xFF)}; //蓝圈的渐变颜色float[] positions = new float[]{0f, 0.5f}; //渐变颜色占的位置Matrix matrix = new Matrix();
  3. SweepGradient sweepGradient = new SweepGradient(scanningData.centerX, scanningData.centerY, colors, positions);
  4. matrix.setRotate(scanningData.bCRightAngleStart+scanningData.bCRotate - , scanningData.centerX, scanningData.centerY);//这里居然需要减5°来矫正?sweepGradient.setLocalMatrix(matrix);
  5. scanningData.mBCPaint.setShader(sweepGradient);

阴影效果可以调用API提供的 来实现,参数填入阴影半径以及Blur type。最后调用Paint.setMaskFilter()方法即可。

  1. public BlurMaskFilter(float radius, Blur style) {}

现在来看下大蓝圈上的三角形如何绘制出来,我们只分析左上角那个,右下角那个处于对称位置。以顺时针X轴正方向为0°角,那么根据设计稿的初始状态,可计算出左上角三角形的初始角度是位于225°左右,右下角三角形的初始角度是45°左右。这里我们的最终目标是求出三角形三个顶点的坐标,然后用线条连起来使其成为一个三角形,但是根据这些条件我们直接计算三角形的顶点坐标是做不到的。现在我们来分解下:先根据三角函数求出三角形的中心坐标,然后又由于这个是正三角形,再根据三角函数求出各个顶点的坐标。

由于三角形是在圆周上,假设圆心(a, b),半径r,和三角形所在的角度m,其实这几个变量都是知道的,圆心坐标(a,b)则是人脸的中心点,可以通过人脸识别后的矩形坐标返回,半径r则是设计稿给的初始半径,角度m就是刚刚我们计算出来的225°,那么根据三角函数可得该三角形的圆心坐标则是 x = rcos(mMath.PI/180)+a, y = rsin(mMath.PI/180)+b。下一步计算三角形三个顶点的坐标。思路还是一样的:画出该三角形的外接圆,三角形的中心坐标即是外接圆的圆心,问题可转化为求外接圆上三个点的坐标,是不是又回到了上面的求解过程?是的。我们已知了这个外接圆的半径(设计稿给出),圆心坐标,现在要知道的是三角形三个顶点的角度,然后我们就可以分别算出它们的坐标了。我们知道这是个正三角形,而且其中一个顶点指向大圆圈的圆心,暂且把这个顶点命名为P。画出三角形的外接圆,即顶点P相对于外接圆的位置就是右下角那个三角形相对于大蓝圈的位置,因为这两个三角形是对称的,顶点是相对的。也就是说顶点P相对于外接圆的角度是45°。如图:

那么这样就好办了,第一个顶点的角度为45°,第二个顶点的角度为45°+120° = 165°,第三个顶点的坐标为165°+120°=285°,再次运用三角函数可求得三个顶点的坐标,然后调用Path.moveTo(),Path.lineTo(),Path.close()等方法把三角形的路径描述出来,最后调用Canvas.drawPath()把三角形绘制出来,三角形的绘制过程就到此结束了。

  1. //绘制三角形/**
  2. * 数学问题:已知圆心(a, b),半径r,和角度m,求圆周上点的坐标?
  3. * 解:假设圆心的坐标为(a, b),那么圆的方程是(x-a)^+(y-b)^=r^
  4. * 根据方程可以求出圆上的各点坐标
  5. * 又已知角度m,则圆上点的坐标分别是(r*cos(m*Math.PI/)+a, r*sin(m*Math.PI/)+b)
  6. *
  7. * 拓展:已知正三角形中心点的坐标和其外接圆的半径,求其他三个顶点的坐标?
  8. * 解:可画出该三角形的外接圆,然后可把问题转化为求圆上三个点的坐标,又因为是正三角形,所以每个点的角度已知。解法同上。
  9. */
  10. scanningData.bTLeftTopX = (float)((scanningData.bCRadius * scanningData.bCScale) * Math.cos((scanningData.bTLeftTopAngle + scanningData.bCRotate) * Math.PI / ) + scanningData.centerX);
  11. scanningData.bTLeftTopY = (float)((scanningData.bCRadius * scanningData.bCScale) * Math.sin((scanningData.bTLeftTopAngle + scanningData.bCRotate) * Math.PI / ) + scanningData.centerY);
  12. float tempAngle1 = scanningData.bTRightBottomAngle + scanningData.bCRotate; //取对称的角度,因为这是在三角形小圆里面的角度,不是大圆的角度
  13. float tempAngle2 = tempAngle1 + 120f; //正三角形,所以加120°
  14. float tempAngle3 = tempAngle2 + 120f;
  15. if (tempAngle1 >= ) {
  16. tempAngle1 = tempAngle1 - ;}
  17. if (tempAngle2 >= ) {
  18. tempAngle2 = tempAngle2 - ;}
  19. if (tempAngle3 >= ) {
  20. tempAngle3 = tempAngle3 - ;}
  21. //问题转化,求三角形三个顶点的坐标(看上面的数学解析)
  22. float vertex1_x = (float) (scanningData.bTRadius * scanningData.bCScale * Math.cos(tempAngle1 * Math.PI / ) + scanningData.bTLeftTopX);
  23. float vertex1_y = (float) (scanningData.bTRadius * scanningData.bCScale * Math.sin(tempAngle1 * Math.PI / ) + scanningData.bTLeftTopY);
  24. float vertex2_x = (float) (scanningData.bTRadius * scanningData.bCScale * Math.cos(tempAngle2 * Math.PI / ) + scanningData.bTLeftTopX);
  25. float vertex2_y = (float) (scanningData.bTRadius * scanningData.bCScale * Math.sin(tempAngle2 * Math.PI / ) + scanningData.bTLeftTopY);
  26. float vertex3_x = (float) (scanningData.bTRadius * scanningData.bCScale * Math.cos(tempAngle3 * Math.PI / ) + scanningData.bTLeftTopX);
  27. float vertex3_y = (float) (scanningData.bTRadius * scanningData.bCScale * Math.sin(tempAngle3 * Math.PI / ) + scanningData.bTLeftTopY);
  28. Path path = new Path();
  29. path.moveTo(vertex1_x, vertex1_y);
  30. path.lineTo(vertex2_x, vertex2_y);
  31. path.lineTo(vertex3_x, vertex3_y);
  32. path.close();
  33. scanningData.mBTPaint.setColor(scanningData.bTLeftTopColor);
  34. scanningData.mBTPaint.setAlpha((int) (scanningData.bTAlpha * ));
  35. canvas.drawPath(path, scanningData.mBTPaint);

好了,最难的一块绘制完了,其他的外圈,内圈,透明圈等等也都问题不大了。嗯,接下来,我们应该让它动起来。为了让动画旋转起来,我们可以用一个变量rotate运用到ObjectAnimator上面,然后在绘制的时候加上rotate的变化值即可。scale,alpha,等等都一样,需要在绘制的时候把这些值给加上去。

  1. if (null == mBCScaleAnimator1) {
  2. mBCScaleAnimator1 = ObjectAnimator.ofFloat(this, "bCScale", 0f, 1.04f);
  3. mBCScaleAnimator1.setInterpolator(new LinearInterpolator());
  4. mBCScaleAnimator1.setDuration();
  5. }
  6. if (null == mBCScaleAnimator2) {
  7. mBCScaleAnimator2 = ObjectAnimator.ofFloat(this, "bCScale", 1.04f, 1f);
  8. mBCScaleAnimator2.setInterpolator(new LinearInterpolator());
  9. mBCScaleAnimator2.setDuration();
  10. }
  11. if (null == mBCScaleAnimator3) {
  12. mBCScaleAnimator3 = ObjectAnimator.ofFloat(this, "bCScale", 1f, 1.02f);
  13. mBCScaleAnimator3.setInterpolator(new LinearInterpolator());
  14. mBCScaleAnimator3.setDuration();
  15. }
  1. scanningData.bTRadius * scanningData.bCScale * Math.cos(tempAngle1 * Math.PI / )
  2. scanningData.bTRadius * scanningData.bCScale * Math.sin(tempAngle1 * Math.PI / )
  3. scanningData.bTRadius * scanningData.bCScale * Math.cos(tempAngle2 * Math.PI / )
  4. scanningData.bTRadius * scanningData.bCScale * Math.sin(tempAngle2 * Math.PI / )
  5. scanningData.bTRadius * scanningData.bCScale * Math.cos(tempAngle3 * Math.PI / )
  6. scanningData.bTRadius * scanningData.bCScale * Math.sin(tempAngle3 * Math.PI / )

人物名称的绘制以及指示线条以及下面的资料浮窗就不说了,调用Canvas.drawLine(),Canvas.drawText(),Canvas.drawCircle()等等就可以实现(其实线条的位置以及角度也需要运用三角函数进行计算),下面简单分析下右边小图片的波纹效果。

其实这里又有一个需要自绘的三角形,有没有感觉到崩溃?没关系,还是按照我们上面的套路,改变下初始角度就可以了,算法在手,三角形我有!其实波纹效果的绘制也比较简单,调用Canvas.drawCircle(),然后通过ObjectAnimator不断地去改变圆圈的alpha值和scale值,从而达到波纹的效果。见如下代码

  1. if (null == mFPOutCircleAlphaAnimator) {
  2. mFPOutCircleAlphaAnimator = ObjectAnimator.ofFloat(this, "fPOutCircleAlpha", 1f, 0f, 0f);
  3. mFPOutCircleAlphaAnimator.setInterpolator(new LinearInterpolator());
  4. mFPOutCircleAlphaAnimator.setDuration();
  5. mFPOutCircleAlphaAnimator.setRepeatCount(ValueAnimator.INFINITE);
  6. }
  7. if (null == mFPOutCircleAlphaAnimatorSet) {
  8. mFPOutCircleAlphaAnimatorSet = new AnimatorSet();
  9. mFPOutCircleAlphaAnimatorSet.play(mFPOutCircleAlphaAnimator);
  10. mFPOutCircleAlphaAnimatorSet.setStartDelay();
  11. }
  12. if (null == mFPOutCircleScaleAnimator) {
  13. mFPOutCircleScaleAnimator = ObjectAnimator.ofFloat(this, "fPOutCircleScale", 1f, 1.3f, 1.3f);
  14. mFPOutCircleScaleAnimator.setInterpolator(new LinearInterpolator());
  15. mFPOutCircleScaleAnimator.setRepeatCount(ValueAnimator.INFINITE);
  16. mFPOutCircleScaleAnimator.setDuration();
  17. }
  18. if (null == mFPOutCircleScaleAnimatorSet) {
  19. mFPOutCircleScaleAnimatorSet = new AnimatorSet();
  20. mFPOutCircleScaleAnimatorSet.play(mFPOutCircleScaleAnimator);
  21. mFPOutCircleScaleAnimatorSet.setStartDelay();
  22. }
  23.  
  24. scanningData.mFPOutCirclePaint.setAlpha((int)(scanningData.fPOutCircleAlpha * * 0.5));
  25. canvas.drawCircle(scanningData.fPCenterX, scanningData.fPCenterY, scanningData.fPOutCircleRadius * scanningData.fPOutCircleScale, scanningData.mFPOutCirclePaint);

到此,整个动画绘制完毕。

相关阅读

爸爸去哪儿玩转黑科技:快来测测自己和老爸有多像?

研究 AI 识别同性恋竟受到死亡威胁!论文作者回应如下

人脸对齐:ASM (主动形状模型)算法

此文已由作者授权腾讯云技术社区发布,转载请注明原文出处

原文链接:https://cloud.tencent.com/community/article/784358

 
海量技术实践经验,尽在腾讯云社区! https://cloud.tencent.com/community

手 Q 人脸识别动画实现详解的更多相关文章

  1. 人脸验证算法Joint Bayesian详解及实现(Python版)

    人脸验证算法Joint Bayesian详解及实现(Python版) Tags: JointBayesian DeepLearning Python 本博客仅为作者记录笔记之用,不免有很多细节不对之处 ...

  2. iOS:核心动画的详解介绍:CAAnimation(抽象类)及其子类

    核心动画的详解介绍:CAAnimation(抽象类)   1.核心动画基本概念 Core Animation是一组非常强大的动画处理API,使用它能做出非常炫丽的动画效果,而且往往是事半功倍! 使用它 ...

  3. Android Animations 视图动画使用详解!!!

    转自:http://www.open-open.com/lib/view/open1335777066015.html Android Animations 视图动画使用详解 一.动画类型 Andro ...

  4. CSS3图片翻转动画技术详解

    CSS动画非常的有趣:这种技术的美就在于,通过使用很多简单的属性,你能创建出漂亮的消隐效果.其中代表性的一种就是CSS图片翻转效果,能让你看到一张卡片的正反两面上的内容.本文就是要用最简单的方法向大家 ...

  5. POPSpring动画参数详解

    POPSpring动画参数详解 效果 源码 https://github.com/YouXianMing/Animations // // POPSpringParameterController.m ...

  6. (转载) 车牌识别EasyPR--开发详解

    车牌识别EasyPR--开发详解 http://blog.csdn.net/liuuze5/article/details/46290455 源码GitHub:https://github.com/l ...

  7. Unity3D NGUI UIPlayTween(原UIButtonTween)动画事件详解

    http://blog.csdn.net/asd237241291/article/details/8507817 原创文章如需转载请注明:转载自 脱莫柔Unity3D学习之旅 Unity3D引擎技术 ...

  8. Android基础夯实--重温动画(五)之属性动画 ObjectAnimator详解

    只有一种真正的英雄主义 一.摘要 ObjectAnimator是ValueAnimator的子类,它和ValueAnimator一样,同样具有计算属性值的功能,但对比ValueAnimator,它会更 ...

  9. EasyPR--中文开源车牌识别系统 开发详解(1)

    在上篇文档中作者已经简单的介绍了EasyPR,现在在本文档中详细的介绍EasyPR的开发过程. 正如淘宝诞生于一个购买来的LAMP系统,EasyPR也有它诞生的原型,起源于CSDN的taotao123 ...

随机推荐

  1. EasyUI Dialog 窗体 布局记要

    通常在窗体里放置的都是表单,或者使用分栏(Tab)来陈列信息也是非常的好用.在这里特别记录一下在窗体里同时放置表单和表格的设计思路. 仅放置一个表单 通常 Dialog 里只放一个表单,而且表单的行数 ...

  2. 【转载】小tips: PC端传统网页试试使用Zepto.js进行开发

    Zepto.js设计之初专为移动端,不对一些古董浏览器支持.所以,尺寸很小,压缩后20K多一点,但是,jQuery压缩后,3.*版本要80多K,1.*版本则要90多K,4倍差距. 由于每个页面都会使用 ...

  3. 原生js反转字符串

    //直接操作 var str='nama';var rts=str.split('').reverse().join(''); //String上拓展方法String.prototype.revers ...

  4. Linux 进程与信号的概念和操作

    进程 主要参考: http://www.bogotobogo.com/Linux/linux_process_and_signals.php 信号与进程几乎控制了操作系统的每个任务. 在shell中输 ...

  5. invalid types 'int[int]' for array subscript// EOF 输入多组数据//如何键盘输入EOF

    数组维度搞错了 一次运行,要输入多组数据,直到读至输入文件末尾(EOF)为止 while(scanf("%d %d",&a, &b) != EOF) // 输入结束 ...

  6. linux 守护进程编程

    概述: Daemon运行在后台也称作"后台服务进程". 它是没有控制终端与之相连的进程.它独立于控制终端.通常周期的执行某种任务. 守护进程脱离终端是为了避免进程在执行过程中的信息 ...

  7. Myeclipse10连接SqlServer2012

    本文纯手打!!!! 对于我们这些刚学java的小白,可是真费劲啊!!!! 要用java连接数据库  首先是要通过JDBC驱动 要先去下载一个sqljdbc4.jar,我这里放百度云盘了, 下载地址:链 ...

  8. HTML笔记<note1>

    <!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8" ...

  9. LArea插件的使用

    楼主菜鸟一枚,开发微信端三级滑动遇到的N多技术问题,与大家分享,话不多说,先上效果图: LArea插件的使用,前端部分参考如下: 关于PHP插件使用,请往下看:   1.首先在前端页面引入js样式和插 ...

  10. 【UML 建模】类图介绍

    1.类图是面向对象系统建模中最常用和最重要的图,是定义其它图的基础.类图主要是用来显示系统中的类.接口以及它们之间的静态结构和关系的一种静态模型. 2.类的关系有泛化(Generalization). ...