目录
前言
对于Android开发,实现贝塞尔曲线还是比较方便的,有对应的API供你调用。由于一阶贝塞尔曲线就是一条直线,实际没啥多大用处,因此,下面主要讲解二阶和三阶。
二阶贝塞尔曲线
在Android中,使用quadTo来实现二阶贝塞尔
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来实现三阶贝塞尔
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区分触摸点是左边还是右边 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分别为两个控制点,通过改变控制点的位置来改变三阶贝塞尔曲线的形状。
a点和b点就是固定点,c点和d点是控制点,我们可以改变c点或d点的位置来改变曲线的形状。
OK,贝塞尔曲线的基础到此就讲完了,下面来个实战,体验一下贝塞尔曲线的丝滑吧!
关于贝塞尔曲线,最典型的应用就是波浪球了,那咱们也来整一个,先上图
首先裁剪一下画布,变为圆形
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)
根据进度改变起点坐标的y值,控制点为曲线的顶部和底部,循环绘制,然后构建曲线之下的封闭区域,填充
//根据进度改变起点坐标的y值 startPoint.y = ((1 - (progress / 100.0)) * height).toInt() //移动到起点 wavePath.moveTo(startPoint.x.toFloat(), startPoint.y.toFloat()) var j = 1 //循环绘制曲线 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 } //绘制封闭的区域 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
这里是设置每隔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) { //根据进度改变起点坐标的y值 startPoint.y = ((1 - (progress / 100.0)) * height).toInt() //移动到起点 wavePath.moveTo(startPoint.x.toFloat(), startPoint.y.toFloat()) var j = 1 //循环绘制曲线 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 } //绘制封闭的区域 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 } }