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

  1. 创建一只画笔,代码中均有注释,不再一一讲解
// 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
}
  1. 重写触摸事件 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:[email protected]
 * 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
  1. 问题出现
    是哪里出问题了吗?这个笔迹一点都不圆滑

其实Path.lineTo方法是简单的将两个点进行直线相连,当我们慢慢滑动手指的时候,相对来说是圆滑的。一旦我们的手指移动速度过快,就会出现不圆滑的情况

  1. 寻找解决方案

Bézier curve(贝塞尔曲线)是应用于二维图形应用程序的数学曲线。 曲线定义:起始点、终止点(也称锚点)、控制点。通过调整控制点,贝塞尔曲线的形状会发生变化。 1962年,法国数学家Pierre Bézier第一个研究了这种矢量绘制曲线的方法,并给出了详细的计算公式,因此按照这样的公式绘制出来的曲线就用他的姓氏来命名,称为贝塞尔曲线。

二阶贝塞尔曲线

————————————————
详细介绍,请查看 原文链接:https://blog.csdn.net/tianhai110/article/details/2203572

  1. 利用Path自带API绘制贝塞尔曲线


    Android Path绘制贝塞尔曲线

6.方法找到了,我们就来实现这个贝塞尔曲线
大概步骤如下:

  1. 手指按下时,记住点的坐标x和y,并缓存于mLastX和mLastY
  2. 手指移动时,计算本次滑动距离,大于等于3像素时,才考虑绘制贝塞尔曲线
  3. 设置贝塞尔曲线的终点坐标endX和endY为 上一个点和当前点的一半
  4. 绘制二次贝塞尔,实现平滑曲线,让mLastX, mLastY为操作点,endX, endY为终点
  5. 将当前点坐标x,y赋予mLastX和mLastY,用于下次移动时使用
  6. 本次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:[email protected]
 * 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

你可能感兴趣的:(Android自定义画板(一))