想要效果需求如下:
1.实现水波纹水平滚动效果。
2.实现多组水波纹。
3.实现水波纹上移效果。
实现上述功能需要用到的技能点:
1.通过 Canvas 绘制贝塞尔曲线2阶
2.通过属性动画实现 起始点位置偏移量;
想法:
1.首先通过自定义 view 绘制出一个静态的水波纹图像。
2.然后实现静态水波纹的平移效果;
3.实现两组静态水波纹平移效果;
4.通过色彩组实现 n 组水波纹平移效果;
需要了解绘制波的起始点、中点、终点如何设置:
1.起始点 x轴:-waveWidth * i (从屏幕外一个波宽进行绘制,避免移动的时候出现没有布局的情况) i 属于第几个波;
2.中点 x轴:上波 waveWidth/4 + 起始点 (属于起始点 +1/4波宽), 下波 起始点+waveWidth*(3/4) (属于起始点 +3/4波宽)
- 终点 x轴: 起始点+waveWidth;
- 基准线:属于水波纹的y轴中线,有了基准线,后面绘制就有了参考点。
- 起始点 y 轴: 基准线的值;针对水波纹上移动 ,也是通过更改基准线的值进行实现
- 中点 y轴:上波 基准线值 - waveHeight / 2 (减 的原因属于 坐标原点在手机左上角,向下是 y轴增加值)下波 基准线值 +waveHeight / 2
- 终点 y轴: 基准线值;
后续需要设置的值:
1.水波纹宽度 waveWidth ,waveHeight, paint, path
2.后续需要设置的值,offsetX,offsetY 属于每个水波纹的x,y 轴偏移量
绘制水波纹静态效果:
private fun drawWave(canvas: Canvas, color: Int, baseLineY: Float) {
path.reset()
canvas.save()
val startPointX = -waveWidth;
path.moveTo(
startPointX,
baseLineY
)
//判断能够绘制多少个波
for (start: Int in -waveWidth.toInt() until (width + waveWidth).toInt() step waveWidth.toInt()) {
val startX = start //每个波的起始点
path.quadTo(
(startX + waveWidth / 4.0f),
baseLineY - waveHeight / 2.0f,
(startX + waveWidth / 2.0f),
baseLineY
)
//下波
path.quadTo(
(startX + waveWidth * (3 / 4.0f)),
baseLineY + waveHeight / 2.0f,
(startX + waveWidth),
baseLineY
)
}
path.lineTo(width.toFloat(), height.toFloat())
path.lineTo(0f, height.toFloat())
path.close()
paint.setColor(color)
canvas.drawPath(path, paint)
canvas.restore()
}
实现动画效果:
1.如何实现水波纹的水平移动效果呢?其实就是通过更改起始点位置来实现水波纹的平移效果。
代码如下:
private var anim: ValueAnimator? = null
@SuppressLint("Recycle")
private fun startAnim() {
if (anim != null && anim?.isStarted == true) {
return
}
anim = ValueAnimator.ofInt(0, waveWidth.toInt())
anim?.repeatCount = ValueAnimator.INFINITE
anim?.interpolator = LinearInterpolator()
anim?.duration = 1000
anim?.addUpdateListener {
dx = it.animatedValue as Int
dy += 0.5f
invalidate()
}
anim?.start()
}
同时需要更改起始点的位置信息如下:
val startPointX = -waveWidth + dx //dx这个地方是每次0-waveWidth 的值,用于实现起始点 -waveWidth -0 位置的移动。
val startX = start + dx //是实现每个波位置都移动相同的位置。实现整个 view 波纹的移动。
根据色值绘制多个波纹:
private val colors = arrayOf(
context.resources.getColor(R.color.purple_200),
context.resources.getColor(R.color.f0purple_200),
context.resources.getColor(R.color.a0purple_200)
)
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
// val baseLineY = height - waveHeight / 2.0f - dy
// if (baseLineY <= -waveHeight) {
//如果滚动到了顶部就移除动画。
// closeAnim()
// }
// 遍历绘制
colors.forEachIndexed { index, color ->
drawWave(
canvas,
dx - offsetX * index,
color, baseLineY - offsetY * index
)
}
}
实现水波纹上移效果是在动画中 的dy 在每次指定动画时就自动增加值。同时基准线减去 dy 来实现上移效果(baseLineY - offsetY);
总的代码如下:
class WaveView @JvmOverloads constructor(
context: Context,
attributeSet: AttributeSet? = null,
def: Int
) : View(context, attributeSet, def) {
private val waveWidth = 80f.dp()
private val waveHeight = 40.dp()
//定义绘制的线条以及设置
private var paint: Paint = Paint()
private val path = Path()
private var dx = 0
private var dy = 0f
private val offsetX = 25.dp()//属于水波纹的偏移量
private val offsetY = 5.dp()
//创建色值的集合
private val colors = arrayOf(
context.resources.getColor(R.color.purple_200),
context.resources.getColor(R.color.f0purple_200),
context.resources.getColor(R.color.a0purple_200)
)
constructor(context: Context, attributeSet: AttributeSet?) : this(context, attributeSet, 0) {
//设置画笔的宽度和颜色
paint.setColor(context.resources.getColor(R.color.purple_200))
paint.isAntiAlias = true
paint.setTypeface(Typeface.DEFAULT)
paint.style = Paint.Style.FILL
startAnim()
}
private var anim: ValueAnimator? = null
@SuppressLint("Recycle")
private fun startAnim() {
if (anim != null && anim?.isStarted == true) {
return
}
anim = ValueAnimator.ofInt(0, waveWidth.toInt())
anim?.repeatCount = ValueAnimator.INFINITE
anim?.interpolator = LinearInterpolator()
anim?.duration = 1000
anim?.addUpdateListener {
dx = it.animatedValue as Int
dy += 0.5f
invalidate()
}
anim?.start()
}
// 绘制水波纹
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
//设置绘制的起始点
//height - waveHeight / 2.0f :属于在view 最底部 waveHeight / 2.0f处
// - dy 属于根据 dy 值的增加逐渐的更改基准线的位置,属于 将基准线向 y 轴 原点处偏移;
val baseLineY = height - waveHeight / 2.0f - dy
if (baseLineY <= -waveHeight) {
//如果滚动到了顶部就移除动画。
closeAnim()
}
/* drawWave(
canvas,
dx,
context.resources.getColor(R.color.f0purple_200), baseLineY
)
drawWave(
canvas,
dx - offsetX,
context.resources.getColor(R.color.purple_200),
baseLineY - offsetY
)*/
// 遍历绘制
colors.forEachIndexed { index, color ->
drawWave(
canvas,
dx - offsetX * index,
color, baseLineY - offsetY * index
)
}
}
private fun drawWave(canvas: Canvas, offset: Int, color: Int, baseLineY: Float) {
path.reset()
canvas.save()
val startPointX = -waveWidth + offset;
path.moveTo(
startPointX,
baseLineY
)
//判断能够绘制多少个波
for (start: Int in -waveWidth.toInt() until (width + waveWidth).toInt() step waveWidth.toInt()) {
val startX = start + offset
path.quadTo(
(startX + waveWidth / 4.0f),
baseLineY - waveHeight / 2.0f,
(startX + waveWidth / 2.0f),
baseLineY
)
//下波
path.quadTo(
(startX + waveWidth * (3 / 4.0f)),
baseLineY + waveHeight / 2.0f,
(startX + waveWidth),
baseLineY
)
}
path.lineTo(width.toFloat(), height.toFloat())
path.lineTo(0f, height.toFloat())
path.close()
paint.setColor(color)
canvas.drawPath(path, paint)
canvas.restore()
}
private fun closeAnim() {
anim?.cancel()
anim = null
}
}