最近項(xiàng)目中有個(gè)新的需求抖韩,就是要實(shí)現(xiàn)用戶手寫簽名,然后展示再上傳到服務(wù)器疫铜∶。看到效果圖后,先是面對(duì)百度編程搜了一下壳咕,很多實(shí)現(xiàn)方法席揽,主要就是自定義View實(shí)現(xiàn)的,為了記錄其中的坑谓厘,并提升自己的自定義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. <br/>
* 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
? ? /**
? ? * 背景色(指最終簽名結(jié)果文件的背景顏色,這里我設(shè)置為白色)
? ? *? 你也可以設(shè)置為透明的
? ? */
? ? private var mBackColor = Color.WHITE
? ? //是否已經(jīng)簽名
? ? 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//設(shè)置簽名顏色
? ? ? ? paint?.style = Paint.Style.STROKE? //設(shè)置填充樣式
? ? ? ? paint?.isAntiAlias = true? //抗鋸齒功能
? ? ? ? paint?.strokeWidth = paintWidth//設(shè)置畫筆寬度
? ? }
? ? override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
? ? ? ? super.onSizeChanged(w, h, oldw, oldh)
? ? ? ? //創(chuàng)建跟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)
? ? ? ? // 通過畫布繪制多點(diǎn)形成的圖形
? ? ? ? canvas.drawPath(path,paint)
? ? }
? ? override fun onTouchEvent(event: MotionEvent): Boolean {
? ? ? ? //記錄每次 X 属桦, Y軸的坐標(biāo)
? ? ? ? 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住练,而手勢(shì)軌跡是實(shí)時(shí)顯示在畫板上的地啰。
? ? ? ? ? ? ? ? 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
? ? ? ? //? 如果圖片過大的話,需要壓縮圖片讲逛,不過在我測(cè)試手機(jī)上最大才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 這里可以擴(kuò)展一些setter方法
? ? /**
? ? * 是否有簽名
? ? *
? ? * @return isSigned
? ? */
? ? public fun getHasSigned() : Boolean{
? ? ? ? return isSigned
? ? }
}
在布局中引用:
? ? <RelativeLayout
? ? ? ? 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">
? ? ? ? <com.yuan.demo.view.SignView
? ? ? ? ? ? android:id="@+id/signView"
? ? ? ? ? ? android:layout_width="match_parent"
? ? ? ? ? ? android:layout_height="match_parent" />
? ? ? ? <ImageView
? ? ? ? ? ? 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" />
? ? </RelativeLayout>
在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("您還沒有簽名亏吝,請(qǐng)簽名!")
? ? ? ? }
? ? }
? 主要代碼及邏輯如上所示。
效果圖: