Simple Android Signature Capture Tutorial and Source Code

Hello my friends! Today’s code is about capturing a user’s signature and saving it as a PNG image in the device SD card.

This android signature capture tutorial is useful if your app requires some signature for purposes like purchases, receipts or any transactions that needs a signature as a proof .

Our simple program below will have two buttons, a ‘save’ and ‘clear’ buttons and a canvas where the user can draw his signature. The program output is simple, so the code should be simple too! So…

The content of this post includes:

1.0 Java and XML Files

1.1 MainActivity.java

1.2 SignatureMainLayout.java

1.3 AndroidManifest.xml

2.0 Source Code Download

 

Alright, let’s get started!

1.0 Java and XML Files

1.1 MainActivity.java

On our main activity, we are just going to set SignatureMainLayout as the primary content view.

package com.example.takesignature;

import android.app.Activity;
import android.os.Bundle;

import com.signature.SignatureMainLayout;

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(new SignatureMainLayout(this));
    }
}

1.2 SignatureMainLayout.java

This file actually contains two classes: The SignatureMainLayout that returns the user interfaces, and, the SignatureView that handles the drawing in the signature canvas.

package com.signature;

import java.io.File;
import java.io.FileOutputStream;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.os.Environment;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.Toast;

public class SignatureMainLayout extends LinearLayout implements OnClickListener {

	LinearLayout buttonsLayout;
	SignatureView signatureView;

	public SignatureMainLayout(Context context) {
		super(context);

		this.setOrientation(LinearLayout.VERTICAL);

		this.buttonsLayout = this.buttonsLayout();
		this.signatureView = new SignatureView(context);

		// add the buttons and signature views
		this.addView(this.buttonsLayout);
		this.addView(signatureView);

	}

	private LinearLayout buttonsLayout() {

		// create the UI programatically
		LinearLayout linearLayout = new LinearLayout(this.getContext());
		Button saveBtn = new Button(this.getContext());
		Button clearBtn = new Button(this.getContext());

		// set orientation
		linearLayout.setOrientation(LinearLayout.HORIZONTAL);
		linearLayout.setBackgroundColor(Color.GRAY);

		// set texts, tags and OnClickListener
		saveBtn.setText("Save");
		saveBtn.setTag("Save");
		saveBtn.setOnClickListener(this);

		clearBtn.setText("Clear");
		clearBtn.setTag("Clear");
		clearBtn.setOnClickListener(this);

		linearLayout.addView(saveBtn);
		linearLayout.addView(clearBtn);

		// return the whoe layout
		return linearLayout;
	}

	// the on click listener of 'save' and 'clear' buttons
	@Override
	public void onClick(View v) {
		String tag = v.getTag().toString().trim();

		// save the signature
		if (tag.equalsIgnoreCase("save")) {
			this.saveImage(this.signatureView.getSignature());
		} 

		// empty the canvas
		else {
			this.signatureView.clearSignature();
		}

	}

