Android Kotlin makes signature whiteboard and saves pictures

learn better from others,

be the better one.

—— "Weika Zhixiang"

b138708b2b2a9c50c9074a9dcfc46b8f.jpeg

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.

69725eecf12b633534ba3420b33ea3e8.png

achieve effect

97d097dfc35720378a381c979ba64377.gif

438215062b2b483aef3d0b3b9fbaa21e.png

4e4647490cf0a9ac3149fc6b1b686eae.png

Code

1b4fc01a4c445c23e89de5e0d00cf37d.png

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

e005826697880d2deb88acbecfd2fcb2.png

Use postInvalidate when drawing the image, set according to the defined dash area

f048fa2f46c050e6eec518ae8c859570.png

Clear the drawing board directly using path.reset() and then postInvalidate()

edf2e5b25439fcc5fdfb3e276675cf6f.png

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

6cfa4aeb3587312e351fadf07925e155.png

82d535abc2b55ed0ffa68419a7755328.png

Wonderful review of the past

1f76f12439456d5ad7200d7b581f24e3.jpeg

Super simple pyTorch training->onnx model->C++ OpenCV DNN reasoning (with source code address)


62d4305d48054dbf71511d6ff45c3666.jpeg

Kotlin uses Select expression in coroutine to select fastest result


36a0a16f476b5f42887206b638d8c2f8.jpeg

Android picture-in-picture (PIP) advanced --- use of the Action button


Guess you like

Origin blog.csdn.net/Vaccae/article/details/128029962