Android Kotlin は署名ホワイトボードを作成し、写真を保存します

他の人からより良く学び、

より良いものになりますように。

—— 「ウェイカ・ジーシャン」

b138708b2b2a9c50c9074a9dcfc46b8f.jpeg

この記事の長さは 899ワードで、読むのにかかる時間は 3分です。

序文

最近のプロジェクトでは、Android に手書き署名機能を実装するためにスタッフの署名スタブを追加し、署名された画像を背景画像として保存する必要があります。この記事では、手書き署名ホワイトボードのデモを作成します。 。

69725eecf12b633534ba3420b33ea3e8.png

効果を達成する

97d097dfc35720378a381c979ba64377.gif

438215062b2b483aef3d0b3b9fbaa21e.png

4e4647490cf0a9ac3149fc6b1b686eae.png

コード

1b4fc01a4c445c23e89de5e0d00cf37d.png

マイクロカード志祥

手書き署名を実装するには、Viewを継承してブラシと線のパスを定義し、そのonTouchEventを書き換えて線のパスに応じてブラシ操作を行うSignatureViewを自分で定義する必要があります。

コアコード 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)
    }


}

いくつかの重要なポイント

e005826697880d2deb88acbecfd2fcb2.png

画像を描画するときに postInvalidate を使用し、定義されたダッシュ領域に従って設定します

f048fa2f46c050e6eec518ae8c859570.png

path.reset() を使用して直接描画ボードをクリアし、その後 postInvalidate() を実行します。

edf2e5b25439fcc5fdfb3e276675cf6f.png

darwToBitmap() を使用して、現在のビューをビットマップから直接転送します。

MainActivityで呼び出される

01

XMLレイアウトでSignatureViewを直接参照

<?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 コード

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)
        }
    }
}

このような簡単な手書きサインパッドの小さなデモが実現します。

以上

6cfa4aeb3587312e351fadf07925e155.png

82d535abc2b55ed0ffa68419a7755328.png

過去の素晴らしいレビュー

1f76f12439456d5ad7200d7b581f24e3.jpeg

超簡単な pyTorch トレーニング -> onnx モデル -> C++ OpenCV DNN 推論 (ソース コード アドレス付き)


62d4305d48054dbf71511d6ff45c3666.jpeg

Kotlin はコルーチンで Select 式を使用して最速の結果を選択します


36a0a16f476b5f42887206b638d8c2f8.jpeg

Android ピクチャーインピクチャー (PIP) の高度な機能 --- アクション ボタンの使用


おすすめ

転載: blog.csdn.net/Vaccae/article/details/128029962