learn better from others,
be the better one.
—— "Weika Zhixiang"
The length of this article is 899 words , and it is expected to read for 3 minutes
foreword
In the recent project, a staff signature stub needs to be added to implement a handwritten signature function on Android, and then the signed image needs to be saved as a background image. In this article, we will make a demo of a handwritten signature whiteboard.
achieve effect
Code
Micro card Zhixiang
To implement a handwritten signature, we need to define a SignatureView by ourselves, which inherits from View, defines the path of the brush and the line, and then rewrites its onTouchEvent, and performs the brush operation according to the path of the line.
Core code SignatureView
package dem.vaccae.signnatureview
import android.content.Context
import android.graphics.*
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import androidx.core.view.drawToBitmap
import java.lang.Float.max
import kotlin.math.min
class SignatureView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
//笔划宽度
private var STROKE_WIDTH = 5f
private var lastTouchX = -1f
private var lastTouchY = -1f
//定义画笔相关
private val paint = Paint()
//定义画笔路径
private val path = Path()
//绘制的矩形区域
private val drawRect = RectF()
//初始化数据
init {
//设置抗锯齿
paint.isAntiAlias = true
//设置画笔颜色
paint.color = Color.BLUE
//设置画笔类型
paint.style = Paint.Style.STROKE
//设置画笔的线冒样式
paint.strokeCap = Paint.Cap.ROUND
//设置画笔连接处样式
paint.strokeJoin = Paint.Join.ROUND
//设置画笔宽度
paint.strokeWidth = STROKE_WIDTH
}
//清除绘制
fun clear(){
path.reset()
postInvalidate()
}
//获取当前页面图片
fun getBitmapFromView():Bitmap{
return drawToBitmap()
}
//设置笔划宽度
fun setPaintStrokeWidth(width:Float){
STROKE_WIDTH = width
paint.strokeWidth = STROKE_WIDTH
}
//设置笔划宽度
fun setPaintColor(color: Int){
paint.color = color
}
//画笔手执处理
override fun onTouchEvent(event: MotionEvent?): Boolean {
event?.let {
val event_x = it.x
val event_y = it.y
when (it.action) {
MotionEvent.ACTION_DOWN -> {
//点击按下时开始记录路径
path.moveTo(event_x, event_y)
//记录最后的X和Y的坐标
lastTouchX = event_x
lastTouchY = event_y
return true
}
MotionEvent.ACTION_MOVE, MotionEvent.ACTION_UP -> {
//计算绘制区域
drawRect.left = min(lastTouchX, event_x)
drawRect.right = max(lastTouchX, event_x)
drawRect.top = min(lastTouchY, event_y)
drawRect.bottom = max(lastTouchY, event_y)
// 当硬件跟踪事件的速度快于事件的交付速度时
// 事件将包含这些跳过点的历史记录
val historySize = it.historySize
(0 until historySize).forEach { i ->
val historicalx = it.getHistoricalX(i)
val historicaly = it.getHistoricalY(i)
if (historicalx < drawRect.left) {
drawRect.left = historicalx
} else if (historicalx > drawRect.right) {
drawRect.right = historicalx
}
if (historicaly < drawRect.top) {
drawRect.top = historicaly
} else if (historicaly > drawRect.bottom) {
drawRect.bottom = historicaly
}
path.lineTo(historicalx, historicaly)
}
// 回放历史记录后,将线路连接到触点。
path.lineTo(event_x, event_y)
}
else -> {
return false
}
}
// 绘制时根据笔画宽度除2用于在中心开妈绘制
postInvalidate(
(drawRect.left - STROKE_WIDTH / 2).toInt(),
(drawRect.top - STROKE_WIDTH / 2).toInt(),
(drawRect.right + STROKE_WIDTH / 2).toInt(),
(drawRect.bottom + STROKE_WIDTH / 2).toInt()
);
lastTouchX = event_x;
lastTouchY = event_y;
}
return super.onTouchEvent(event)
}
override fun onDraw(canvas: Canvas?) {
canvas?.let {
it.drawPath(path, paint)
}
super.onDraw(canvas)
}
}
a few key points
Use postInvalidate when drawing the image, set according to the defined dash area
Clear the drawing board directly using path.reset() and then postInvalidate()
Use darwToBitmap() to directly transfer the current View out of the Bitmap
Called in MainActivity
01
Directly refer to SignatureView in xml layout
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<dem.vaccae.signnatureview.SignatureView
android:layout_width="match_parent"
android:layout_height="400dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:id="@+id/signatureView" />
<ImageView
android:layout_width="200dp"
android:layout_height="150dp"
android:id="@+id/imgv"
android:layout_marginTop="20dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/signatureView" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/btnclear"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/imgv"
app:layout_constraintEnd_toStartOf="@id/btnSave"
android:text="清除" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/btnSave"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@id/btnclear"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/imgv"
android:text="保存图片" />
</androidx.constraintlayout.widget.ConstraintLayout>
02
MainActivity code
package dem.vaccae.signnatureview
import android.graphics.Color
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.ImageView
import android.widget.Toast
import dem.vaccae.signnatureview.databinding.ActivityMainBinding
import kotlin.random.Random
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// setContentView(R.layout.activity_main)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.signatureView.setBackgroundColor(Color.rgb(245,245,245))
binding.btnclear.setOnClickListener {
binding.signatureView.clear()
}
binding.btnSave.setOnClickListener {
val bmp = binding.signatureView.getBitmapFromView()
binding.imgv.scaleType = ImageView.ScaleType.FIT_XY
binding.imgv.setImageBitmap(bmp)
}
}
}
Such a small demo of a simple handwritten signature pad is realized.
over
Wonderful review of the past
Super simple pyTorch training->onnx model->C++ OpenCV DNN reasoning (with source code address)
Kotlin uses Select expression in coroutine to select fastest result
Android picture-in-picture (PIP) advanced --- use of the Action button