Android自定義View繪制貝塞爾曲線實現(xiàn)流程
前言
對于Android開發(fā),實現(xiàn)貝塞爾曲線還是比較方便的,有對應的API供你調(diào)用。由于一階貝塞爾曲線就是一條直線,實際沒啥多大用處,因此,下面主要講解二階和三階。
二階貝塞爾曲線
在Android中,使用quadTo來實現(xiàn)二階貝塞爾
path.reset()
path.moveTo(startX, startY)
path.quadTo(currentX, currentY, endX, endY)
canvas.drawPath(path, curvePaint)
startX和startY,endX和endY為兩個固定點,currentX和currentY就是控制點,通過改變控制點的位置來改變二階貝塞爾曲線的形狀。

a點和b點就是固定點,c點是控制點,我們可以改變c點的位置來改變曲線的形狀。
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來實現(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ū)分觸摸點是左邊還是右邊
if (event.x < divideLine) {
fixedX1 = event.x
fixedY1 = event.y
} else {
fixedX2 = event.x
fixedY2 = event.y
}
postInvalidate()
}
}
return true
}其中,startX和startY,endX和endY為兩個固定點,fixedX1和fixedY1,fixedX2和fixedY2分別為兩個控制點,通過改變控制點的位置來改變?nèi)A貝塞爾曲線的形狀。

a點和b點就是固定點,c點和d點是控制點,我們可以改變c點或d點的位置來改變曲線的形狀。
OK,貝塞爾曲線的基礎(chǔ)到此就講完了,下面來個實戰(zhàn),體驗一下貝塞爾曲線的絲滑吧!
關(guān)于貝塞爾曲線,最典型的應用就是波浪球了,那咱們也來整一個,先上圖

首先裁剪一下畫布,變?yōu)閳A形
val circlePath = Path()
circlePath.addCircle(width / 2f, height / 2f, width / 2f, Path.Direction.CW)
canvas.clipPath(circlePath)
Path.Direction.CW:沿順時針方向繪制,Path.Direction.CCW:沿逆時針方向繪制
以View為中心,畫圓
canvas.drawCircle(width / 2f, height / 2f, width / 2f, circularPaint)
利用二階貝塞爾,繪制波浪,起點為屏幕外,circleLen為曲線1/4周期長度
private val startPoint = Point(-4 * circleLen, 0)
根據(jù)進度改變起點坐標的y值,控制點為曲線的頂部和底部,循環(huán)繪制,然后構(gòu)建曲線之下的封閉區(qū)域,填充
//根據(jù)進度改變起點坐標的y值
startPoint.y = ((1 - (progress / 100.0)) * height).toInt()
//移動到起點
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()
//走完一周回到原點
startPoint.x =
if (startPoint.x + translateX >= 0) -circleLen * 4 else startPoint.x + translateX這里是設(shè)置每隔100ms,進度加一
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)
//默認的長寬值
private val defaultSize = DensityUtils.dp2px(context, 300)
//進度
private var progress = 0
//平移的長度
private val translateX = circleLen / 4
//圓形Paint
private val circularPaint = Paint()
//波浪Paint
private val wavePaint = Paint()
//波浪的路徑
private val wavePath = Path()
//曲線的起始坐標
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)
//自動增長進度
autoGrow()
}
//進度從0-100,自動增長
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ù)進度改變起點坐標的y值
startPoint.y = ((1 - (progress / 100.0)) * height).toInt()
//移動到起點
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()
//走完一周回到原點
startPoint.x =
if (startPoint.x + translateX >= 0) -circleLen * 4 else startPoint.x + translateX
}
}到此這篇關(guān)于Android自定義View繪制貝塞爾曲線實現(xiàn)流程的文章就介紹到這了,更多相關(guān)Android貝塞爾曲線內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
詳解Android ContentProvider的基本原理和使用
ContentProvider(內(nèi)容提供者)是 Android 的四大組件之一,管理 Android 以結(jié)構(gòu)化方式存放的數(shù)據(jù),以相對安全的方式封裝數(shù)據(jù)(表)并且提供簡易的處理機制和統(tǒng)一的訪問接口供其他程序調(diào)用2021-06-06
Android 自定義密碼輸入框?qū)崿F(xiàn)代碼
最近做個項目自定義密碼輸入框功能,下面小編把實現(xiàn)思路分享到腳本之家平臺,需要的朋友參考下吧2018-03-03
Android AlertDialog多種創(chuàng)建方式案例詳解
這篇文章主要介紹了Android AlertDialog多種創(chuàng)建方式案例詳解,本篇文章通過簡要的案例,講解了該項技術(shù)的了解與使用,以下就是詳細內(nèi)容,需要的朋友可以參考下2021-08-08
Android Activity啟動模式之singleTask實例詳解
這篇文章主要介紹了Android Activity啟動模式之singleTask,結(jié)合實例形式較為詳細的分析了singleTask模式的功能、使用方法與相關(guān)注意事項,需要的朋友可以參考下2016-01-01

