In this blog, I’ll show you how to capture signature using canvas and save it. For doing that we’ll create a custom SignatureView and use it in our sample app. So let’s get started.
1. Create an android project and add resource file in the string.xml
<resources> <string name="app_name">Capture Signature</string> <string name="ok">Ok</string> <string name="clear">Clear</string> <string name="cancel">Cancel</string> </resources>
<style name="Dialog.App" parent="AppTheme"> <item name="android:windowNoTitle">true</item> <item name="android:windowFullscreen">false</item> <item name="android:windowIsFloating">false</item> </style>
2. Write attribute for SignatureView
Create a new file inside res=>value=>attrs.xml, and paste below code
<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="SignatureView"> <attr name="penMinWidth" format="dimension" /> <attr name="penMaxWidth" format="dimension" /> <attr name="penColor" format="color" /> <attr name="velocityFilterWeight" format="float" /> <attr name="clearOnDoubleClick" format="boolean" /> </declare-styleable> </resources>
3. Create a Signature View.
In the src folder create a new java class named is SignatureView.java. let’s copy all code and paste into the own class. after that, we have to create a utils file that I explain below.
package com.capturesignature.view import android.content.Context import android.content.res.Resources.NotFoundException import android.graphics.* import android.graphics.Bitmap.Config.ARGB_8888 import android.graphics.Matrix.ScaleToFit.CENTER import android.os.Bundle import android.os.Parcelable import android.util.AttributeSet import android.view.MotionEvent import android.view.View import android.view.ViewTreeObserver.OnGlobalLayoutListener import com.capturesignature.R.styleable import com.capturesignature.utils.Bezier import com.capturesignature.utils.ControlTimedPoints import com.capturesignature.utils.TimedPoint import com.capturesignature.view.ViewCompat.isLaidOut import java.util.* import kotlin.math.max import kotlin.math.roundToInt class SignatureView(context: Context, attrs: AttributeSet?) : View(context, attrs) { //View state private var mPoints: MutableList<TimedPoint>? = null private var mIsEmpty = false private var mHasEditState: Boolean? = null private var mLastTouchX = 0f private var mLastTouchY = 0f private var mLastVelocity = 0f private var mLastWidth = 0f private val mDirtyRect: RectF private var mBitmapSavedState: Bitmap? = null // Cache private val mPointsCache: MutableList<TimedPoint> = ArrayList() private val mControlTimedPointsCached: ControlTimedPoints = ControlTimedPoints() private val mBezierCached: Bezier = Bezier() //Configurable parameters private var mMinWidth = 0 private var mMaxWidth = 0 private var mVelocityFilterWeight = 0f private var mOnSignedListener: OnSignedListener? = null private var mClearOnDoubleClick = false //Click values private var mFirstClick: Long = 0 private var mCountClick = 0 //Default attribute values private val DEFAULT_ATTR_PEN_MIN_WIDTH_PX = 2 private val DEFAULT_ATTR_PEN_MAX_WIDTH_PX = 3 private val DEFAULT_ATTR_PEN_COLOR = Color.BLACK private val DEFAULT_ATTR_VELOCITY_FILTER_WEIGHT = 0.9f private val DEFAULT_ATTR_CLEAR_ON_DOUBLE_CLICK = false private val mPaint = Paint() private var mSignatureBitmap: Bitmap? = null private var mSignatureBitmapCanvas: Canvas? = null override fun onSaveInstanceState(): Parcelable? { val bundle = Bundle() bundle.putParcelable("superState", super.onSaveInstanceState()) if (mHasEditState == null || mHasEditState!!) { mBitmapSavedState = getTransparentSignatureBitmap() } bundle.putParcelable("signatureBitmap", mBitmapSavedState) return bundle } override fun onRestoreInstanceState(state: Parcelable) { var state: Parcelable? = state if (state is Bundle) { val bundle = state setSignatureBitmap(bundle.getParcelable<Parcelable>("signatureBitmap") as Bitmap) mBitmapSavedState = bundle.getParcelable("signatureBitmap") state = bundle.getParcelable("superState") } mHasEditState = false super.onRestoreInstanceState(state) } /** * Set the pen color from a given resource. * If the resource is not found, [Color.BLACK] is assumed. * * @param colorRes the color resource. */ fun setPenColorRes(colorRes: Int) { try { setPenColor(resources.getColor(colorRes)) } catch (ex: NotFoundException) { setPenColor(Color.parseColor("#000000")) } } /** * Set the pen color from a given color. * * @param color the color. */ private fun setPenColor(color: Int) { mPaint.color = color } /** * Set the minimum width of the stroke in pixel. * * @param minWidth the width in dp. */ fun setMinWidth(minWidth: Float) { mMinWidth = convertDpToPx(minWidth) } /** * Set the maximum width of the stroke in pixel. * * @param maxWidth the width in dp. */ fun setMaxWidth(maxWidth: Float) { mMaxWidth = convertDpToPx(maxWidth) } /** * Set the velocity filter weight. * * @param velocityFilterWeight the weight. */ fun setVelocityFilterWeight(velocityFilterWeight: Float) { mVelocityFilterWeight = velocityFilterWeight } private fun clearView() { mPoints = ArrayList() mLastVelocity = 0f mLastWidth = (mMinWidth + mMaxWidth) / 2.toFloat() if (mSignatureBitmap != null) { mSignatureBitmap = null ensureSignatureBitmap() } setIsEmpty(true) invalidate() } fun clear() { clearView() mHasEditState = true } override fun onTouchEvent(event: MotionEvent): Boolean { if (!isEnabled) return false val eventX = event.x val eventY = event.y when (event.action) { MotionEvent.ACTION_DOWN -> { parent.requestDisallowInterceptTouchEvent(true) mPoints!!.clear() if (isDoubleClick()) { return false } mLastTouchX = eventX mLastTouchY = eventY addPoint(getNewPoint(eventX, eventY)) if (mOnSignedListener != null) mOnSignedListener!!.onStartSigning() resetDirtyRect(eventX, eventY) addPoint(getNewPoint(eventX, eventY)) setIsEmpty(false) } MotionEvent.ACTION_MOVE -> { resetDirtyRect(eventX, eventY) addPoint(getNewPoint(eventX, eventY)) setIsEmpty(false) } MotionEvent.ACTION_UP -> { resetDirtyRect(eventX, eventY) addPoint(getNewPoint(eventX, eventY)) parent.requestDisallowInterceptTouchEvent(true) } else -> return false } //invalidate(); invalidate( (mDirtyRect.left - mMaxWidth).toInt(), (mDirtyRect.top - mMaxWidth).toInt(), (mDirtyRect.right + mMaxWidth).toInt(), (mDirtyRect.bottom + mMaxWidth).toInt() ) return true } override fun onDraw(canvas: Canvas) { if (mSignatureBitmap != null) { canvas.drawBitmap(mSignatureBitmap!!, 0f, 0f, mPaint) } } fun setOnSignedListener(listener: OnSignedListener?) { mOnSignedListener = listener } private fun setIsEmpty(newValue: Boolean) { mIsEmpty = newValue if (mOnSignedListener != null) { if (mIsEmpty) { mOnSignedListener!!.onClear() } else { mOnSignedListener!!.onSigned() } } } fun getSignatureBitmap(): Bitmap { val originalBitmap = getTransparentSignatureBitmap() val whiteBgBitmap = Bitmap.createBitmap( originalBitmap!!.width, originalBitmap.height, ARGB_8888 ) val canvas = Canvas(whiteBgBitmap) canvas.drawColor(Color.WHITE) canvas.drawBitmap(originalBitmap, 0f, 0f, null) return whiteBgBitmap } fun setSignatureBitmap(signature: Bitmap) { if (isLaidOut(this)) { clearView() ensureSignatureBitmap() val tempSrc = RectF() val tempDst = RectF() val dWidth = signature.width val dHeight = signature.height val vWidth = width val vHeight = height // Generate the required transform. tempSrc[0f, 0f, dWidth.toFloat()] = dHeight.toFloat() tempDst[0f, 0f, vWidth.toFloat()] = vHeight.toFloat() val drawMatrix = Matrix() drawMatrix.setRectToRect(tempSrc, tempDst, CENTER) val canvas = Canvas(mSignatureBitmap!!) canvas.drawBitmap(signature, drawMatrix, null) setIsEmpty(false) invalidate() } else { viewTreeObserver.addOnGlobalLayoutListener(object : OnGlobalLayoutListener { override fun onGlobalLayout() { // Remove layout listener... ViewTreeObserverCompat.removeOnGlobalLayoutListener(viewTreeObserver, this) // Signature bitmap... setSignatureBitmap(signature) } }) } } private fun getTransparentSignatureBitmap(): Bitmap? { ensureSignatureBitmap() return mSignatureBitmap } private fun getTransparentSignatureBitmap(trimBlankSpace: Boolean): Bitmap? { if (!trimBlankSpace) { return getTransparentSignatureBitmap() } ensureSignatureBitmap() val imgHeight = mSignatureBitmap!!.height val imgWidth = mSignatureBitmap!!.width val backgroundColor = Color.TRANSPARENT var xMin = Int.MAX_VALUE var xMax = Int.MIN_VALUE var yMin = Int.MAX_VALUE var yMax = Int.MIN_VALUE var foundPixel = false // Find xMin for (x in 0 until imgWidth) { var stop = false for (y in 0 until imgHeight) { if (mSignatureBitmap!!.getPixel(x, y) != backgroundColor) { xMin = x stop = true foundPixel = true break } } if (stop) break } // Image is empty... if (!foundPixel) return null // Find yMin for (y in 0 until imgHeight) { var stop = false for (x in xMin until imgWidth) { if (mSignatureBitmap!!.getPixel(x, y) != backgroundColor) { yMin = y stop = true break } } if (stop) break } // Find xMax for (x in imgWidth - 1 downTo xMin) { var stop = false for (y in yMin until imgHeight) { if (mSignatureBitmap!!.getPixel(x, y) != backgroundColor) { xMax = x stop = true break } } if (stop) break } // Find yMax for (y in imgHeight - 1 downTo yMin) { var stop = false for (x in xMin..xMax) { if (mSignatureBitmap!!.getPixel(x, y) != backgroundColor) { yMax = y stop = true break } } if (stop) break } return Bitmap.createBitmap(mSignatureBitmap!!, xMin, yMin, xMax - xMin, yMax - yMin) } private fun isDoubleClick(): Boolean { if (mClearOnDoubleClick) { if (mFirstClick != 0L && System.currentTimeMillis() - mFirstClick > DOUBLE_CLICK_DELAY_MS) { mCountClick = 0 } mCountClick++ if (mCountClick == 1) { mFirstClick = System.currentTimeMillis() } else if (mCountClick == 2) { val lastClick = System.currentTimeMillis() if (lastClick - mFirstClick < DOUBLE_CLICK_DELAY_MS) { clearView() return true } } } return false } private fun getNewPoint( x: Float, y: Float ): TimedPoint { val mCacheSize = mPointsCache.size val timedPoint: TimedPoint if (mCacheSize == 0) { // Cache is empty, create a new point timedPoint = TimedPoint() } else { // Get point from cache timedPoint = mPointsCache.removeAt(mCacheSize - 1) } return timedPoint.set(x, y) } private fun recyclePoint(point: TimedPoint) { mPointsCache.add(point) } private fun addPoint(newPoint: TimedPoint) { mPoints!!.add(newPoint) val pointsCount = mPoints!!.size if (pointsCount > 3) { var tmp: ControlTimedPoints = calculateCurveControlPoints(mPoints!![0], mPoints!![1], mPoints!![2]) val c2: TimedPoint = tmp.c2 recyclePoint(tmp.c1) tmp = calculateCurveControlPoints(mPoints!![1], mPoints!![2], mPoints!![3]) val c3: TimedPoint = tmp.c1 recyclePoint(tmp.c2) val curve: Bezier = mBezierCached.set(mPoints!![1], c2, c3, mPoints!![2]) val startPoint: TimedPoint = curve.startPoint val endPoint: TimedPoint = curve.endPoint var velocity: Float = endPoint.velocityFrom(startPoint) velocity = if (java.lang.Float.isNaN(velocity)) 0.0f else velocity velocity = (mVelocityFilterWeight * velocity + (1 - mVelocityFilterWeight) * mLastVelocity) // The new width is a function of the velocity. Higher velocities // correspond to thinner strokes. val newWidth = strokeWidth(velocity) addBezier(curve, mLastWidth, newWidth) mLastVelocity = velocity mLastWidth = newWidth // Remove the first element from the list, // so that we always have no more than 4 mPoints in mPoints array. recyclePoint(mPoints!!.removeAt(0)) recyclePoint(c2) recyclePoint(c3) } else if (pointsCount == 1) { // To reduce the initial lag make it work with 3 mPoints // by duplicating the first point val firstPoint: TimedPoint = mPoints!![0] mPoints!!.add(getNewPoint(firstPoint.x, firstPoint.y)) } mHasEditState = true } private fun addBezier( curve: Bezier, startWidth: Float, endWidth: Float ) { // mSvgBuilder.append(curve, (startWidth + endWidth) / 2); ensureSignatureBitmap() val originalWidth = mPaint.strokeWidth val widthDelta = endWidth - startWidth val drawSteps = Math.ceil(curve.length().toDouble()) .toFloat() var i = 0 while (i < drawSteps) { // Calculate the Bezier (x, y) coordinate for this step. val t = i.toFloat() / drawSteps val tt = t * t val ttt = tt * t val u = 1 - t val uu = u * u val uuu = uu * u var x: Float = uuu * curve.startPoint.x x += 3 * uu * t * curve.control1.x x += 3 * u * tt * curve.control2.x x += ttt * curve.endPoint.x var y: Float = uuu * curve.startPoint.y y += 3 * uu * t * curve.control1.y y += 3 * u * tt * curve.control2.y y += ttt * curve.endPoint.y // Set the incremental stroke width and draw. mPaint.strokeWidth = startWidth + ttt * widthDelta mSignatureBitmapCanvas!!.drawPoint(x, y, mPaint) expandDirtyRect(x, y) i++ } mPaint.strokeWidth = originalWidth } private fun calculateCurveControlPoints( s1: TimedPoint, s2: TimedPoint, s3: TimedPoint ): ControlTimedPoints { val dx1: Float = s1.x - s2.x val dy1: Float = s1.y - s2.y val dx2: Float = s2.x - s3.x val dy2: Float = s2.y - s3.y val m1X: Float = (s1.x + s2.x) / 2.0f val m1Y: Float = (s1.y + s2.y) / 2.0f val m2X: Float = (s2.x + s3.x) / 2.0f val m2Y: Float = (s2.y + s3.y) / 2.0f val l1 = Math.sqrt(dx1 * dx1 + dy1 * dy1.toDouble()) .toFloat() val l2 = Math.sqrt(dx2 * dx2 + dy2 * dy2.toDouble()) .toFloat() val dxm = m1X - m2X val dym = m1Y - m2Y var k = l2 / (l1 + l2) if (java.lang.Float.isNaN(k)) k = 0.0f val cmX = m2X + dxm * k val cmY = m2Y + dym * k val tx: Float = s2.x - cmX val ty: Float = s2.y - cmY return mControlTimedPointsCached.set( getNewPoint(m1X + tx, m1Y + ty), getNewPoint(m2X + tx, m2Y + ty) ) } private fun strokeWidth(velocity: Float): Float { return max(mMaxWidth / (velocity + 1), mMinWidth.toFloat()) } /** * Called when replaying history to ensure the dirty region includes all * mPoints. * * @param historicalX the previous x coordinate. * @param historicalY the previous y coordinate. */ private fun expandDirtyRect( historicalX: Float, historicalY: Float ) { if (historicalX < mDirtyRect.left) { mDirtyRect.left = historicalX } else if (historicalX > mDirtyRect.right) { mDirtyRect.right = historicalX } if (historicalY < mDirtyRect.top) { mDirtyRect.top = historicalY } else if (historicalY > mDirtyRect.bottom) { mDirtyRect.bottom = historicalY } } /** * Resets the dirty region when the motion event occurs. * * @param eventX the event x coordinate. * @param eventY the event y coordinate. */ private fun resetDirtyRect( eventX: Float, eventY: Float ) { // The mLastTouchX and mLastTouchY were set when the ACTION_DOWN motion event occurred. mDirtyRect.left = Math.min(mLastTouchX, eventX) mDirtyRect.right = Math.max(mLastTouchX, eventX) mDirtyRect.top = Math.min(mLastTouchY, eventY) mDirtyRect.bottom = Math.max(mLastTouchY, eventY) } private fun ensureSignatureBitmap() { if (mSignatureBitmap == null) { mSignatureBitmap = Bitmap.createBitmap( width, height, ARGB_8888 ) mSignatureBitmapCanvas = Canvas(mSignatureBitmap!!) } } private fun convertDpToPx(dp: Float): Int { return (context.resources.displayMetrics.density * dp).roundToInt() } interface OnSignedListener { fun onStartSigning() fun onSigned() fun onClear() } private fun getPoints(): List<TimedPoint?>? { return mPoints } companion object { private const val DOUBLE_CLICK_DELAY_MS = 200 } init { val a = context.theme.obtainStyledAttributes( attrs, styleable.SignatureView, 0, 0 ) //Configurable parameters try { mMinWidth = a.getDimensionPixelSize( styleable.SignatureView_penMinWidth, convertDpToPx(DEFAULT_ATTR_PEN_MIN_WIDTH_PX.toFloat()) ) mMaxWidth = a.getDimensionPixelSize( styleable.SignatureView_penMaxWidth, convertDpToPx(DEFAULT_ATTR_PEN_MAX_WIDTH_PX.toFloat()) ) mPaint.color = a.getColor( styleable.SignatureView_penColor, DEFAULT_ATTR_PEN_COLOR ) mVelocityFilterWeight = a.getFloat( styleable.SignatureView_velocityFilterWeight, DEFAULT_ATTR_VELOCITY_FILTER_WEIGHT ) mClearOnDoubleClick = a.getBoolean( styleable.SignatureView_clearOnDoubleClick, DEFAULT_ATTR_CLEAR_ON_DOUBLE_CLICK ) } finally { a.recycle() } //Fixed parameters mPaint.isAntiAlias = true mPaint.style = Paint.Style.STROKE mPaint.strokeCap = Paint.Cap.ROUND mPaint.strokeJoin = Paint.Join.ROUND //Dirty rectangle to update only the changed portion of the view mDirtyRect = RectF() clearView() } }
4. Create a view utils file ViewCompat
package com.capturesignature.view import android.os.Build import android.view.View object ViewCompat { fun isLaidOut(view: View): Boolean { return if (Build.VERSION.SDK_INT >= 19) { view.isLaidOut } else view.width > 0 && view.height > 0 } }
5. Create a view utils class named ViewTreeObserverCompat
package com.capturesignature.view import android.annotation.SuppressLint import android.os.Build import android.view.ViewTreeObserver import android.view.ViewTreeObserver.OnGlobalLayoutListener object ViewTreeObserverCompat { /** * Remove a previously installed global layout callback. * @param observer the view observer * @param victim the victim */ @SuppressLint("NewApi") fun removeOnGlobalLayoutListener( observer: ViewTreeObserver, victim: OnGlobalLayoutListener? ) { // Future (API16+)... if (Build.VERSION.SDK_INT >= 16) { observer.removeOnGlobalLayoutListener(victim) } else { observer.removeGlobalOnLayoutListener(victim) } } }
6. Create another package named is utils and paste 3 utilizes class.
Bezier.java
package com.capturesignature.utils class Bezier { lateinit var startPoint: TimedPoint lateinit var control1: TimedPoint lateinit var control2: TimedPoint lateinit var endPoint: TimedPoint operator fun set( startPoint: TimedPoint, control1: TimedPoint, control2: TimedPoint, endPoint: TimedPoint ): Bezier { this.startPoint = startPoint this.control1 = control1 this.control2 = control2 this.endPoint = endPoint return this } fun length(): Float { val steps = 10 var length = 0f var cx: Double var cy: Double var px = 0.0 var py = 0.0 var xDiff: Double var yDiff: Double for (i in 0..steps) { val t = i.toFloat() / steps cx = point( t, startPoint.x, control1.x, control2.x, endPoint.x ) cy = point( t, startPoint.y, control1.y, control2.y, endPoint.y ) if (i > 0) { xDiff = cx - px yDiff = cy - py length += Math.sqrt(xDiff * xDiff + yDiff * yDiff) .toFloat() } px = cx py = cy } return length } private fun point( t: Float, start: Float, c1: Float, c2: Float, end: Float ): Double { return start * (1.0 - t) * (1.0 - t) * (1.0 - t) + 3.0 * c1 * (1.0 - t) * (1.0 - t) * t + 3.0 * c2 * (1.0 - t) * t * t + end * t * t * t } }
ControlTimedPoints.java
package com.capturesignature.utils class ControlTimedPoints { lateinit var c1: TimedPoint lateinit var c2: TimedPoint operator fun set(c1: TimedPoint, c2: TimedPoint): ControlTimedPoints { this.c1 = c1 this.c2 = c2 return this } }
TimedPoint.java
package com.capturesignature.utils class TimedPoint { var x = 0f var y = 0f var timestamp: Long = 0 operator fun set( x: Float, y: Float ): TimedPoint { this.x = x this.y = y timestamp = System.currentTimeMillis() return this } fun velocityFrom(start: TimedPoint): Float { var diff = timestamp - start.timestamp if (diff <= 0) { diff = 1 } var velocity = distanceTo(start) / diff if (java.lang.Float.isInfinite(velocity) || java.lang.Float.isNaN(velocity)) { velocity = 0f } return velocity } private fun distanceTo(point: TimedPoint): Float { return Math.sqrt( Math.pow( point.x - x.toDouble(), 2.0 ) + Math.pow(point.y - y.toDouble(), 2.0) ) .toFloat() } }
7. Create a layout file for capture signature pad
In this layout file, we have three buttons with SignatureView
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/colorWhite" android:fitsSystemWindows="true" tools:context=".MainActivity"> <Button android:id="@+id/buttonCancel" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="16dp" android:background="@color/colorAccent" android:text="@string/cancel" android:textColor="@drawable/button_text_color" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@+id/buttonClear" app:layout_constraintHorizontal_bias="0.5" app:layout_constraintStart_toStartOf="parent" /> <Button android:id="@+id/buttonClear" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="16dp" android:background="@color/colorAccent" android:text="@string/clear" android:textColor="@drawable/button_text_color" android:visibility="visible" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@+id/buttonOk" app:layout_constraintHorizontal_bias="0.5" app:layout_constraintStart_toEndOf="@+id/buttonCancel" /> <Button android:id="@+id/buttonOk" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="16dp" android:background="@color/colorAccent" android:text="@string/ok" android:textColor="@drawable/button_text_color" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.5" app:layout_constraintStart_toEndOf="@+id/buttonClear" /> <com.capturesignature.view.SignatureView android:id="@+id/signatureView" android:layout_width="0dp" android:layout_height="0dp" android:layout_marginBottom="16dp" app:layout_constraintBottom_toTopOf="@+id/buttonClear" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout>
8. Create a dialog fragment SignatureDialogFragment
package com.capturesignature import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.fragment.app.DialogFragment import com.capturesignature.view.SignatureView import kotlinx.android.synthetic.main.fragment_signature_dialog.* class SignatureDialogFragment(private val onSignedListener: OnSignedCaptureListener) : DialogFragment(), SignatureView.OnSignedListener { override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { isCancelable = false return inflater.inflate(R.layout.fragment_signature_dialog, container, false) } override fun getTheme(): Int { return R.style.Dialog_App } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) buttonCancel.setOnClickListener { dismiss() } buttonClear.setOnClickListener { signatureView.clear() } buttonOk.setOnClickListener { onSignedListener.onSignatureCaptured(signatureView.getSignatureBitmap(), "") dismiss() } signatureView.setOnSignedListener(this) } override fun onStartSigning() { } override fun onSigned() { buttonOk.isEnabled = true buttonClear.isEnabled = true } override fun onClear() { buttonClear.isEnabled = false buttonOk.isEnabled = false } }
Open the MainActivity class and paste below code
Basically, we are creating an instance of SignatureDialogFragment. This fragment is written a signature image Bitmap in onSignatureCaptured() callback.
package com.capturesignature import android.graphics.Bitmap import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import kotlinx.android.synthetic.main.activity_main.* class MainActivity : AppCompatActivity(), OnSignedCaptureListener { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) buttonShowDialog.setOnClickListener { showDialog() } } private fun showDialog() { val dialogFragment = SignatureDialogFragment(this) dialogFragment.show(supportFragmentManager, "signature") } override fun onSignatureCaptured(bitmap: Bitmap, fileUri: String) { imageView.setImageBitmap(bitmap) } }
Conclusion
In this blog, we have learned the implementation of a capture signature using Canvas in Android. I hope it’s helpful for you. Thanks for reading this post, I would like to recommend the new way of performing background jobs using Coroutines