效果图:
View代码:
class SignView : View {
//画笔
private val mGesturePaint = Paint()
private val mTextPaint = Paint()
//路径
private val mPath = Path()
//起点X,Y
private var mX = 0F
private var mY = 0F
//缓存的画布
private lateinit var mCacheCanvas: Canvas
//生成的图片
private lateinit var mCacheBitmap: Bitmap
//背景提示文案 不需要展示在最终生成的图片中 当有按下事件,即赋值为true
var hintTextIsClear = false
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
init {
with(mGesturePaint) {
isAntiAlias = true//坑锯齿
style = Paint.Style.STROKE
strokeWidth = 10F//画笔宽度
color = Color.BLACK
}
with(mTextPaint) {
isAntiAlias = true//坑锯齿
textSize = 120F
color = Color.parseColor("#cccccc")
}
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
//创建根view一样大的bitmap,用来保存签名(当控件大小发生改变时调用)
mCacheBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
mCacheCanvas = Canvas(mCacheBitmap)
mCacheCanvas.drawColor(Color.TRANSPARENT)
//背景提示文案
val textWidth: Float = AnnulusUtil.calcTextWidth(mTextPaint, "请在此签名").toFloat()
mCacheCanvas.drawText("请在此签名", width / 2F - (textWidth / 2), height / 2F, mTextPaint)
}
override fun onTouchEvent(event: MotionEvent?): Boolean {
when (event?.action) {
MotionEvent.ACTION_DOWN -> {
touchDown(event)
}
MotionEvent.ACTION_MOVE -> {
touchMove(event)
}
MotionEvent.ACTION_UP -> {
//将路径画到Bitmap,即一笔画完才更新bitmap,不会那么频繁,但是轨迹路径还是实时显示在画板上
mCacheCanvas.drawPath(mPath, mGesturePaint)
//清空路径
mPath.reset()
}
}
invalidate()
return true
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
//画此次笔画之前的内容,内容已保存在mCacheBitmap
canvas?.drawBitmap(mCacheBitmap, 0F, 0F, mGesturePaint)
//实时画路径
canvas?.drawPath(mPath, mGesturePaint)
}
private fun touchDown(event: MotionEvent?) {
if (!hintTextIsClear) {
clear()
hintTextIsClear = true
}
//清空路径
mPath.reset()
val startX = event?.x ?: 0F
val startY = event?.y ?: 0F
mX = startX
mY = startY
//设置路径的起点
mPath.moveTo(startX, startY)
}
private fun touchMove(event: MotionEvent?) {
val x = event?.x ?: 0F
val y = event?.y ?: 0F
val previousX = mX
val previousY = mY
//平滑曲线,previousX, previousY控制点,x,y结束点
mPath.quadTo(previousX, previousY, x, y)
mX = x
mY = y
}
public fun clear() {
mCacheCanvas?.run {
drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR)
}
}
public fun getBitMap(): Bitmap {
val bitmap = mCacheBitmap
//20kb
ImageUtils.compressByQuality(bitmap, 20 * 1024L)
return bitmap
}
public fun getBitmapFile(): File {
val file = File(Environment.getExternalStorageDirectory(), "${System.currentTimeMillis()}.png")
val fileOutputStream = FileOutputStream(file)
try {
fileOutputStream.write(ImageUtils.bitmap2Bytes(getBitMap()))
fileOutputStream.flush()
fileOutputStream.close()
} catch (e: IOException) {
e.printStackTrace()
}
return file
}
}
计算文字长宽
/**
* calculates the approximate width of a text, depending on a demo text
* avoid repeated calls (e.g. inside drawing methods)
*
* @param paint
* @param demoText
* @return
*/
fun calcTextWidth(paint: Paint, demoText: String?): Int {
return paint.measureText(demoText).toInt()
}
private val mCalcTextHeightRect = Rect()
/**
* calculates the approximate height of a text, depending on a demo text
* avoid repeated calls (e.g. inside drawing methods)
*
* @param paint
* @param demoText
* @return
*/
fun calcTextHeight(paint: Paint, demoText: String): Int {
val r = mCalcTextHeightRect
r[0, 0, 0] = 0
paint.getTextBounds(demoText, 0, demoText.length, r)
return r.height()
}
xml中使用:
<xxxxxxxx.view.SignView
android:id="@+id/sign_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="10dp"
android:background="@drawable/shape_dash_corner_8" />
虚线边框被灰色背景
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<stroke
android:width="1dp"
android:color="@color/text_color_cccccc"
android:dashWidth="2dp"
android:dashGap="2dp" />
<corners android:radius="8dp" />
<solid android:color="@color/f8f8f8" />
</shape>