上图效果图
device-2023-04-27-142045
上代码:
class WaterView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
private val wavePaint = Paint(Paint.ANTI_ALIAS_FLAG)
// 定义一个Path对象用来绘制波浪的路径;
private val wavePath = Path()
private var waveHeight = 30f // 波浪高度
private var waveWidth = screenWidth.toFloat() // 波长
private var waveOffset = 0f // 相位
private var halfWaveWidth:Float = 0f
private var midY:Float = 0f
init {
wavePaint.color = Color.BLUE
wavePaint.style = Paint.Style.FILL
halfWaveWidth = waveWidth / 2
midY = screenHeight / 2f
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
// waveWidth = w.toFloat()
}
override fun onDraw(canvas: Canvas?) {
wavePath.reset()
//x从屏幕外,直到走到0点
Log.d("JMGG", "onDraw: moveTo${-waveWidth + waveOffset} $midY")
wavePath.moveTo(-waveWidth + waveOffset, midY)
// 计算出正弦函数的值作为波浪高度,并且根据周期和相位等因素来修改波浪形状
for (i in -waveWidth.toInt() until width step waveWidth.toInt()) {
wavePath.rQuadTo(halfWaveWidth / 2, waveHeight, halfWaveWidth, 0f)
wavePath.rQuadTo(halfWaveWidth / 2, -waveHeight, halfWaveWidth, 0f)
}
// 使用Path.lineTo()方法将计算得到的每个点添加到Path中,
// 然后使用Canvas.drawPath()方法将Path绘制出来;
wavePath.lineTo(width.toFloat(), height.toFloat())
wavePath.lineTo(0f, height.toFloat())
wavePath.close()
// 绘制渐变
//在onDraw()方法中创建一个LinearGradient对象,并设置startColor、endColor和循环模式。
val shader = LinearGradient(
0f, height / 2f - waveHeight,
0f, height.toFloat(),
intArrayOf(Color.parseColor("#3300CCFF"), Color.TRANSPARENT),
floatArrayOf(0f,1f),
Shader.TileMode.CLAMP
)
// 将该Gradient传入Paint对象的shader属性,从而使渐变效果应用到波浪上面。
wavePaint.shader = shader
// 绘制阴影
// 在Paint对象上调用setShadowLayer()方法,添加阴影的效果。
wavePaint.setShadowLayer(dip(4).toFloat(), 0f, 0f, Color.parseColor("#44000000"))
canvas?.drawPath(wavePath, wavePaint)
// 最后绘制波浪到画布上,然后清除shader属性和shadow layer。
wavePaint.shader = null
wavePaint.clearShadowLayer()
}
// 使用ValueAnimator或者ObjectAnimator等动画库来产生动态波浪效果。
fun startAnimation() {
val animator = ObjectAnimator.ofFloat(this, "waveOffset", 0f, waveWidth)
animator.duration = 1600
animator.repeatCount = ValueAnimator.INFINITE
animator.interpolator = LinearInterpolator()
animator.start()
}
fun setWaveOffset(value: Float) {
waveOffset = value
invalidate()
}
}
上思路:
先说能学到得东西:正弦函数(sin),了解贝塞尔曲线的绘制,动画的添加。
如果在activity中调用startAnimation()方法,是不会有动画的,在理清思路前建议先别加,来看看草图,正弦函数图解也就是第二、三、四、五步骤一个规律的波浪线。
这里的绘制步骤有8步,可以算成三个屏幕,然后连接成在一起
第一步:将点移动到屏幕外一点
wavePath.moveTo(-waveWidth + waveOffset, midY)
第二步:绘制贝塞尔曲线,取了屏幕的一半的一半,然后像y轴走了30f,这里为什么是正的呢?因为自定义view绘制的坐标轴为:
所以看起30f是向下的。
wavePath.rQuadTo(halfWaveWidth / 2, waveHeight, halfWaveWidth, 0f)
第三步:rQuadTo是累加的,所以这里就到了屏幕的右边的一半的一半,y轴走了-30f,和第二部相反向上面去了
wavePath.rQuadTo(halfWaveWidth / 2, -waveHeight, halfWaveWidth, 0f)
第四步:
第五步:
重复二三步骤进行累加,绘制超过屏幕外,如果不超过会很鬼畜,可以把for循环关掉试试
for (i in -waveWidth.toInt() until width step waveWidth.toInt()) {
wavePath.rQuadTo(halfWaveWidth / 2, waveHeight, halfWaveWidth, 0f)
wavePath.rQuadTo(halfWaveWidth / 2, -waveHeight, halfWaveWidth, 0f)
}
第六步:
第七步:
连接底部的两条线
wavePath.lineTo(width.toFloat(), height.toFloat())
wavePath.lineTo(0f, height.toFloat())
第八步:
连接到起点实现闭合。
wavePath.close()