自定义签字板,实现名字居中,增加边距等
可直接获取到 bitmap,uri,File ,并且直接进行保存到手机,支持 Android Q
下面看一下效果:
上面清除的展示了名字居中的效果,并且四周设置了内边距
下面看一下实现代码:
/**
* @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()
}
}
源码地址
如有问题,还请指出,谢谢!