当前位置: 首页 > article >正文

Android 手写签名板

文章目录

  • Android 手写签名板
    • 概述
    • 效果
    • 代码实现
    • 源码下载

Android 手写签名板

概述

手写签名板功能,支持图片保存、支持去除空白区域。

效果

在这里插入图片描述

生成图片效果:

在这里插入图片描述在这里插入图片描述

代码实现

定义属性:

<declare-styleable name="SignatureView">
    <attr name="bgColor" format="color" />
    <attr name="brushColor" format="color" />
    <attr name="brushWidth" format="dimension" />
</declare-styleable>

代码:

class SignatureView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
    companion object {
        const val DEFAULT_BG_COLOR = Color.WHITE
        const val DEFAULT_BRUSH_COLOR = Color.BLACK
        const val DEFAULT_BRUSH_WIDTH = 12F
    }

    private var bgColor: Int = DEFAULT_BG_COLOR
    private var brushColor: Int = DEFAULT_BRUSH_COLOR
    private var brushWidth = DEFAULT_BRUSH_WIDTH

    private val paint = Paint().apply {
        isAntiAlias = true
        color = brushColor
        style = Paint.Style.STROKE
        strokeWidth = brushWidth
    }

    private val path = Path()
    private var touchX: Float = 0F
    private var touchY: Float = 0F
    private var isClear = false

    init {
        val typedArray = context.obtainStyledAttributes(attrs, R.styleable.SignatureView)
        bgColor = typedArray.getColor(R.styleable.SignatureView_bgColor, DEFAULT_BG_COLOR)
        brushColor = typedArray.getColor(R.styleable.SignatureView_brushColor, DEFAULT_BRUSH_COLOR)
        brushWidth =
            typedArray.getDimension(R.styleable.SignatureView_brushWidth, DEFAULT_BRUSH_WIDTH)
        typedArray.recycle()

        paint.apply {
            color = brushColor
            strokeWidth = brushWidth
        }
        setBackgroundColor(bgColor)
    }

    /**
     * 保存图片
     *
     * @param imageDirs 目录
     * @param imageName 文件名
     * @param clearBlank 是否清除空白区域
     * @param blank 边距
     */
    fun save(imageDirs: String, imageName: String, clearBlank: Boolean = false, blank: Int = 0) {
        if (imageDirs.isEmpty() || imageName.isEmpty()) {
            return
        }
        if (path.isEmpty) {
            return
        }
        var bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
        val canvas = Canvas(bitmap)
        canvas.drawColor(bgColor)
        canvas.drawPath(path, paint)
        if (clearBlank) {
            bitmap = clearBlank(bitmap, blank)
        }
        val dir = File(imageDirs)
        if (!dir.exists())
            dir.mkdirs()
        val file = File(dir, imageName)
        if (file.exists())
            file.delete()
        val out = FileOutputStream(file)
        try {
            bitmap.compress(Bitmap.CompressFormat.PNG, 100, out)
        } catch (e: Exception) {
            e.printStackTrace()
        } finally {
            out.close()
        }
    }

    /**
     * 清除空白区域
     *
     * @param bitmap 原图
     * @param blank 保留边距
     * @return
     */
    private fun clearBlank(bitmap: Bitmap, blank: Int): Bitmap {
        var space = blank
        val width = bitmap.width
        val height = bitmap.height
        var left = 0
        var right = 0
        var top = 0
        var bottom = 0
        var pixs = IntArray(width)
        var isStop = false
        //扫描上边距不等于背景颜色的第一个点
        for (i in 0 until height) {
            bitmap.getPixels(pixs, 0, width, 0, i, width, 1)
            isStop = false
            for (pix in pixs) {
                if (pix != bgColor) {
                    top = i
                    isStop = true
                    break
                }
            }
            if (isStop) {
                break
            }
        }
        //扫描下边距不等于背景颜色的第一个点
        for (i in height - 1 downTo 0) {
            bitmap.getPixels(pixs, 0, width, 0, i, width, 1)
            isStop = false
            for (pix in pixs) {
                if (pix != bgColor) {
                    bottom = i
                    isStop = true
                    break
                }
            }
            if (isStop) {
                break
            }
        }
        pixs = IntArray(height)
        //扫描左边距不等于背景颜色的第一个点
        for (x in 0 until width) {
            bitmap.getPixels(pixs, 0, 1, x, 0, 1, height)
            isStop = false
            for (pix in pixs) {
                if (pix != bgColor) {
                    left = x
                    isStop = true
                    break
                }
            }
            if (isStop) {
                break
            }
        }
        //扫描右边距不等于背景颜色的第一个点
        for (x in width - 1 downTo 1) {
            bitmap.getPixels(pixs, 0, 1, x, 0, 1, height)
            isStop = false
            for (pix in pixs) {
                if (pix != bgColor) {
                    right = x
                    isStop = true
                    break
                }
            }
            if (isStop) {
                break
            }
        }
        if (space < 0) {
            space = 0
        }
        //计算加上保留空白距离之后的图像大小
        left = Math.max(left - space, 0)
        top = Math.max(top - space, 0)
        right = Math.min(right + space, width - 1)
        bottom = Math.min(bottom + space, height - 1)
        return Bitmap.createBitmap(bitmap, left, top, right - left, bottom - top)
    }

    /**
     * 清除画板
     */
    fun clear() {
        isClear = true
        invalidate()
    }

    override fun onTouchEvent(event: MotionEvent): Boolean {
        when (event.action) {
            MotionEvent.ACTION_DOWN -> {
                touchX = event.x
                touchY = event.y
                path.moveTo(touchX, touchY)
                return true
            }
            MotionEvent.ACTION_MOVE -> {
                val x = event.x
                val y = event.y
                Log.e("TAG", "moveX:$x moveY:$y")
                // 计算滑动时偏移值
                val dx = Math.abs(x - touchX)
                val dy = Math.abs(y - touchY)
                // 偏移值大于3px绘制
                if (dx >= 3 || dy >= 3) {
                    val cx = (x + touchX) / 2
                    val cy = (y + touchY) / 2
                    path.quadTo(touchX, touchY, cx, cy)
                    touchX = x
                    touchY = y
                }
                invalidate()
            }
        }
        return super.onTouchEvent(event)
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        if (isClear) {
            canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
            canvas.drawColor(bgColor)
            path.reset()
            isClear = false
        } else {
            if (!path.isEmpty) {
                canvas.drawPath(path, paint)
            }
        }
    }
}

