Android自定義View繪制貝塞爾曲線實(shí)現(xiàn)流程
前言
對(duì)于Android開發(fā),實(shí)現(xiàn)貝塞爾曲線還是比較方便的,有對(duì)應(yīng)的API供你調(diào)用。由于一階貝塞爾曲線就是一條直線,實(shí)際沒啥多大用處,因此,下面主要講解二階和三階。
二階貝塞爾曲線
在Android中,使用quadTo來(lái)實(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),通過(guò)改變控制點(diǎn)的位置來(lái)改變二階貝塞爾曲線的形狀。

a點(diǎn)和b點(diǎn)就是固定點(diǎn),c點(diǎn)是控制點(diǎn),我們可以改變c點(diǎn)的位置來(lái)改變曲線的形狀。
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來(lái)實(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),通過(guò)改變控制點(diǎn)的位置來(lái)改變?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)的位置來(lái)改變曲線的形狀。
OK,貝塞爾曲線的基礎(chǔ)到此就講完了,下面來(lái)個(gè)實(shí)戰(zhàn),體驗(yàn)一下貝塞爾曲線的絲滑吧!
關(guān)于貝塞爾曲線,最典型的應(yīng)用就是波浪球了,那咱們也來(lái)整一個(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í)針?lè)较蚶L制,Path.Direction.CCW:沿逆時(shí)針?lè)较蚶L制
以View為中心,畫圓
canvas.drawCircle(width / 2f, height / 2f, width / 2f, circularPaint)
利用二階貝塞爾,繪制波浪,起點(diǎn)為屏幕外,circleLen為曲線1/4周期長(zhǎng)度
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周期的長(zhǎng)度
private val circleLen = DensityUtils.dp2px(context, 53)
//曲線高度
private val waveHeight = DensityUtils.dp2px(context, 27)
//默認(rèn)的長(zhǎng)寬值
private val defaultSize = DensityUtils.dp2px(context, 300)
//進(jìn)度
private var progress = 0
//平移的長(zhǎng)度
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)
//取最小的,作為長(zhǎng)寬
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)增長(zhǎng)進(jìn)度
autoGrow()
}
//進(jìn)度從0-100,自動(dòng)增長(zhǎ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)文章
利用smsmanager實(shí)現(xiàn)后臺(tái)發(fā)送短信示例
這篇文章主要介紹了android利用SmsManager可以實(shí)現(xiàn)后臺(tái)發(fā)送短信的方法,最近有使用說(shuō)明,大家可以參考使用2014-01-01
Android天氣預(yù)報(bào)之基于HttpGet對(duì)象解析天氣數(shù)據(jù)的方法
這篇文章主要介紹了Android天氣預(yù)報(bào)之基于HttpGet對(duì)象解析天氣數(shù)據(jù)的方法,非常實(shí)用的功能,需要的朋友可以參考下2014-08-08
探秘Android手勢(shì)事件機(jī)制與優(yōu)化技巧
在Android開發(fā)中,手勢(shì)操作被廣泛應(yīng)用于各種應(yīng)用場(chǎng)景,如滑動(dòng)、雙擊等。本文將介紹Android手勢(shì)事件傳遞的原理,包括手勢(shì)事件的類型、分發(fā)機(jī)制和處理流程等內(nèi)容,并提供一些優(yōu)化用戶體驗(yàn)的技巧,需要的朋友可以參考下2023-06-06
Android仿美團(tuán)淘寶實(shí)現(xiàn)多級(jí)下拉列表菜單功能
這篇文章主要介紹了Android仿美團(tuán)淘寶實(shí)現(xiàn)多級(jí)下拉列表菜單功能,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友參考下2017-01-01
Android 開機(jī)直接運(yùn)行app并當(dāng)做手機(jī)桌面的實(shí)例
今天小編就為大家分享一篇Android 開機(jī)直接運(yùn)行app并當(dāng)做手機(jī)桌面的實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-08-08
Android studio 廣播的簡(jiǎn)單使用代碼詳解
這篇文章主要介紹了Android studio 廣播的簡(jiǎn)單使用,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-04-04
android中UIColletionView瀑布流布局實(shí)現(xiàn)思路以及封裝的實(shí)現(xiàn)
本篇文章主要介紹了android中UIColletionView瀑布流布局實(shí)現(xiàn)思路以及封裝的實(shí)現(xiàn),具有一定的參考價(jià)值,有興趣的可以了解一下。<BR>2017-02-02
android端微信支付V3版本地簽名統(tǒng)一下單詳解
本篇文章主要介紹了android端微信支付V3版本地簽名統(tǒng)一下單,具有一定的參考價(jià)值,有興趣的同學(xué)可以了解一下。2016-11-11
Android實(shí)現(xiàn)加載廣告圖片和倒計(jì)時(shí)的開屏布局
這篇文章主要介紹了Android實(shí)現(xiàn)加載廣告圖片和倒計(jì)時(shí)的開屏布局,需要的朋友可以參考下2014-07-07

