最近项目中有个新的需求,就是要实现用户手写签名,然后展示再上传到服务器。看到效果图后,先是面对百度编程搜了一下,很多实现方法,主要就是自定义View实现的,为了记录其中的坑,并提升自己的自定义View的能力,还是写出来记录一下。
主要代码如下(用的Kotlin写的):
import android.content.Context
import android.graphics.*
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import java.io.ByteArrayOutputStream
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
/**
* Created by yuan7016 on 2019/07/02.
* desc : 签名View
*/
class SignView : View {
/**
* 画笔
*/
private var paint : Paint? = null
private var path : Path? = null
private lateinit var cacheCanvas : Canvas
/**
* 签名画布
*/
private lateinit var signBitmap: Bitmap
//画笔颜色
private var paintColor : Int = Color.BLACK
//画笔宽度
private var paintWidth = 15f
private var xAlixs : Float = 0.0f
private var yAlixs : Float = 0.0f
/**
* 背景色(指最终签名结果文件的背景颜色,这里我设置为白色)
* 你也可以设置为透明的
*/
private var mBackColor = Color.WHITE
//是否已经签名
private var isSigned : Boolean = false
constructor(context: Context?) : super(context){
init(context)
}
constructor(context: Context?,attributeSet: AttributeSet?) : super(context,attributeSet){
init(context)
}
constructor(context: Context?,attributeSet: AttributeSet,defStyleAttr : Int) : super(context,attributeSet,defStyleAttr){
init(context)
}
fun init(context: Context?){
paint = Paint()
path = Path()
//setBackgroundColor(Color.WHITE)
paint?.color = paintColor//设置签名颜色
paint?.style = Paint.Style.STROKE //设置填充样式
paint?.isAntiAlias = true //抗锯齿功能
paint?.strokeWidth = paintWidth//设置画笔宽度
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
//创建跟view一样大的bitmap,用来保存签名
signBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
cacheCanvas = Canvas(signBitmap)
cacheCanvas.drawColor(mBackColor)
isSigned = false
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
//画此次笔画之前的签名
canvas.drawBitmap(signBitmap, 0f, 0f, paint)
// 通过画布绘制多点形成的图形
canvas.drawPath(path,paint)
}
override fun onTouchEvent(event: MotionEvent): Boolean {
//记录每次 X , Y轴的坐标
xAlixs = event.x
yAlixs = event.y
when (event.action) {
MotionEvent.ACTION_DOWN -> {
path?.reset()
path?.moveTo(xAlixs, yAlixs)
}
MotionEvent.ACTION_MOVE -> {
path?.lineTo(xAlixs, yAlixs)
isSigned = true
}
MotionEvent.ACTION_UP -> {
//将路径画到bitmap中,即一次笔画完成才去更新bitmap,而手势轨迹是实时显示在画板上的。
cacheCanvas.drawPath(path, paint)
path?.reset()
}
else -> AppLog.e("otherwise")
}
// 更新绘制
invalidate()
return true
}
/**
* 清除画板
*/
public fun clear(){
isSigned = false
path?.reset()
paint?.color = paintColor
cacheCanvas.drawColor(mBackColor, PorterDuff.Mode.CLEAR)
invalidate()
}
/**
* 保存画板
*
* @param path 保存到路径
*/
@Throws(IOException::class)
fun save(path: String) {
val bitmap = signBitmap
// 如果图片过大的话,需要压缩图片,不过在我测试手机上最大才50多kb
val bos = ByteArrayOutputStream()
bitmap.compress(Bitmap.CompressFormat.PNG, 100, bos)
val buffer = bos.toByteArray()
if (buffer != null) {
val file = File(path)
if (file.exists()) {
file.delete()
}
val outputStream = FileOutputStream(file)
outputStream.write(buffer)
outputStream.close()
}
}
//TODO 这里可以扩展一些setter方法
/**
* 是否有签名
*
* @return isSigned
*/
public fun getHasSigned() : Boolean{
return isSigned
}
}
在布局中引用:
android:layout_width="match_parent" android:layout_height="match_parent" android:layout_above="@id/ll_bottom" android:layout_marginBottom="15dp" android:background="@drawable/bg_shape_white_round_4" android:padding="10dp"> android:id="@+id/signView" android:layout_width="match_parent" android:layout_height="match_parent" /> android:id="@+id/ivBack" android:layout_width="30dp" android:layout_height="38dp" android:padding="8dp" android:background="?attr/selectableItemBackground" android:src="@mipmap/icon_back_gray" />
在Activity中保存签名:
/**
* 保存签名
*/
private fun saveSignBitmap(){
//保存路径
val path : String = getExternalFilesDir(Environment.DIRECTORY_PICTURES).path + File.separator + "order_sign_" + System.currentTimeMillis() + ".png"
if (signView.getHasSigned()){
try {
signView.save(path)
ToastUtil.showToast("保存成功!")
SharedPreferencesUtil.setPreferStr(AppConstant.KEY_SIGN_PATH,path)
finish()
}catch ( ex: Exception){
ToastUtil.showToast("签名保存失败!")
}
}else{
ToastUtil.showToast("您还没有签名,请签名!")
}
}
主要代码及逻辑如上所示。
效果图: