學習kotlin把以前的一些知識重新理了一遍
首先對于需要大量繪制的操作,不能直接繪制,使用 SurfaceView,直接把繪制作業放到子執行緒中去操作,否則繪制作業加大一下,會卡頓,不過SurfaceView是獨立的一層View,不能平移,縮放,旋轉或者設定透明度等,如果需要這些操作可以考慮使用TextureView,這兩者都是可以通過回呼在子執行緒中去更新UI

思路:
使用二級貝塞爾曲線來完成,簡單的把螢屏對半,然后各用貝塞爾去繪制一個扇形,在到螢屏外復制一份,改變控制點,達到可回圈移動的效果
對比以前:
簡化計算邏輯,直接回圈計算控制點,重復利用執行緒暫停恢復操作,利用插值器替代直接計算移動的坐標(對于不同的場景可以更換不同的插值器),抽離繪制邏輯
流程:
1.從繁化簡,首先不考慮其它,一級貝塞爾是一條直線,兩個控制點,二級貝塞爾多了一個控制點,通過控制點利用公式演算法繪制出形狀,下面是直接一個貝塞爾梭哈

假設起點(0,0)螢屏的四分之一就是第二個點,螢屏的二分之一就是第三個點,然后加個二級貝塞爾,扇形就成了
path.reset()
path.moveTo(starPoint[0], starPoint[1])
path.quadTo(quadPoint[0], quadPoint[1], quadPoint[2], quadPoint[3])
canvas.drawPath(path, paint)
2.繪制第二個扇形,同樣的扇形,但是方向反一下(改變控制點),path會把第一個連接點就是第一個扇形的最后一個控制點當作第二個扇形的起點,第二個坐標就是螢屏的四分之三,第三個坐標就是螢屏的寬度,然后連起來就是兩個相連接的扇形,看起來不太像,這里只有線條

path.reset() path.moveTo(starPoint[0], starPoint[1]) path.quadTo(quad1Point[0], quad1Point[1], quad1Point[2], quad1Point[3]) path.quadTo(quad2Point[0], quad2Point[1], quad2Point[2], quad2Point[3]) canvas.drawPath(path, paint)
3.把扇形閉合一下,畫筆設定為填充,這樣看起來有點樣子了,如果能讓這個波浪動起來,基本的效果也就有了,可以改變x坐標移動它們的位置,但是就兩個波浪,移動后很顯然還達不到想要的效果,這時候需要在繪制兩條一模一樣的扇形,然后
回圈切換

paint.style = Paint.Style.FILL //閉合區域 path.lineTo(mWidth, mHeight) path.lineTo(0f, mHeight) path.close()
4.繪制移動的波浪,假設左邊還有一個螢屏,也分為四等分,以此類推,這樣波浪就繪制完成了

5.改變所有波浪的x的坐標,讓它從左往右移動,到臨界點在置為0,重復操作,這里可以交給插值器去做,直接從0開始移動,臨界點是螢屏的寬度,剛好兩個波浪的距離

不用插值器也行,直接自己計算一下,可以看到,動起來了

我這邊為了更好的看到效果,我給每個x坐標控制點都繪制了一個小圓點,這樣看起來就更直觀一點,也有利于除錯一些bug,也可以把坐標都列印出來,方便計算,然后邏輯簡化了一下,上面是直接把每個點坐標都一條一條的去繪制,但是由于未了達到無線波浪的效果,所以其實就是兩個螢屏,一樣的波浪,復制了一份,所以可以利用一個回圈,從左到右,動態的改變它的坐標去繪制出兩個螢屏一樣的扇形(i*width)


然后可以看到答應出來的坐標資訊

y坐標基本可以不看,y坐標主要控制扇形的弧度,因為是相反的兩個扇形,所以一個高一個低,然后x坐標根據螢屏的寬度,分了四等份,在用貝塞爾曲線連接起來,這樣一看就清晰多了
因為繪制了圓點,所以看起來視覺上比較生硬,如果把圓點去掉就絲滑多了

