網(wǎng)站首頁 編程語言 正文
前言
對于Android開發(fā),實(shí)現(xiàn)貝塞爾曲線還是比較方便的,有對應(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為兩個固定點(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為兩個固定點(diǎn),fixedX1和fixedY1,fixedX2和fixedY2分別為兩個控制點(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ǔ)到此就講完了,下面來個實(shí)戰(zhàn),體驗(yàn)一下貝塞爾曲線的絲滑吧!
關(guān)于貝塞爾曲線,最典型的應(yīng)用就是波浪球了,那咱們也來整一個,先上圖
首先裁剪一下畫布,變?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)
利用二階貝塞爾,繪制波浪,起點(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()
//移動到起點(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)
//自動增長進(jìn)度
autoGrow()
}
//進(jìn)度從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ù)進(jìn)度改變起點(diǎn)坐標(biāo)的y值
startPoint.y = ((1 - (progress / 100.0)) * height).toInt()
//移動到起點(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
}
}
原文鏈接:https://blog.csdn.net/qq_45485851/article/details/124692717
相關(guān)推薦
- 2022-09-08 Python數(shù)據(jù)分析基礎(chǔ)之異常值檢測和處理方式_python
- 2022-10-05 Python數(shù)據(jù)可視化制作全球地震散點(diǎn)圖_python
- 2022-04-08 Unity?UGUI?按鈕綁定事件的?4?種方式匯總_C#教程
- 2022-11-23 Android?IdleHandler基本使用及應(yīng)用案例詳解_Android
- 2023-02-27 nginx編譯安裝及常用參數(shù)詳解_nginx
- 2022-07-22 jQuery實(shí)現(xiàn)點(diǎn)擊顯示密碼框密碼
- 2022-03-18 .NET?6開發(fā)TodoList應(yīng)用之使用MediatR實(shí)現(xiàn)POST請求_實(shí)用技巧
- 2022-09-25 Dynamic-DataSource多數(shù)據(jù)源配置mybatis/mybatis-plus
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運(yùn)行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運(yùn)算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實(shí)現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡單動態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊(duì)列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支