本文主要参考 Android 自定义view实现水波纹效果
思路基本如下:
1、确定水波函数方程
是一个标准的正余弦函数:**y = Asin(wx+b)+h **,
其中,w影响周期,A影响振幅,h影响y位置,b为初相
周期:(2π-b)/w,
2、根据函数方程得出每一个波纹上点的坐标
一个viewWidth是一个周期
将每个像素点的初始Y值存入sourcePoint
W= Math.PI*2/ viewWidth
for (i in 0 until viewWidth){
sourcePoint[i]=A*Math.sin(W*i) + H
}
之后,每次刷新的时候,根据offset,将Y值copy到destPoint
private fun resetDestPoint() {
val oneYInterval=sourcePoint.size-oneOffset
System.arraycopy(sourcePoint,oneOffset,destOnePoint,0,oneYInterval)
System.arraycopy(sourcePoint,0,destOnePoint,oneYInterval,oneOffset)
val twoYInterval=sourcePoint.size-twoOffset
System.arraycopy(sourcePoint,twoOffset,destTwoPoint,0,twoYInterval)
System.arraycopy(sourcePoint,0,destTwoPoint,twoYInterval,twoOffset)
}
3、绘制
其中波纹上每个点的坐标是(i,destPoint[i])
for (i in 0 until viewWidth){
canvas.drawLine(i.toFloat(), viewHeight-destOnePoint[i]!!.toFloat()-40, i.toFloat(),viewHeight-destOnePoint[i]!!.toFloat(),wavePaintOne)
canvas.drawLine(i.toFloat(), viewHeight-destTwoPoint[i]!!.toFloat()-40, i.toFloat(),viewHeight-destOnePoint[i]!!.toFloat(),wavePaintTwo)
}
4、水波平移,调用 invalidate()方法,
在每次刷新的时候都有改变offset的值
oneOffset+=10
twoOffset+=5
通过他们就可以改变destOnePoint、destTwoPoint的值
代码如下:
class DynamicWave : View {
constructor(context:Context) : super(context){ initView() }
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs){ initView() }
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr){ initView() }
var viewWidth:Int=0
var viewHeight:Int=0
var sourcePoint:Array<Double?> = emptyArray()
var destOnePoint:Array<Double?> = emptyArray()
var destTwoPoint:Array<Double?> = emptyArray()
val A:Int=10
var W:Double=0.0
val H:Int=40
var oneOffset=0
var twoOffset=0
var wavePaintOne:Paint?=null
var wavePaintTwo:Paint?=null
var paintFilter:PaintFlagsDrawFilter?=null
private fun initView() {
wavePaintOne= Paint(Paint.ANTI_ALIAS_FLAG)
wavePaintOne?.apply {
color=Color.GREEN
style=Paint.Style.FILL
alpha=60
}
wavePaintTwo= Paint(Paint.ANTI_ALIAS_FLAG)
wavePaintTwo?.apply {
color=Color.GRAY
style=Paint.Style.FILL
alpha= 60
}
paintFilter=PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG or Paint.FILTER_BITMAP_FLAG)
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
viewWidth=w
viewHeight=h
if (viewWidth==0){
return
}
sourcePoint= arrayOfNulls(viewWidth)
destOnePoint= arrayOfNulls(viewWidth)
destTwoPoint= arrayOfNulls(viewWidth)
W= Math.PI*2/ viewWidth
for (i in 0 until viewWidth){
sourcePoint[i]=A*Math.sin(W*i) + H
}
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
canvas.drawFilter=paintFilter
resetDestPoint()
for (i in 0 until viewWidth){
canvas.drawLine(i.toFloat(), viewHeight-destOnePoint[i]!!.toFloat()-40, i.toFloat(),viewHeight-destOnePoint[i]!!.toFloat(),wavePaintOne)
canvas.drawLine(i.toFloat(), viewHeight-destTwoPoint[i]!!.toFloat()-40, i.toFloat(),viewHeight-destOnePoint[i]!!.toFloat(),wavePaintTwo)
}
oneOffset+=10
twoOffset+=5
if (oneOffset>=viewWidth){
oneOffset=0
}
if (twoOffset>=viewWidth){
twoOffset=0
}
invalidate()
}
private fun resetDestPoint() {
val oneYInterval=sourcePoint.size-oneOffset
System.arraycopy(sourcePoint,oneOffset,destOnePoint,0,oneYInterval)
System.arraycopy(sourcePoint,0,destOnePoint,oneYInterval,oneOffset)
val twoYInterval=sourcePoint.size-twoOffset
System.arraycopy(sourcePoint,twoOffset,destTwoPoint,0,twoYInterval)
System.arraycopy(sourcePoint,0,destTwoPoint,twoYInterval,twoOffset)
}
}
5、kotlin
emptyArray()
arrayOfNulls(viewWidth)
for (i in 0 until size){}
参考: