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
- 創(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
}
- 重寫觸摸事件 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)
看看效果吧:

- 問題出現(xiàn)
是哪里出問題了嗎?這個筆跡一點都不圓滑
其實Path.lineTo方法是簡單的將兩個點進行直線相連,當我們慢慢滑動手指的時候,相對來說是圓滑的。一旦我們的手指移動速度過快,就會出現(xiàn)不圓滑的情況
- 尋找解決方案
Bézier curve(貝塞爾曲線)是應(yīng)用于二維圖形應(yīng)用程序的數(shù)學曲線。 曲線定義:起始點、終止點(也稱錨點)、控制點。通過調(diào)整控制點,貝塞爾曲線的形狀會發(fā)生變化。 1962年,法國數(shù)學家Pierre Bézier第一個研究了這種矢量繪制曲線的方法,并給出了詳細的計算公式,因此按照這樣的公式繪制出來的曲線就用他的姓氏來命名,稱為貝塞爾曲線。
二階貝塞爾曲線
————————————————
詳細介紹,請查看 原文鏈接:https://blog.csdn.net/tianhai110/article/details/2203572
-
利用Path自帶API繪制貝塞爾曲線
Android Path繪制貝塞爾曲線
6.方法找到了,我們就來實現(xiàn)這個貝塞爾曲線
大概步驟如下:
- 手指按下時,記住點的坐標x和y,并緩存于mLastX和mLastY
- 手指移動時,計算本次滑動距離,大于等于3像素時,才考慮繪制貝塞爾曲線
- 設(shè)置貝塞爾曲線的終點坐標endX和endY為 上一個點和當前點的一半
- 繪制二次貝塞爾,實現(xiàn)平滑曲線,讓mLastX, mLastY為操作點,endX, endY為終點
- 將當前點坐標x,y賦予mLastX和mLastY,用于下次移動時使用
- 本次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
}
}
看看效果吧:真圓滑!!

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

