自定义 View | 画板

简介: 自定义 View | 画板

自定义签字板,实现名字居中,增加边距等


可直接获取到 bitmap,uri,File ,并且直接进行保存到手机,支持 Android Q


下面看一下效果:


2019082413041482.gif


上面清除的展示了名字居中的效果,并且四周设置了内边距


下面看一下实现代码:


/**
 * @name DrawingView
 * @package com.example.ui.customView
 * @author 345 QQ:1831712732
 * @time 2020/5/26 20:46
 * @description 画板
 */
class DrawingView : View {
    private lateinit var mPaint: Paint
    private lateinit var mPath: Path
    private var cacheBitmap //用户保存签名的Bitmap
            : Bitmap? = null
    private var cacheCanvas //用户保存签名的Canvas
            : Canvas? = null
    //位置
    private var mLeft: Float = 0f
    private var mRight: Float = 0f
    private var mTop: Float = 0f
    private var mBottom: Float = 0f
    //边距
    private var mPadding = 20f
    //线宽度
    private var mPaintWidth = 10f
    constructor(context: Context?) : this(context, null)
    constructor(context: Context?, attrs: AttributeSet?) : this(context, attrs, 0)
    constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
        init()
    }
    private fun init() { //初始化画笔
        mPaint = Paint()
        mPaint.style = Paint.Style.STROKE
        mPaint.color = Color.BLACK
        mPaint.strokeWidth = mPaintWidth
        mPath = Path()
    }
    /**
     * 在控件大小发生改变是调用
     */
    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
        super.onSizeChanged(w, h, oldw, oldh)
        cacheBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888)
        cacheCanvas = Canvas(cacheBitmap!!)
        //设置背景色为透明
        cacheCanvas?.drawColor(Color.WHITE)
    }
    @SuppressLint("ClickableViewAccessibility")
    override fun onTouchEvent(event: MotionEvent): Boolean {
        when (event.action) {
            MotionEvent.ACTION_DOWN -> {
                //路径起点
                mPath.moveTo(event.x, event.y)
                if (mLeft == 0f) {
                    mLeft = event.x
                }
                if (mTop == 0f) {
                    mTop = event.y
                }
                setVertexCoordinates(event.x, event.y)
            }
            MotionEvent.ACTION_MOVE -> {
                mPath.lineTo(event.x, event.y)
                postInvalidate()
                //限制滑动的位置
                if (event.x < width && event.x >= 0) {
                    if (event.y < height && event.y >= 0) {
                        //如果是第二次按下,也需要记录位置
                        setVertexCoordinates(event.x, event.y)
                    }
                }
            }
            MotionEvent.ACTION_UP -> //将签名绘制到缓存画布上
                cacheCanvas?.drawPath(mPath, mPaint)
        }
        return true
    }
    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        //绘制签名路径
        canvas.drawPath(mPath, mPaint)
    }
    /**
     * 重置画布
     */
    fun resetCanvas() {
        mPath.reset()
        cacheBitmap = null
        cacheCanvas = null
        cacheBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
        cacheCanvas = Canvas(cacheBitmap!!)
        //设置背景色为透明
        cacheCanvas?.drawColor(Color.WHITE)
        mBottom = 0f
        mTop = mBottom
        mRight = mTop
        mLeft = mRight
        postInvalidate()
    }
    /**
     * 记录四个边的位置
     */
    private fun setVertexCoordinates(x: Float, y: Float) {
        if (x > mRight) {
            mRight = x
        }
        if (x < mLeft) {
            mLeft = x
        }
        if (y > mBottom) {
            mBottom = y
        }
        if (y < mTop) {
            mTop = y
        }
    }
    /**
     * 返回 bitmap
     * @param blank :边距
     */
    fun getBitmap(blank: Int): Bitmap? {
        return cropCanvas(blank.toFloat())
    }
    /**
     * 获取图片 file
     */
    fun getFile(): File? {
        val bitmap = getBitmap(15) ?: return null
        val uri = save(bitmap, "${System.currentTimeMillis()}.png")
                ?: throw FileNotFoundException("文件未找到")
        val query = context.contentResolver.query(uri, arrayOf(MediaStore.Images.Media.DATA), null, null, null)
        query?.moveToFirst()
        val index = query?.getColumnIndexOrThrow(MediaStore.Images.Media.DATA)
        val path = query!!.getString(index!!)
        if (path == null) {
            ToastUtils.showText("获取失败")
            query.close()
        }
        return File(path)
    }
    /**
     * 保存图片到本地
     * @param displayName 图片名称,注意需要加上后缀名 .png
     * 注意名字不能重复,否则无法保存
     */
    fun save(displayName: String): Uri? {
        val bitmap = getBitmap(15) ?: return null
        return save(bitmap, displayName)
    }
    fun save(bitmap: Bitmap, displayName: String): Uri? {
        val values = ContentValues()
        values.put(MediaStore.MediaColumns.DISPLAY_NAME, displayName)
        values.put(MediaStore.MediaColumns.MIME_TYPE, "image/png")
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DCIM)
        } else {
            values.put(MediaStore.MediaColumns.DATA, "${Environment.getExternalStorageDirectory().path}/${Environment.DIRECTORY_DCIM}/$displayName")
        }
        val uri = context.contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)
        if (uri != null) {
            val outputStream = context.contentResolver.openOutputStream(uri)
            if (outputStream != null) {
                bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream)
                outputStream.close()
            }
            ToastUtils.showText("完成")
            return uri
        }
        return null
    }
    /**
     * 剪裁画布把多余的画布去掉只保留签名部分
     *
     * @param blank 边距
     */
    private fun cropCanvas(): Bitmap? {
        return cropCanvas(mPadding)
    }
    private fun cropCanvas(padding: Float): Bitmap? {
        var right = (mRight - mLeft)
        if (right + (padding * 2) < width) {
            mLeft -= padding
            right += padding
        }
        var height = (mBottom - mTop)
        if (height + (padding * 2) < getHeight()) {
            mTop -= padding
            height += padding
        }
        if (right <= padding && height <= padding) {
            ToastUtils.showText("请进行签名")
            return null
        }
        val dip2px = dip2px(padding)
        //裁切签名的部分
        val cropBitmap = Bitmap.createBitmap(cacheBitmap!!, mLeft.toZero(), mTop.toZero(), right.toZero(), height.toZero())
        //设置边距
        val bitmap = Bitmap.createBitmap((cropBitmap.width + (dip2px * 2)), (cropBitmap.height + (dip2px * 2)), Bitmap.Config.ARGB_8888)
        val canvas = Canvas(bitmap)
        canvas.drawColor(Color.WHITE)
        canvas.drawBitmap(cropBitmap, dip2px.toFloat(), dip2px.toFloat(), mPaint)
        return bitmap
    }
    private fun Float.toZero(): Int {
        return if (this < 0f) {
            0
        } else {
            this.toInt()
        }
    }
    /**
     * 根据手机的分辨率从 dp 的单位 转成为 px(像素)
     */
    private fun dip2px(dpValue: Float): Int {
        val scale = resources.displayMetrics.density
        return (dpValue * scale + 0.5f).toInt()
    }
}


