Android自定義View繪制貝塞爾曲線實(shí)現(xiàn)流程
前言
對(duì)于Android開發(fā),實(shí)現(xiàn)貝塞爾曲線還是比較方便的,有對(duì)應(yīng)的API供你調(diào)用。由于一階貝塞爾曲線就是一條直線,實(shí)際沒啥多大用處,因此,下面主要講解二階和三階。
二階貝塞爾曲線
在Android中,使用quadTo來實(shí)現(xiàn)二階貝塞爾
path.reset() path.moveTo(startX, startY) path.quadTo(currentX, currentY, endX, endY) canvas.drawPath(path, curvePaint)
startX和startY,endX和endY為兩個(gè)固定點(diǎn),currentX和currentY就是控制點(diǎn),通過改變控制點(diǎn)的位置來改變二階貝塞爾曲線的形狀。
a點(diǎn)和b點(diǎn)就是固定點(diǎn),c點(diǎn)是控制點(diǎn),我們可以改變c點(diǎn)的位置來改變曲線的形狀。
override fun onTouchEvent(event: MotionEvent): Boolean { when (event.action) { MotionEvent.ACTION_DOWN, MotionEvent.ACTION_MOVE -> { currentX = event.x currentY = event.y postInvalidate() } } return true }
三階貝塞爾曲線
在Android中,使用cubicTo來實(shí)現(xiàn)三階貝塞爾
override fun onDraw(canvas: Canvas) { super.onDraw(canvas) path.reset() path.moveTo(startX, startY) path.cubicTo(fixedX1, fixedY1, fixedX2, fixedY2, endX, endY) canvas.drawPath(path, curvePaint) //繪制輔助線 drawHelpLine(canvas) } override fun onTouchEvent(event: MotionEvent): Boolean { when (event.action) { MotionEvent.ACTION_DOWN, MotionEvent.ACTION_MOVE -> { //divideLine區(qū)分觸摸點(diǎn)是左邊還是右邊 if (event.x < divideLine) { fixedX1 = event.x fixedY1 = event.y } else { fixedX2 = event.x fixedY2 = event.y } postInvalidate() } } return true }
其中,startX和startY,endX和endY為兩個(gè)固定點(diǎn),fixedX1和fixedY1,fixedX2和fixedY2分別為兩個(gè)控制點(diǎn),通過改變控制點(diǎn)的位置來改變?nèi)A貝塞爾曲線的形狀。
a點(diǎn)和b點(diǎn)就是固定點(diǎn),c點(diǎn)和d點(diǎn)是控制點(diǎn),我們可以改變c點(diǎn)或d點(diǎn)的位置來改變曲線的形狀。
OK,貝塞爾曲線的基礎(chǔ)到此就講完了,下面來個(gè)實(shí)戰(zhàn),體驗(yàn)一下貝塞爾曲線的絲滑吧!
關(guān)于貝塞爾曲線,最典型的應(yīng)用就是波浪球了,那咱們也來整一個(gè),先上圖
首先裁剪一下畫布,變?yōu)閳A形
val circlePath = Path() circlePath.addCircle(width / 2f, height / 2f, width / 2f, Path.Direction.CW) canvas.clipPath(circlePath)
Path.Direction.CW:沿順時(shí)針方向繪制,Path.Direction.CCW:沿逆時(shí)針方向繪制
以View為中心,畫圓
canvas.drawCircle(width / 2f, height / 2f, width / 2f, circularPaint)
利用二階貝塞爾,繪制波浪,起點(diǎn)為屏幕外,circleLen為曲線1/4周期長度
private val startPoint = Point(-4 * circleLen, 0)
根據(jù)進(jìn)度改變起點(diǎn)坐標(biāo)的y值,控制點(diǎn)為曲線的頂部和底部,循環(huán)繪制,然后構(gòu)建曲線之下的封閉區(qū)域,填充
//根據(jù)進(jìn)度改變起點(diǎn)坐標(biāo)的y值 startPoint.y = ((1 - (progress / 100.0)) * height).toInt() //移動(dòng)到起點(diǎn) wavePath.moveTo(startPoint.x.toFloat(), startPoint.y.toFloat()) var j = 1 //循環(huán)繪制曲線 for (i in 1..8) { val controlX = (startPoint.x + circleLen * j).toFloat() //波頂和波底 val controlY = if (i % 2 == 0) (startPoint.y + waveHeight).toFloat() else (startPoint.y - waveHeight).toFloat() //二階貝塞爾 wavePath.quadTo( controlX, controlY, (startPoint.x + circleLen * 2 * i).toFloat(), startPoint.y.toFloat() ) j += 2 } //繪制封閉的區(qū)域 wavePath.lineTo(width.toFloat(), height.toFloat()) wavePath.lineTo(startPoint.x.toFloat(), height.toFloat()) wavePath.lineTo(startPoint.x.toFloat(), startPoint.y.toFloat()) wavePath.close() canvas.drawPath(wavePath, wavePaint) wavePath.reset() //走完一周回到原點(diǎn) startPoint.x = if (startPoint.x + translateX >= 0) -circleLen * 4 else startPoint.x + translateX
這里是設(shè)置每隔100ms,進(jìn)度加一
progress = if (progress >= 100) 0 else progress + 1 postInvalidateDelayed(100)
全部代碼如下
class ProgressBallView : View { //曲線1/4周期的長度 private val circleLen = DensityUtils.dp2px(context, 53) //曲線高度 private val waveHeight = DensityUtils.dp2px(context, 27) //默認(rèn)的長寬值 private val defaultSize = DensityUtils.dp2px(context, 300) //進(jìn)度 private var progress = 0 //平移的長度 private val translateX = circleLen / 4 //圓形Paint private val circularPaint = Paint() //波浪Paint private val wavePaint = Paint() //波浪的路徑 private val wavePath = Path() //曲線的起始坐標(biāo) private val startPoint = Point(-4 * circleLen, 0) constructor(context: Context) : super(context) constructor(context: Context, attributeSet: AttributeSet) : super(context, attributeSet) { initPaint() } private fun initPaint() { with(circularPaint) { isAntiAlias = true color = Color.GRAY } with(wavePaint) { isAntiAlias = true color = Color.RED } } override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { super.onMeasure(widthMeasureSpec, heightMeasureSpec) var viewWidth = measureView(widthMeasureSpec) var viewHeight = measureView(heightMeasureSpec) //取最小的,作為長寬 viewWidth = min(viewWidth, viewHeight) viewHeight = viewWidth setMeasuredDimension(viewWidth, viewHeight) } private fun measureView(measureSpec: Int): Int { val mode = MeasureSpec.getMode(measureSpec) return if (mode == MeasureSpec.AT_MOST || mode == MeasureSpec.EXACTLY) { MeasureSpec.getSize(measureSpec) } else { defaultSize } } override fun onDraw(canvas: Canvas) { super.onDraw(canvas) //裁剪畫布為圓形 cutCanvas(canvas) //繪制圓形 drawRound(canvas) //繪制波浪 drawWave(canvas) //自動(dòng)增長進(jìn)度 autoGrow() } //進(jìn)度從0-100,自動(dòng)增長 private fun autoGrow() { progress = if (progress >= 100) 0 else progress + 1 postInvalidateDelayed(100) } //裁剪畫布為圓形 private fun cutCanvas(canvas: Canvas) { val circlePath = Path() circlePath.addCircle(width / 2f, height / 2f, width / 2f, Path.Direction.CW) canvas.clipPath(circlePath) } //繪制圓形 private fun drawRound(canvas: Canvas) { canvas.drawCircle(width / 2f, height / 2f, width / 2f, circularPaint) } //繪制波浪 private fun drawWave(canvas: Canvas) { //根據(jù)進(jìn)度改變起點(diǎn)坐標(biāo)的y值 startPoint.y = ((1 - (progress / 100.0)) * height).toInt() //移動(dòng)到起點(diǎn) wavePath.moveTo(startPoint.x.toFloat(), startPoint.y.toFloat()) var j = 1 //循環(huán)繪制曲線 for (i in 1..8) { val controlX = (startPoint.x + circleLen * j).toFloat() //波頂和波底 val controlY = if (i % 2 == 0) (startPoint.y + waveHeight).toFloat() else (startPoint.y - waveHeight).toFloat() //二階貝塞爾 wavePath.quadTo( controlX, controlY, (startPoint.x + circleLen * 2 * i).toFloat(), startPoint.y.toFloat() ) j += 2 } //繪制封閉的區(qū)域 wavePath.lineTo(width.toFloat(), height.toFloat()) wavePath.lineTo(startPoint.x.toFloat(), height.toFloat()) wavePath.lineTo(startPoint.x.toFloat(), startPoint.y.toFloat()) wavePath.close() canvas.drawPath(wavePath, wavePaint) wavePath.reset() //走完一周回到原點(diǎn) startPoint.x = if (startPoint.x + translateX >= 0) -circleLen * 4 else startPoint.x + translateX } }
到此這篇關(guān)于Android自定義View繪制貝塞爾曲線實(shí)現(xiàn)流程的文章就介紹到這了,更多相關(guān)Android貝塞爾曲線內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android使用Handler實(shí)現(xiàn)倒計(jì)時(shí)功能
這篇文章主要為大家詳細(xì)介紹了Android使用Handler實(shí)現(xiàn)倒計(jì)時(shí)功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-06-06Android實(shí)現(xiàn)自動(dòng)輪播圖效果
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)自動(dòng)輪播圖效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-11-11Android個(gè)人中心的頭像上傳,圖片編碼及截取實(shí)例
本篇文章主要介紹了Android個(gè)人中心的頭像上傳,圖片編碼及截取實(shí)例,非常具有實(shí)用價(jià)值,需要的朋友可以參考下。2016-12-12詳解Android ContentProvider的基本原理和使用
ContentProvider(內(nèi)容提供者)是 Android 的四大組件之一,管理 Android 以結(jié)構(gòu)化方式存放的數(shù)據(jù),以相對(duì)安全的方式封裝數(shù)據(jù)(表)并且提供簡易的處理機(jī)制和統(tǒng)一的訪問接口供其他程序調(diào)用2021-06-06Android 自定義密碼輸入框?qū)崿F(xiàn)代碼
最近做個(gè)項(xiàng)目自定義密碼輸入框功能,下面小編把實(shí)現(xiàn)思路分享到腳本之家平臺(tái),需要的朋友參考下吧2018-03-03Android AlertDialog多種創(chuàng)建方式案例詳解
這篇文章主要介紹了Android AlertDialog多種創(chuàng)建方式案例詳解,本篇文章通過簡要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-08-08Android Activity啟動(dòng)模式之singleTask實(shí)例詳解
這篇文章主要介紹了Android Activity啟動(dòng)模式之singleTask,結(jié)合實(shí)例形式較為詳細(xì)的分析了singleTask模式的功能、使用方法與相關(guān)注意事項(xiàng),需要的朋友可以參考下2016-01-01