如何实现Siri中的波纹动画

来源:互联网 发布:微信 积分 源码下载 编辑:程序博客网 时间:2024/05/29 08:21

苹果手机上的siri语音波纹动画很有意思,本文将带领大家研究如何实现生动画效果。
本文中代码是基于android的Java,其他平台上可以参考算法自行实现。本文假定读者会实现自定义View,不会的同学可以自行百度,文中将不会赘述。

绘制正弦波

Siri波纹的基本型是正弦波,所以首先我们要能够绘制一条正弦波。
一切以代码说话:

protected void onDraw(Canvas canvas) {    super.onDraw(canvas);    float period = 2.0f;// 区域内,正弦波的周期    // 将绘图原点移动到区域中心    int width = getWidth();    int height = getHeight();    float midWidth = width / 2.0f;    float midHeight = height / 2.0f;    canvas.translate(midWidth, midHeight);    // 初始化画笔    Paint paint = new Paint();    paint.setStrokeWidth(1);// 画线宽度    paint.setStyle(Style.STROKE);//空心效果    paint.setAntiAlias(true);//抗锯齿    paint.setColor(Color.BLACK);    // 初始化线条    Path sinPath = new Path();    sinPath.moveTo(-midWidth, 0);    // 计算线条    for (float x = -midWidth; x < midWidth; x++) {        double sine = Math.sin(2 * Math.PI * period * (x / width));//计算该点上的正弦值        float y = (float) (midHeight * sine);// 将正弦值限定到绘图区的高度上        sinPath.lineTo(x, y);    }    canvas.drawPath(sinPath, paint);//绘制线条    canvas.restore();}

简单起见,我把所有的有效代码都放到了View.onDraw方法里面了。
这里,关键代码是这两句

double sine = Math.sin(2 * Math.PI * period * (x / width));
float y = (float) (midHeight * sine);

结合上下文,这两句话计算了x轴上对应的y值,即:

y=midHeightsin(2πperiodxwidth)

其中,坐标原点是绘图区的中心;period是x坐标上的正弦周期数,这里是2。
下图是这段代码绘制出的线条效果(图片做了缩小处理,会有不连贯效果):
基本正弦波

振幅的抛物线修正

Siri中,波纹中间和两边的波动振幅是不同的,明显是两边小,中间大。那么我们可以定义一个函数,使得波纹振幅按照该函数变化。这里我定义的是二次函数如下,当然也可以自行设计合适的函数比如半个周期的正弦波。

scaling=1(xmidWidth)2

修改之后的关键代码如下,加粗的部分是新添加的内容:

double scaling = 1 - Math.pow(x / midWidth, 2);
double sine = Math.sin(2 * Math.PI * period * (x / width));
float y = (float) (midHeight * sine * scaling);

如此修正后的正弦波纹看起来就像siri中的样子了,为了更明显的表示scaling的作用,我将两条抛物线画在了上面。
抛物修正后的正弦波

附抛物线代码:

Path parabola1 = new Path();parabola1.moveTo(-midWidth, 0);Path parabola2 = new Path();parabola2.moveTo(-midWidth, 0);// 计算线条for (float x = -midWidth; x < midWidth; x++) {    double scaling = 1 - Math.pow(x / midWidth, 2);    double sine = Math.sin(2 * Math.PI * period * (x / width));//计算该点上的正弦值    float y = (float) (midHeight * sine * scaling);// 将正弦值限定到绘图区的高度上    sinPath.lineTo(x, y);    parabola1.lineTo(x, (float) scaling * midHeight);    parabola2.lineTo(x, -(float) scaling * midHeight);}canvas.drawPath(sinPath, paint);//绘制正弦线canvas.drawPath(parabola1, paint);//绘制抛物线1canvas.drawPath(parabola2, paint);//绘制抛物线2

副波纹

Siri的波纹由多个线条组成的,下面我们就来添加几条副波纹。副波纹的条数和相关属性可以依个人爱好自行调整,这里我添加4条副波纹,包括主波纹一共是5条正弦波纹。

首先我们来定义一下几条波纹的宽度。在之前的绘图中,定义的画笔宽度一直是一个像素,这里我们给他改一下。
这里我定义了每条波纹的宽度,并依据屏幕像素密度进行修改,以减小不同设备上的视觉区别

float[] waveWidth = {3, 2, 2, 1, 1};DisplayMetrics metric = getResources().getDisplayMetrics();float density = metric.density; // 屏幕密度for (int i = 0; i < waveWidth.length; i++) {    waveWidth[i] = waveWidth[i] * density / 2;}

接下来定义一下波纹的透明度,透明度可以使波纹看起来有层次感^_^。

float[] waveAlpha = {1.0f, 0.9f, 0.7f, 0.4f, 0.2f};

接下来要修改副波纹的振幅,这样几条波纹才会区别开来

float[] waveAmplitude = {1.0f, 0.7f, 0.4f, 0.1f, -0.2f};

下面,我要把这些属性都应用到几条波纹上:

for (int i = 0; i < waveWidth.length; i++) {    paint.setStrokeWidth(waveWidth[i]);//画笔宽度    paint.setAlpha((int) (waveAlpha[i] * 255));//画笔透明度    sinPath.reset();//重置线条    sinPath.moveTo(-midWidth, 0);    // 计算线条    for (float x = -midWidth; x < midWidth; x++) {        double scaling = 1 - Math.pow(1 / midWidth * x, 2);        double sine = Math.sin(2 * Math.PI * period * (x / width));//计算该点上的正弦值        float y = (float) (midHeight// 将正弦值限定到绘图区的高度上                * sine   // 正弦值                * scaling// 振幅修正 - 距离中心越远,振幅越小                * waveAmplitude[i]// 副波纹振幅修正        );        sinPath.lineTo(x, y);    }    canvas.drawPath(sinPath, paint);//绘制线条}

这段代码的绘制效果如图:
副波纹效果1

现在有点样子了,不过几条波纹在 x 轴上有个很明显的焦点,想当难看,我们给几条副波纹来个偏移

int[] wavePhase = {0, 6, -9, 15, -21};

double sine = Math.sin(2 * Math.PI * period * ((x + wavePhase[i]) / width));//计算该点上的正弦值

现在的效果就成这个样子了:
副波纹效果2

波纹移动

接下来就是让波纹移动了。很简单,在绘制正弦波纹的时候添加一个x轴上的偏移即可。
比如我们可以定义这样一个方法让波纹右移 n 个像素:

private float phase;public void nextPhase(float n) {    phase -= n;    invalidate();}

然后在绘图时,将 phase 添加到偏移里面:

double sine = Math.sin(2 * Math.PI * period * ((x + phase + wavePhase[i]) / width));//计算该点上的正弦值

做成动画的效果就是这样的:
动画效果

按照音量调整振幅

Siri语音动画效果是依据环境音量调整振幅的,不过对此本文就不多做介绍了,感兴趣的同学可以去研究一下源码: Siri动画完整源码文件(Android版)

2 0