使用如下:


//画板
        activity_drawing.setOnClickListener {
            val dialog = FastDialog.Builder(this)
                    .setContentView(R.layout.layout_drawing)
                    .setWidth(0.7f)
                    .build()
            dialog.show()
            val drawingView = dialog.getView<DrawingView>(R.id.layout_drawing)
            dialog.setOnClickListener(R.id.layout_save) {
                //这里使用的是 getFile,你也可以直接调用 getBitmap 等
                val file = drawingView?.getFile()
                val bitmap = BitmapFactory.decodeFile("${file?.path}")
                activity_views_image.setImageBitmap(bitmap)
            }
            dialog.setOnClickListener(R.id.layout_reset) {
                ToastUtils.showCenterText("重置")
                drawingView?.resetCanvas()
            }
        }


相关文章
|
7月前
|
小程序 容器
小程序图片水平垂直居中显示在view中
小程序图片水平垂直居中显示在view中
116 0
|
XML Android开发 数据格式
控件布局(View)叠加效果
控件布局(View)叠加效果
|
小程序 前端开发 定位技术
【小程序】view视图,swiper轮播图,scroll-view滑动列表 (在线详细手册)
【小程序】view视图,swiper轮播图,scroll-view滑动列表 (在线详细手册)
|
小程序 前端开发 JavaScript
01day 动态绑定变量 导航组件 view text是否可以复制 button 上下滚动组件
01day 动态绑定变量 导航组件 view text是否可以复制 button 上下滚动组件
01day 动态绑定变量 导航组件 view text是否可以复制 button 上下滚动组件
|
C#
【WPF】自定义形状的按钮Button
原文:【WPF】自定义形状的按钮Button 需求:做一个如下图所示的多边形按钮。 Points点从左上角(0, 0)点开始,顺时针绘制,最后回到原点完成封闭的图形。
1725 0
|
前端开发 API Android开发
3.3 自定义控件基础 之 View的绘制
当测量好了一个View之后,我们就可以简单地重写onDraw()方法,并在Canvas对象上来绘制所需要的图形。首先我们来了解一下利用系统2D绘图API所必须要使用到的Canvas对象。
683 0
|
前端开发 Android开发 Python
这可能是第二好的自定义 View 教程之绘制
面试系列 不继续了吗? 知道我的人都知道,之前我写了这个 面试系列宣言,如今好像一直都没有连载,而是隔三差五地来一篇,其实也是因为笔者也能力有限,构思一篇文章需要足够的时间去印证其准确性,而之前的部分就因为印证不够造成了勘误。
1117 0