Android签字面板主要是一个自定义的view,然后记录手指在view上的移动轨迹,再将轨迹保存为图片文件。
具体步骤:
- 实现一个view的子类,子类需要定义一个用作缓冲的canvas,然后定义一个bitmap(用作保存轨迹图片),将此bitmap设置到缓冲的画布上
package com.yk.skill.androidskillplatform.selfcreate.double_cache_canvas.view import android.content.Context import android.graphics.* import android.os.Environment import android.util.AttributeSet import android.util.Log import android.view.MotionEvent import android.view.View import java.io.File import java.io.FileOutputStream /** * Created by Administrator on 2018/3/15. */ class SingleCacheCanvasView : View { var path:Path = Path()//触摸产生的路径 var cacheBmp:Bitmap? = null//缓冲canvas的bitmap var cacheCanvas: Canvas? = null//缓冲canvas var cachePaint:Paint? = null//缓冲canvas画轨迹时的所使用画笔 constructor(context: Context?) : super(context){ initView(context,null,0) } constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs){ initView(context,attrs,0) } constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr){ initView(context,attrs,defStyleAttr) } /** * 初始化一些参数,比如缓冲画笔、画布canvas、bitmap */ private fun initView(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) { cachePaint = Paint() cachePaint?.strokeWidth = 10f cachePaint?.color = Color.BLUE cachePaint?.style = Paint.Style.STROKE } /** * 不是必须的,这里是设置缓冲canvas的大小(设置的bitmap位图大小就是canvas的大小) * 如果必须计算view的大小才能设置缓冲canvas的大小,建议把将缓冲canvas的bitmap放在这初始化,并将bitmap设置到缓冲canvas上,因为initView方法在onMeasure方法前执行 */ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { var width = MeasureSpec.getSize(widthMeasureSpec) var height = MeasureSpec.getSize(heightMeasureSpec) cacheBmp = Bitmap.createBitmap(width,height,Bitmap.Config.ARGB_8888) cacheCanvas = Canvas(cacheBmp) Log.e("SCCV","width="+width) Log.e("SCCV","height="+height) super.onMeasure(widthMeasureSpec, heightMeasureSpec) } }
- 复写onTouchEvent方法,在此方法中生成一个触摸产生的轨迹的path,然后将path路径绘制到一个缓冲的canvas上
//触摸事件产生的轨迹绘制 override fun onTouchEvent(event: MotionEvent?): Boolean { var x = event?.x var y = event?.y when(event?.action){ MotionEvent.ACTION_DOWN->{ x = event?.x y = event?.y path.moveTo(x!!,y!!) } MotionEvent.ACTION_MOVE->{ x = event?.x y = event?.y path.lineTo(x!!,y!!) } MotionEvent.ACTION_UP->{ x = event?.x y = event?.y path.lineTo(x!!,y!!) } } PAINT_STYLE_PAINT->cacheCanvas?.drawPath(path,cachePaint) invalidate() return true }
- 复写onDraw方法,将缓冲的canvas绘制到实际展现canvas上
/** * 在canvas上绘制view展现出来的轨迹 */ override fun onDraw(canvas: Canvas?) { super.onDraw(canvas) var paint = Paint() paint.strokeWidth = 10f paint.style = Paint.Style.STROKE paint.color = Color.GREEN canvas?.drawBitmap(cacheBmp,0f,0f,paint) }
- 图片的保存
至此,一个简单的签字面板的view就完成了,下面会增加一些功能用于控制签字面板的字迹清除、保存等按钮!//保存轨迹图片 fun saveBmp(){ var out = FileOutputStream(Environment.getExternalStorageDirectory().absoluteFile.toString()+File.separator+"test.png") cacheBmp?.compress(Bitmap.CompressFormat.PNG,0,out) }
- 轨迹的清除
//清除轨迹,思路---重新创建缓冲画布,以及将path路径重置 fun clearBmp(){ cacheBmp = Bitmap.createBitmap(cacheBmpWidth,cacheBmpHeight,Bitmap.Config.ARGB_8888) cacheBmp?.eraseColor(Color.WHITE) cacheCanvas = Canvas(cacheBmp) var paint = Paint() paint.color = Color.WHITE paint.style = Paint.Style.FILL cacheCanvas?.drawRect(0f,0f,cacheBmpWidth.toFloat(),cacheBmpHeight.toFloat(),paint) path.reset() invalidate() }
- 一个完整的签字面板还包括擦除签字轨迹的按钮和保存签字轨迹的按钮。我们使用一个viewgroup把签字面板以及清除、保存两个按钮给装到一起。
import android.animation.ObjectAnimator import android.content.Context import android.util.AttributeSet import android.util.Log import android.view.ViewGroup import android.widget.Button import com.yk.skill.androidskillplatform.selfcreate.single_panel.listener.CheckIsSingleListener import com.yk.skill.androidskillplatform.utils.WindowCalcUtils /** * Created by Administrator on 2018/3/22. */ class SinglePanelContainer:ViewGroup { constructor(context: Context?) : super(context){ initContainer(context,null,0) } constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs){ initContainer(context,attrs,0) } constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr){ initContainer(context,attrs,defStyleAttr) } private lateinit var cancelButton: Button//取消按钮,用于清除签字面板上的签字轨迹 private lateinit var okButton: Button//确认按钮,用于保存签字面板上的签字轨迹到图片 private lateinit var startAnimatorOK:ObjectAnimator//确认按钮的动画 private lateinit var startAnimatorCancel:ObjectAnimator//取消按钮的动画 /** * 初始化容器,包括签字面板的高宽,以及添加签字面板的事件监听 */ private fun initContainer(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) { //初始化,将自定的签字笔放进来 var singleCacheCanvasView = SingleCacheCanvasView(context) singleCacheCanvasView.buildHeight(WindowCalcUtils.getWindowsHeight(context)/2) singleCacheCanvasView.buildWidth(WindowCalcUtils.getWindowsWidth(context)) singleCacheCanvasView.addCheckIsSingleListener(object : CheckIsSingleListener { override fun startSingle() { if(startAnimatorOK.isRunning){ startAnimatorOK.cancel() } if(startAnimatorCancel.isRunning){ startAnimatorCancel.cancel() } startAnimatorOK.start() startAnimatorCancel.start() } override fun endSingle() { startAnimatorCancel.reverse() startAnimatorOK.reverse() } }) //将签字面板的view添加到容器中 addView(singleCacheCanvasView) cancelButton = Button(context) cancelButton.width = 200 cancelButton.height = 100 cancelButton.text = "cancel" cancelButton.setOnClickListener { singleCacheCanvasView.clearBmp() } //添加取消按钮到容器中 addView(cancelButton) okButton = Button(context) okButton.width = 200 okButton.height = 100 okButton.text = "ok" okButton.setOnClickListener { singleCacheCanvasView.saveBmp() } //添加确认按钮到容器中 addView(okButton) initAmimation() } /** * 动画的初始化 */ private fun initAmimation() { startAnimatorOK = ObjectAnimator.ofFloat(okButton, "alpha", 1f, 0f) startAnimatorOK.duration = 2000 startAnimatorCancel = ObjectAnimator.ofFloat(cancelButton, "alpha", 1f, 0f) startAnimatorCancel.duration = 2000 } //让子view能调用onMeasure方法 override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { for (i in 0..(childCount-1)){ var view = getChildAt(i) measureChild(view,widthMeasureSpec,heightMeasureSpec) Log.e("SPC","width="+view.width+"`````height="+view.height) } super.onMeasure(widthMeasureSpec, heightMeasureSpec) } override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) { //因为各个view放置到容器中的顺序和数量都是固定好的,所有这里就直接通过getChildAt去取,不判断容器中view的数量 var view = getChildAt(0) var w = (view as SingleCacheCanvasView).cacheBmpWidth var h = (view as SingleCacheCanvasView).cacheBmpHeight view.layout(0,0,view.measuredWidth+l,view.measuredHeight+t) view = getChildAt(1) view.layout(0,0+h-view.measuredHeight,l+view.measuredWidth,h) view = getChildAt(2) view.layout(0+w-view.measuredWidth,0+h-view.measuredHeight,l+w, h) } }