美文网首页晓我课堂
Android自定义画板(一)

Android自定义画板(一)

作者: 丨逐风者丨 | 来源:发表于2022-03-02 16:25 被阅读0次

    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: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
    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: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

    相关文章

      网友评论

        本文标题:Android自定义画板(一)

        本文链接:https://www.haomeiwen.com/subject/xwhwkrtx.html