为了加强对自定义 View 的认知以及开发能力,我计划这段时间陆续来完成几个难度从易到难的自定义 View,并简单的写几篇博客来进行介绍,所有的代码也都会开源,也希望读者能给个 star 哈 GitHub 地址: 也可以下载 Apk 来体验下:
先看下效果图:
一、思路解析
波浪 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); } }复制代码