Android自定義畫板(一)

Android畫板千千萬,網(wǎng)上一搜一大堆,但總是找不到一個滿意的,今天我們就來自己做一個畫板,包括以下功能:

  • 繪制任意線條
  • 畫筆顏色和寬度可選
  • 繪制幾何形狀
  • 包含橡皮擦功能
  • 筆跡可撤銷,可恢復(fù)
  • 設(shè)置畫布背景,比如田字格等
  • 將畫布內(nèi)容保存為圖片
  • 不斷增加中...

源碼地址(不定期添加新功能):https://gitee.com/ZengCS/android-sketch-pad-pro

一、自定義View來做一個畫板

創(chuàng)建一個基類畫板BaseSketchView,完整代碼:https://gitee.com/ZengCS/android-sketch-pad-pro/blob/master/sketchpad/src/main/java/com/zcs/lib/sketchpad/canvas/BaseSketchView.kt

  1. 創(chuàng)建一只畫筆,代碼中均有注釋,不再一一講解
// Paint 一只畫筆
private val mPaint = Paint()

// Path 用于記錄路徑
val mPath = Path()

/**
 * 初始化畫筆,默認顏色:紅色 寬度(px):10f
 */
private fun initPaint(color: Int = Color.RED, strokeWidth: Float = 10f) {
    // 設(shè)置畫筆顏色
    mPaint.color = color
    // 設(shè)置畫筆寬度(單位px)
    mPaint.strokeWidth = strokeWidth
    // 設(shè)置畫筆樣式
    mPaint.style = Paint.Style.STROKE
    // 畫筆開始和結(jié)束為圓
    mPaint.strokeCap = Paint.Cap.ROUND
    // 連接處為圓
    mPaint.strokeJoin = Paint.Join.ROUND
    // 當style為STROKE或FILL_AND_STROKE時設(shè)置連接處的傾斜度,這個值必須大于0
    mPaint.strokeMiter = 1.0f
    // 設(shè)置畫筆透明度
    mPaint.alpha = 0xFF
    // 設(shè)置抗鋸齒
    mPaint.isAntiAlias = true
}
  1. 重寫觸摸事件 onTouchEvent(event: MotionEvent)
/**
 * 重寫觸摸事件監(jiān)聽并消費掉,不讓其往下傳遞
 */
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移動到此點,否則會出現(xiàn)多余的直線
    mPath.moveTo(event.x, event.y)
    return true
}

/**
 * 手指移動,必須在子類中實現(xiàn),這里就是畫筆跡的核心
 */
abstract fun onFingerMove(event: MotionEvent): Boolean

/**
 * 手指抬起
 */
open fun onFingerUp(event: MotionEvent): Boolean {
    return false
}

寫到這里,我們的畫板功能已經(jīng)完成90%了,那么最重要的一步就是處理手指移動事件,這個需要在子類來實現(xiàn),下面我們看看子類怎么實現(xiàn)筆跡繪制吧,代碼很簡單,直接貼全碼:

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)來實現(xiàn)
看看效果吧:


普通畫筆.jpg
  1. 問題出現(xiàn)
    是哪里出問題了嗎?這個筆跡一點都不圓滑

其實Path.lineTo方法是簡單的將兩個點進行直線相連,當我們慢慢滑動手指的時候,相對來說是圓滑的。一旦我們的手指移動速度過快,就會出現(xiàn)不圓滑的情況

  1. 尋找解決方案

Bézier curve(貝塞爾曲線)是應(yīng)用于二維圖形應(yīng)用程序的數(shù)學曲線。 曲線定義:起始點、終止點(也稱錨點)、控制點。通過調(diào)整控制點,貝塞爾曲線的形狀會發(fā)生變化。 1962年,法國數(shù)學家Pierre Bézier第一個研究了這種矢量繪制曲線的方法,并給出了詳細的計算公式,因此按照這樣的公式繪制出來的曲線就用他的姓氏來命名,稱為貝塞爾曲線。

二階貝塞爾曲線

————————————————
詳細介紹,請查看 原文鏈接:https://blog.csdn.net/tianhai110/article/details/2203572

  1. 利用Path自帶API繪制貝塞爾曲線


    Android Path繪制貝塞爾曲線

6.方法找到了,我們就來實現(xiàn)這個貝塞爾曲線
大概步驟如下:

  1. 手指按下時,記住點的坐標x和y,并緩存于mLastX和mLastY
  2. 手指移動時,計算本次滑動距離,大于等于3像素時,才考慮繪制貝塞爾曲線
  3. 設(shè)置貝塞爾曲線的終點坐標endX和endY為 上一個點和當前點的一半
  4. 繪制二次貝塞爾,實現(xiàn)平滑曲線,讓mLastX, mLastY為操作點,endX, endY為終點
  5. 將當前點坐標x,y賦予mLastX和mLastY,用于下次移動時使用
  6. 本次move事件處理完成,等待下一個move事件觸發(fā)

具體代碼實現(xiàn):

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: 在普通的畫板基礎(chǔ)之上,點與點之間使用貝塞爾曲線相連
 */
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)
    }

    // 手指在屏幕上滑動時調(diào)用
    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) {
            // 設(shè)置貝塞爾曲線的終點坐標為 上一個點和當前點的一半
            val endX = (currX + mLastX) / 2
            val endY = (currY + mLastY) / 2

            // 繪制二次貝塞爾,實現(xiàn)平滑曲線;mLastX, mLastY為操作點,endX, endY為終點
            mPath.quadTo(mLastX, mLastY, endX, endY)

            // 第二次執(zhí)行時,第一次結(jié)束調(diào)用的坐標值將作為第二次調(diào)用的初始坐標值
            this.mLastX = currX
            this.mLastY = currY
        }
        return true
    }
}

看看效果吧:真圓滑!!


貝塞爾畫筆.jpg

源碼地址(不定期添加新功能):https://gitee.com/ZengCS/android-sketch-pad-pro

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容