Android自定义画板(一)
Android画板千千万,网上一搜一大堆,但总是找不到一个满意的,今天我们就来自己做一个画板,包括以下功能:
- 绘制任意线条
- 画笔颜色和宽度可选
- 绘制几何形状
- 包含橡皮擦功能
- 笔迹可撤销,可恢复
- 设置画布背景,比如田字格等
- 将画布内容保存为图片
- 不断增加中...
源码地址(不定期添加新功能):https://gitee.com/ZengCS/android-sketch-pad-pro
一、自定义View来做一个画板
创建一个基类画板BaseSketchView,完整代码:https://gitee.com/ZengCS/android-sketch-pad-pro/blob/master/sketchpad/src/main/java/com/zcs/lib/sketchpad/canvas/BaseSketchView.kt
- 创建一只画笔,代码中均有注释,不再一一讲解
// Paint 一只画笔
private val mPaint = Paint()
// Path 用于记录路径
val mPath = Path()
/**
* 初始化画笔,默认颜色:红色 宽度(px):10f
*/
private fun initPaint(color: Int = Color.RED, strokeWidth: Float = 10f) {
// 设置画笔颜色
mPaint.color = color
// 设置画笔宽度(单位px)
mPaint.strokeWidth = strokeWidth
// 设置画笔样式
mPaint.style = Paint.Style.STROKE
// 画笔开始和结束为圆
mPaint.strokeCap = Paint.Cap.ROUND
// 连接处为圆
mPaint.strokeJoin = Paint.Join.ROUND
// 当style为STROKE或FILL_AND_STROKE时设置连接处的倾斜度,这个值必须大于0
mPaint.strokeMiter = 1.0f
// 设置画笔透明度
mPaint.alpha = 0xFF
// 设置抗锯齿
mPaint.isAntiAlias = true
}
- 重写触摸事件 onTouchEvent(event: MotionEvent)
/**
* 重写触摸事件监听并消费掉,不让其往下传递
*/
override fun onTouchEvent(event: MotionEvent): Boolean {
// 事件处理
val needInvalidate = when (event.action) {
// 1.手指按下
MotionEvent.ACTION_DOWN -> onFingerDown(event)
// 2.手指滑动
MotionEvent.ACTION_MOVE -> onFingerMove(event)
// 3.手指抬起
MotionEvent.ACTION_UP -> onFingerUp(event)
// 默认不重绘
else -> false
}
if (needInvalidate) {
// 重绘
invalidate()
}
return true
}
/**
* 手指按下
*/
open fun onFingerDown(event: MotionEvent): Boolean {
// 每次按下的时候,将path移动到此点,否则会出现多余的直线
mPath.moveTo(event.x, event.y)
return true
}
/**
* 手指移动,必须在子类中实现,这里就是画笔迹的核心
*/
abstract fun onFingerMove(event: MotionEvent): Boolean
/**
* 手指抬起
*/
open fun onFingerUp(event: MotionEvent): Boolean {
return false
}
写到这里,我们的画板功能已经完成90%了,那么最重要的一步就是处理手指移动事件,这个需要在子类来实现,下面我们看看子类怎么实现笔迹绘制吧,代码很简单,直接贴全码:
import android.content.Context
import android.util.AttributeSet
import android.view.MotionEvent
/**
* Created by ZengCS on 2021/11/30.
* E-mail:zengcs@vip.qq.com
* Add:成都市天府软件园E3-3F
*
* desc: 普通的画板,点与点之间直接相连
*/
class NormalSketchView @JvmOverloads constructor(
context: Context?,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : BaseSketchView(context, attrs, defStyleAttr) {
// 自定义画笔颜色,默认:Color.RED
// override fun penColor(): Int = Color.BLUE
// 自定义画笔宽度,默认:10f
// override fun strokeWidth(): Float = 20f
override fun onFingerMove(event: MotionEvent): Boolean {
// 每次移动的时候,将上个点与此点连接
mPath.lineTo(event.x, event.y)
return true
}
}
只需要将每次手指移动的点与点相连即可,采用:Path.lineTo(x, y)来实现
看看效果吧:
普通画笔.jpg
- 问题出现
是哪里出问题了吗?这个笔迹一点都不圆滑
其实Path.lineTo方法是简单的将两个点进行直线相连,当我们慢慢滑动手指的时候,相对来说是圆滑的。一旦我们的手指移动速度过快,就会出现不圆滑的情况
- 寻找解决方案
Bézier curve(贝塞尔曲线)是应用于二维图形应用程序的数学曲线。 曲线定义:起始点、终止点(也称锚点)、控制点。通过调整控制点,贝塞尔曲线的形状会发生变化。 1962年,法国数学家Pierre Bézier第一个研究了这种矢量绘制曲线的方法,并给出了详细的计算公式,因此按照这样的公式绘制出来的曲线就用他的姓氏来命名,称为贝塞尔曲线。
二阶贝塞尔曲线
————————————————
详细介绍,请查看 原文链接:https://blog.csdn.net/tianhai110/article/details/2203572
-
利用Path自带API绘制贝塞尔曲线
Android Path绘制贝塞尔曲线
6.方法找到了,我们就来实现这个贝塞尔曲线
大概步骤如下:
- 手指按下时,记住点的坐标x和y,并缓存于mLastX和mLastY
- 手指移动时,计算本次滑动距离,大于等于3像素时,才考虑绘制贝塞尔曲线
- 设置贝塞尔曲线的终点坐标endX和endY为 上一个点和当前点的一半
- 绘制二次贝塞尔,实现平滑曲线,让mLastX, mLastY为操作点,endX, endY为终点
- 将当前点坐标x,y赋予mLastX和mLastY,用于下次移动时使用
- 本次move事件处理完成,等待下一个move事件触发
具体代码实现:
import android.content.Context
import android.util.AttributeSet
import android.view.MotionEvent
import kotlin.math.abs
/**
* Created by ZengCS on 2021/11/30.
* E-mail:zengcs@vip.qq.com
* Add:成都市天府软件园E3-3F
*
* desc: 在普通的画板基础之上,点与点之间使用贝塞尔曲线相连
*/
class BezierSketchView @JvmOverloads constructor(
context: Context?,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : BaseSketchView(context, attrs, defStyleAttr) {
// 记录上一个点的坐标
private var mLastX = 0f
private var mLastY = 0f
override fun onFingerDown(event: MotionEvent): Boolean {
// 缓存本次点坐标
mLastX = event.x
mLastY = event.y
return super.onFingerDown(event)
}
// 手指在屏幕上滑动时调用
override fun onFingerMove(event: MotionEvent): Boolean {
val currX = event.x
val currY = event.y
// 计算本次滑动距离
val distanceX = abs(currX - mLastX)
val distanceY = abs(currY - mLastY)
// 如果本次移动的距离>=3px时,绘制贝塞尔曲线
if (distanceX >= 3 || distanceY >= 3) {
// 设置贝塞尔曲线的终点坐标为 上一个点和当前点的一半
val endX = (currX + mLastX) / 2
val endY = (currY + mLastY) / 2
// 绘制二次贝塞尔,实现平滑曲线;mLastX, mLastY为操作点,endX, endY为终点
mPath.quadTo(mLastX, mLastY, endX, endY)
// 第二次执行时,第一次结束调用的坐标值将作为第二次调用的初始坐标值
this.mLastX = currX
this.mLastY = currY
}
return true
}
}
看看效果吧:真圆滑!!
贝塞尔画笔.jpg
源码地址(不定期添加新功能):https://gitee.com/ZengCS/android-sketch-pad-pro