自定义View合辑(6)-波浪(贝塞尔曲线)
2019-05-08 本文已影响4人
业志陈
为了加强对自定义 View 的认知以及开发能力,我计划这段时间陆续来完成几个难度从易到难的自定义 View,并简单的写几篇博客来进行介绍,所有的代码也都会开源,也希望读者能给个 star 哈
GitHub 地址:https://github.com/leavesC/CustomView
也可以下载 Apk 来体验下:https://www.pgyer.com/CustomView
先看下效果图:
一、思路解析
波浪 View(即 WaveView)的重点在于其 onDraw 方法的十行代码上,当中运用到了贝塞尔曲线的知识
//每个波浪的起伏高度
private float waveHeight;
//每个波浪的宽度
private float waveWidth;
//波浪的速度
private long speed = DEFAULT_SPEED;
private float animatedValue;
private Path path = new Path();
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
path.reset();
path.moveTo(-waveWidth + animatedValue, contentHeight / 2);
for (float i = -waveWidth; i < contentWidth + waveWidth; i += waveWidth) {
path.rQuadTo(waveWidth / 4, -waveHeight, waveWidth / 2, 0);
path.rQuadTo(waveWidth / 4, waveHeight, waveWidth / 2, 0);
}
path.lineTo(contentWidth, contentHeight);
path.lineTo(0, contentHeight);
path.close();
canvas.drawPath(path, paint);
}
从图片可以看出来各个波浪的起伏高度和宽度都是一样的,意味着在贝塞尔曲线中控制点的 Y 坐标是保持不变的,以上的逻辑可以利用下图来帮助理解
蓝色背景代表的是 View 所占的面积,红色小球连起来的曲线轨迹即为波浪的运行轨迹,waveWidth 代表的是每个波浪的宽度,即每一个绿色方块的宽度。两个 path.rQuadTo
方法所绘制出来的分别是向上的曲线和向下的曲线,并在 for
循环中不断重复这个过程,直到绿色方块所占的总宽度超出 View 的宽度为止
为了呈现出“波浪向右前进”的效果,当中就需要用到动画值 animatedValue
来不断改变贝塞尔曲线的起始坐标点
private ValueAnimator valueAnimator;
public void initAnimation() {
valueAnimator = new ValueAnimator();
valueAnimator.setDuration(speed);
valueAnimator.setRepeatCount(ValueAnimator.INFINITE);
valueAnimator.setInterpolator(new LinearInterpolator());
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
animatedValue = (float) animation.getAnimatedValue();
invalidate();
}
});
}
然后向外部开放改变波浪宽度、波浪高度、动画时长的这三个方法,即可以此来改变 View 的形状
public void setWaveScaleWidth(float waveScaleWidth) {
if (waveScaleWidth <= 0 || waveScaleWidth > 1) {
return;
}
this.waveScaleWidth = waveScaleWidth;
resetWaveParams();
}
public void setWaveScaleHeight(float waveScaleHeight) {
if (waveScaleWidth <= 0 || waveScaleWidth > 1) {
return;
}
this.waveScaleHeight = waveScaleHeight;
resetWaveParams();
}
public void setSpeed(long speed) {
this.speed = speed;
resetWaveParams();
}
private void resetWaveParams() {
waveWidth = contentWidth * waveScaleWidth;
waveHeight = contentHeight * waveScaleHeight;
if (valueAnimator != null) {
valueAnimator.setFloatValues(0, waveWidth);
valueAnimator.setDuration(speed);
}
}