使用:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
    android:orientation="vertical"
    tools:context=".signature.SignatureActivity">

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="onSave1"
            android:text="保存1" />

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="onSave2"
            android:text="保存2" />

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="onClear"
            android:text="清除" />
    </LinearLayout>

    <com.example.widgets.signature.SignatureView
        android:id="@+id/signature_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:bgColor="@color/blue"
        app:brushColor="@color/red"
        app:brushWidth="6dp" />
</LinearLayout>
class SignatureActivity : BaseActivity() {
    private lateinit var signatureView: SignatureView

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_signature)
        signatureView = findViewById(R.id.signature_view)
    }

    fun onSave1(view: View) {
        val imageDir = "${this.cacheDir}/signature"
        val imageName = "${System.currentTimeMillis()}.png"
        signatureView.save(imageDir, imageName)
    }

    fun onSave2(view: View) {
        val imageDir = "${this.cacheDir}/signature"
        val imageName = "${System.currentTimeMillis()}.png"
        signatureView.save(imageDir, imageName, true, 10)
    }

    fun onClear(view: View) {
        signatureView.clear()
    }
}

源码下载


http://www.kler.cn/a/412623.html

相关文章:

  • 学习threejs,设置envMap环境贴图创建反光效果
  • 基础入门-Web应用架构类别源码类别镜像容器建站模版编译封装前后端分离
  • LeetCode 0632.最小区间:优先队列
  • 如何选择黑白相机和彩色相机
  • 计算机网络的发展
  • 手搓人工智能—聚类分析(下)谱系聚类与K-mean聚类
  • 数据结构 【堆实现】
  • 力扣876. 链表的中间结点
  • nginx和netcore加载常见的3D模型
  • Go 中的并发 Map:深入探索 sync.Map 及其他实现方法
  • Django中 model 一对一 一对多 多对多关系 关联
  • NR 5G SIB1读取失败应该怎么办?
  • Ubuntu系统通过命令行连接WiFi
  • 美创科技获选“金智奖”年度创新解决方案,为工业企业数据安全治理提供思路
  • 图书系统小案例
  • 欢迪迈手机商城:基于SpringBoot的用户体验提升
  • JavaWeb三层架构
  • Flutter 开发环境—Linux
  • RabblitMQ 消息队列组件与 libev事件驱动库
  • 【Petri网导论学习笔记】Petri网导论入门学习(十一) —— 3.3 变迁发生序列与Petri网语言
  • Leecode刷题C语言之交替组②
  • 鸿蒙面试 --- 性能优化(精简版)
  • K8s调度器扩展(scheduler)
  • 小程序-基于java+SpringBoot+Vue的微信小程序养老院系统设计与实现
  • C语言中使用动态内存
  • SpringBoot集成minio,并实现文件上传