如何实现Siri中的波纹动画
2015-08-18 22:04
537 查看
苹果手机上的siri语音波纹动画很有意思,本文将带领大家研究如何实现生动画效果。
本文中代码是基于android的Java,其他平台上可以参考算法自行实现。本文假定读者会实现自定义View,不会的同学可以自行百度,文中将不会赘述。
一切以代码说话:
简单起见,我把所有的有效代码都放到了View.onDraw方法里面了。
这里,关键代码是这两句
double sine = Math.sin(2 * Math.PI * period * (x / width));
float y = (float) (midHeight * sine);
结合上下文,这两句话计算了x轴上对应的y值,即:
y=midHeight∗sin(2π∗period∗xwidth)
其中,坐标原点是绘图区的中心;period是x坐标上的正弦周期数,这里是2。
下图是这段代码绘制出的线条效果(图片做了缩小处理,会有不连贯效果):
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的作用,我将两条抛物线画在了上面。
附抛物线代码:
首先我们来定义一下几条波纹的宽度。在之前的绘图中,定义的画笔宽度一直是一个像素,这里我们给他改一下。
这里我定义了每条波纹的宽度,并依据屏幕像素密度进行修改,以减小不同设备上的视觉区别
接下来定义一下波纹的透明度,透明度可以使波纹看起来有层次感^_^。
接下来要修改副波纹的振幅,这样几条波纹才会区别开来
下面,我要把这些属性都应用到几条波纹上:
这段代码的绘制效果如图:
现在有点样子了,不过几条波纹在 x 轴上有个很明显的焦点,想当难看,我们给几条副波纹来个偏移
int[] wavePhase = {0, 6, -9, 15, -21};
…
double sine = Math.sin(2 * Math.PI * period * ((x + wavePhase[i]) / width));//计算该点上的正弦值
现在的效果就成这个样子了:
比如我们可以定义这样一个方法让波纹右移 n 个像素:
然后在绘图时,将 phase 添加到偏移里面:
double sine = Math.sin(2 * Math.PI * period * ((x + phase + wavePhase[i]) / width));//计算该点上的正弦值
做成动画的效果就是这样的:
本文中代码是基于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=midHeight∗sin(2π∗period∗xwidth)
其中,坐标原点是绘图区的中心;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);//绘制抛物线1 canvas.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);//绘制线条 }
这段代码的绘制效果如图:
现在有点样子了,不过几条波纹在 x 轴上有个很明显的焦点,想当难看,我们给几条副波纹来个偏移
int[] wavePhase = {0, 6, -9, 15, -21};
…
double sine = Math.sin(2 * Math.PI * period * ((x + wavePhase[i]) / width));//计算该点上的正弦值
现在的效果就成这个样子了:
波纹移动
接下来就是让波纹移动了。很简单,在绘制正弦波纹的时候添加一个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版)相关文章推荐
- 使用C++实现JNI接口需要注意的事项
- Android IPC进程间通讯机制
- Android Manifest 用法
- [转载]Activity中ConfigChanges属性的用法
- Android之获取手机上的图片和视频缩略图thumbnails
- Android之使用Http协议实现文件上传功能
- Android学习笔记(二九):嵌入浏览器
- android string.xml文件中的整型和string型代替
- i-jetty环境搭配与编译
- android之定时器AlarmManager
- android wifi 无线调试
- Android Native 绘图方法
- Android java 与 javascript互访(相互调用)的方法例子
- android 代码实现控件之间的间距
- android FragmentPagerAdapter的“标准”配置
- Android"解决"onTouch和onClick的冲突问题
- android:installLocation简析
- android searchView的关闭事件
- SourceProvider.getJniDirectories