這里初步的思路就完成了
class DrawWaterUtils(private val holder: SurfaceHolder) : Thread() { private var mWidth: Float = 0f private var mHeight: Float = 0f //波浪的高度 private var mWaterHeight: Float = 0f //起伏的高度 private var mWaterUp: Float = 0f /** * off = 偏移值 */ private var offx: Float = 0f private var path: Path = Path() private var paint: Paint = Paint() private var isRun: Boolean = false private var circlePaint: Paint = Paint() private lateinit var mValueAnimator: ValueAnimator init { // 去除畫筆鋸齒 paint.isAntiAlias = true // 設定風格為實線 paint.style = Paint.Style.FILL paint.strokeWidth = 2f circlePaint.style = Paint.Style.FILL } fun init(width: Int, height: Int) { mWidth = width.toFloat() mHeight = height.toFloat() //線性差值器 mValueAnimator = ValueAnimator.ofFloat(0f, mWidth) mValueAnimator.duration = 1700 mValueAnimator.interpolator = LinearInterpolator() mValueAnimator.repeatCount = ValueAnimator.INFINITE mValueAnimator.addUpdateListener { animation -> offx = animation.animatedValue as Float } //波浪的高度 mWaterHeight = mHeight / 2 //起伏的高度 mWaterUp = mWaterHeight / 2 } /** 啟動執行緒 */ fun runDraw() { isRun = true start() mValueAnimator.start() } /** 恢復執行緒 */ fun resumeThread() { synchronized(this) { isRun = true notify() mValueAnimator.start() } } /** 暫停執行緒 */ fun stopDraw() { isRun = false mValueAnimator.cancel() } override fun run() { synchronized(this) { while (true) { if (!isRun) { wait() } val canvas = holder.lockCanvas() if (canvas != null) { //清除畫布 canvas.drawColor( KtxProvider.mContext.getColor(R.color.text_color_titleBar_title), android.graphics.PorterDuff.Mode.CLEAR ) paint.color = KtxProvider.mContext.getColor(R.color.bg_color_3159c7_83) circlePaint.color = KtxProvider.mContext.getColor(R.color.black) water(canvas) //回圈起伏 // offx += 3 // if (offx >= mWidth) { // offx = 0f // } // 解除鎖定,并提交修改內容 holder.unlockCanvasAndPost(canvas) } } } } /** 繪制波浪 */ private fun water(canvas: Canvas) { path.reset() //起點 path.moveTo(-mWidth + offx, mWaterHeight) //波浪的數量 for (i in 0 until 2) { path.quadTo( -mWidth * 3 / 4 + i * mWidth + offx, mWaterHeight - mWaterUp, -mWidth / 2 + i * mWidth + offx, mWaterHeight ) //canvas.drawCircle(-mWidth * 3 / 4 + i * mWidth + offx, mWaterHeight - mWaterUp,5f,circlePaint) //canvas.drawCircle(-mWidth / 2 + i * mWidth + offx, mWaterHeight,5f,circlePaint) path.quadTo( -mWidth / 4 + i * mWidth + offx, mWaterHeight + mWaterUp, i * mWidth + offx, mWaterHeight ) //canvas.drawCircle(-mWidth / 4 + i * mWidth + offx, mWaterHeight + mWaterUp,5f,circlePaint) //canvas.drawCircle(i * mWidth + offx, mWaterHeight,5f,circlePaint) Log.e( "===\n", "i = $i\n" + "x1 ${-mWidth * 3 / 4 + i * mWidth} y1 ${mWaterHeight - mWaterUp} x2 ${-mWidth / 2 + i * mWidth} y2 $mWaterHeight \n" + "x1 ${-mWidth / 4 + i * mWidth} y1 ${mWaterHeight + mWaterUp} x2 ${i * mWidth} y2 $mWaterHeight" ) } //閉合操作 path.lineTo(mWidth, mHeight) path.lineTo(0f, mHeight) path.close() canvas.drawPath(path, paint) } }View Code
/** * created by YooJin. * date: 2021/2/4 14:44 * desc:波浪影片 */ class WaterBgSurfaceView(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : SurfaceView(context, attrs, defStyleAttr), SurfaceHolder.Callback { init { init() } constructor(context: Context, attrs: AttributeSet?):this(context, attrs, 0) constructor(context: Context):this(context, null) private lateinit var drawWater: DrawWaterUtils private fun init() { holder.addCallback(this) //透明處理 holder.setFormat(PixelFormat.TRANSPARENT) setZOrderOnTop(true) drawWater = DrawWaterUtils(holder) } @SuppressLint("DrawAllocation") override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { super.onLayout(changed, left, top, right, bottom) if (changed){ drawWater.init(width,height) } } override fun onDraw(canvas: Canvas?) { super.onDraw(canvas) } override fun surfaceCreated(p0: SurfaceHolder) { //Log.e("===","surfaceCreated drawWater.isAlive ${drawWater.isAlive}") if (!drawWater.isAlive) { //Log.e("===","run") drawWater.runDraw() }else{ //Log.e("===","notify") drawWater.resumeThread() } } override fun surfaceChanged(p0: SurfaceHolder, p1: Int, p2: Int, p3: Int) { } override fun surfaceDestroyed(p0: SurfaceHolder) { //Log.e("===","surfaceDestroyed") drawWater.stopDraw() } }View Code
這里雖然很絲滑,不過surfaceview也有缺點,就是不能像一般的view一樣去設定偏移旋轉等影片透明度啥的,如果你的繪制不復雜,直接draw就行了,或者可以使用 TextureView 去實作,專案里也有一套TextureView的繪制類,沒什么大的區別,下面是專案地址
github:https://github.com/1024477951/KotlinStrong
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/258357.html
標籤:其他
上一篇:華宇拼音不錯,QT的
