这次给大家带来的是一篇关于自定义View实现水波动画效果的文章,其实在去年项目中使用过类似的动画,当时就自定义View也实现了预期的效果,最近项目中又使用了相似的效果,于是对代码重新整理了一下并且记录下来,便于以后有类似需求可以当作参考!
按照惯例,无图无真相
实现方式:
1、正余弦函数实现
2、贝塞尔曲线实现
开篇
看到上边的两种实现方式是不是感觉都和数学公式有关呐,这对于毕业多年之后的我们来说如果当初数学基础不是很好现在估计也全部还给老师了吧,所以一提到相关的数学计算公式只能用一个表情表达了。。。
1、正余弦函数实现
正余弦的函数不知道大家还记不记得,我们温习一下相关参数的意义
y=A*sin(ωx+φ)+k
A—振幅越大,波形在y轴上最大与最小值的差值越大
ω—角速度, 控制正弦周期(单位角度内震动的次数)
φ—初相,反映在坐标系上则为图像的左右移动。这里通过不断改变φ,达到波浪移动效果
k—偏距,反映在坐标系上则为图像的上移或下移。
我们要实现移动的波形首先是先画出静态的波形,那么怎么来绘制一个波形图呐,Math函数里已经提供了相应的方法,我们可以直接使用
A* Math.sin(ω * x + φ ) + K)
开始绘制之前首先定义相关画笔之类的参数,在此就不做过多说明了,根据上边的公式我们知道需要哪些参数,首先是A,这是振幅,就是波形最高和最低点的差值,我们可以设置定值或者外界传入;其次是ω,角速度,给一个定制或者外界传入;φ,相位,我们就是根据不断改变相位来达到波形移动的效果,每次移动多少可以从外界传入,便于控制速度;K,波形偏移上下的距离,知道了以上各个参数的具体使用意义,下边就可以直接通过代码看下具体实现效果了,毕竟公式都有了,参数也发给你了,剩下的就是根据公式填写以下相应参数就ok了
private void drawSin(Canvas canvas) {
φ -= 0.03;
float y;
path.reset();
path.moveTo(0, getHeight());
for (float x = 0; x <= getWidth(); x += 20) {
y = (float) (A * Math.sin(ω * x + φ ) + K);
path.lineTo(x, getHeight() - y);
}
canvas.drawPath(path, paint);
}
静态的波形出来之后我们就要借助属性动画来让波形动起来
private void initAnimation() {
valueAnimator = ValueAnimator.ofInt(0, getWidth());
valueAnimator.setDuration(1000);
valueAnimator.setRepeatCount(ValueAnimator.INFINITE);
valueAnimator.setInterpolator(new LinearInterpolator());
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
/**
* 刷新页面调取onDraw方法,通过变更φ 达到移动效果
*/
invalidate();
}
});
if (waveStart) {
valueAnimator.start();
}
}
开启动画之后再运行一下看看效果吧
看到这里只是一个单纯的波形,我们一般使用的时候并不是这样的,而是一个封闭的波形,可以向上封闭也可以向下封闭,我们在波形绘制完成之后
.........省略部分代码
//填充矩形
path.lineTo(getWidth(), getHeight());
path.lineTo(0, getHeight());
path.close();
这样就绘制出封闭的波形了,然后画笔设成填充就ok
代码中我已经对向下密封还是向上密封封装了方法,在此就不再赘述,需要的可以看源码哦,除此之外还有其他的参数都进行了可配置话,可以通过xml进行设置,至此通过正余弦函数进行绘制波形图已经介绍完毕了。
2、贝塞尔曲线实现
对贝塞尔曲线不是很了解的可以自行百度,概念性的东东就不在此赘述,我们使用二阶的贝塞尔进行绘制,为什么选择二阶的呐,看一个图就知道啦
一段完整的波形其实就是两个二阶的贝塞尔组成的,来看下代码
/**
* sin函数图像的波形
*
* @param canvas
*/
private void drawSinPath(Canvas canvas) {
mWavePath.reset();
mWavePath.moveTo(-mWaveLength + mOffset, mWaveAmplitude);
//相信很多人会疑惑为什么控制点的纵坐标是以下值,
//是根据公式计算出来的,具体计算方法情况文章内容
for (int i = 0; i < mWaveCount; i++) {
//第一个控制点的坐标为(-mWaveLength * 3 / 4,-mWaveAmplitude)
mWavePath.quadTo(-mWaveLength * 3 / 4 + mOffset + i * mWaveLength,
-mWaveAmplitude,
-mWaveLength / 2 + mOffset + i * mWaveLength,
mWaveAmplitude);
//第二个控制点的坐标为(-mWaveLength / 4,3 * mWaveAmplitude)
mWavePath.quadTo(-mWaveLength / 4 + mOffset + i * mWaveLength,
3 * mWaveAmplitude,
mOffset + i * mWaveLength,
mWaveAmplitude);
}
mWavePath.lineTo(getWidth(), getHeight());
mWavePath.lineTo(0, getHeight());
mWavePath.close();
canvas.drawPath(mWavePath, mWavePaint);
}
根据计算得到起点和控制点坐标之后就可以写代码运行了效果和上边的运行效果一样就不再展示了,上边的计算内容就解释了代码提出的问题
3、两种方式对比总结
图像的绘制其实都不复杂,不过关键点还是有几个的。
正余弦函数的波形使用是根据相位控制的,而贝塞尔曲线实现的波形效果是不断改变波的起始位置控制的,并且使用贝塞尔曲线的话需要先在屏幕外边绘制一个完整的波形,保证在平移的过程中可以看到图像不间断的移动来达到移动的波形效果。
最后
看到这里你是你会感觉到这边文章的内容其实很简单,只要中间的几个点注意一下就可以实现相应的效果了,建议朋友们动手敲一遍代码,加深一下印象,毕竟真是做出来和知道理论没有实践还是有很大区别的!
github源码地址传送门
谨以此篇来记录自己项目中遇到的问题,献给需要类似功能的小伙伴们。如果你有好的建议欢迎评论指出,大家一起讨论、学习、进步!