	/**
	 * save the signature to an sd card directory
	 * @param signature bitmap
	 */
	final void saveImage(Bitmap signature) {

		String root = Environment.getExternalStorageDirectory().toString();

		// the directory where the signature will be saved
		File myDir = new File(root + "/saved_signature");

		// make the directory if it does not exist yet
		if (!myDir.exists()) {
			myDir.mkdirs();
		}

		// set the file name of your choice
		String fname = "signature.png";

		// in our case, we delete the previous file, you can remove this
		File file = new File(myDir, fname);
		if (file.exists()) {
			file.delete();
		}

		try {

			// save the signature
			FileOutputStream out = new FileOutputStream(file);
			signature.compress(Bitmap.CompressFormat.PNG, 90, out);
			out.flush();
			out.close();

			Toast.makeText(this.getContext(), "Signature saved.", Toast.LENGTH_LONG).show();

		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * The View where the signature will be drawn
	 */
	private class SignatureView extends View {

		// set the stroke width
		private static final float STROKE_WIDTH = 5f;
		private static final float HALF_STROKE_WIDTH = STROKE_WIDTH / 2;

		private Paint paint = new Paint();
		private Path path = new Path();

		private float lastTouchX;
		private float lastTouchY;
		private final RectF dirtyRect = new RectF();

		public SignatureView(Context context) {

			super(context);

			paint.setAntiAlias(true);
			paint.setColor(Color.BLACK);
			paint.setStyle(Paint.Style.STROKE);
			paint.setStrokeJoin(Paint.Join.ROUND);
			paint.setStrokeWidth(STROKE_WIDTH);

			// set the bg color as white
			this.setBackgroundColor(Color.WHITE);

			// width and height should cover the screen
			this.setLayoutParams(new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));

		}

		/**
		 * Get signature
		 *
		 * @return
		 */
		protected Bitmap getSignature() {

			Bitmap signatureBitmap = null;

			// set the signature bitmap
			if (signatureBitmap == null) {
				signatureBitmap = Bitmap.createBitmap(this.getWidth(), this.getHeight(), Bitmap.Config.RGB_565);
			}

			// important for saving signature
			final Canvas canvas = new Canvas(signatureBitmap);
			this.draw(canvas);

			return signatureBitmap;
		}

		/**
		 * clear signature canvas
		 */
		private void clearSignature() {
			path.reset();
			this.invalidate();
		}

		// all touch events during the drawing
		@Override
		protected void onDraw(Canvas canvas) {
			canvas.drawPath(this.path, this.paint);
		}

		@Override
		public boolean onTouchEvent(MotionEvent event) {
			float eventX = event.getX();
			float eventY = event.getY();

			switch (event.getAction()) {
			case MotionEvent.ACTION_DOWN:

				path.moveTo(eventX, eventY);

				lastTouchX = eventX;
				lastTouchY = eventY;
				return true;

			case MotionEvent.ACTION_MOVE:

			case MotionEvent.ACTION_UP:

				resetDirtyRect(eventX, eventY);
				int historySize = event.getHistorySize();
				for (int i = 0; i < historySize; i++) {
					float historicalX = event.getHistoricalX(i);
					float historicalY = event.getHistoricalY(i);

					expandDirtyRect(historicalX, historicalY);
					path.lineTo(historicalX, historicalY);
				}
				path.lineTo(eventX, eventY);
				break;

			default:

				return false;
			}

			invalidate((int) (dirtyRect.left - HALF_STROKE_WIDTH),
					(int) (dirtyRect.top - HALF_STROKE_WIDTH),
					(int) (dirtyRect.right + HALF_STROKE_WIDTH),
					(int) (dirtyRect.bottom + HALF_STROKE_WIDTH));

			lastTouchX = eventX;
			lastTouchY = eventY;

			return true;
		}

		private void expandDirtyRect(float historicalX, float historicalY) {
			if (historicalX < dirtyRect.left) {
				dirtyRect.left = historicalX;
			} else if (historicalX > dirtyRect.right) {
				dirtyRect.right = historicalX;
			}

			if (historicalY < dirtyRect.top) {
				dirtyRect.top = historicalY;
			} else if (historicalY > dirtyRect.bottom) {
				dirtyRect.bottom = historicalY;
			}
		}

		private void resetDirtyRect(float eventX, float eventY) {
			dirtyRect.left = Math.min(lastTouchX, eventX);
			dirtyRect.right = Math.max(lastTouchX, eventX);
			dirtyRect.top = Math.min(lastTouchY, eventY);
			dirtyRect.bottom = Math.max(lastTouchY, eventY);
		}

	}

}

1.3 AndroidManifest.xml

Since we are saving the image as PNG in the device SD card, we have to put a certain permission on the manifest. You AndroidManifest.xml should look like this:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.takesignature"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="14"
        android:targetSdkVersion="14" />

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="com.example.takesignature.MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

 

2.0 Source Code Download

Buy now

Note: The code is working but this write up is not yet in its final form. I posted it right away because some people might need this kind of code at the moment.

18 replies
  1. Zoudan
    Zoudan says:

    Jesus christ, this just saved me so much time that I will drink a beer for your sake, and I would pay you one if I’d have the chance.

    Thank you so much for your work, and for sharing it, keep going with your awesome job!

    Reply
  2. Ryan
    Ryan says:

    I just want to ask, what if i have 2 activities and this is called in the second activity, how do i go back to the main activity whenever i click on another button that i added which is back. I understand the the whole program is in a java class, not in an activity. You just used “setContentView”.

    Reply
    • Mike Dalisay
      Mike Dalisay says:

      Hello @Ryan, the program used an activity, so you won’t have any problems using the back button.

      As you can see in section 1.1 above.


      public class MainActivity extends Activity {...

      Reply
  3. Jose Dias
    Jose Dias says:

    This is a huge help Mike.
    How do you set the content view back or move to another layout after the user press the save button?

    Thank You in Advance!

    Reply

Leave a Reply

Want to join the discussion?
Feel free to contribute!

Leave a Reply

Your email address will not be published. Required fields are marked *