I am working on implementing digital signature functionality in android app using custom view.
I want user be able to clear and retry new signature without closing dialog or re-creating activity.When I clear,it successfully clears the signature but doesn't allow to draw a new signature and draws some black overlays along with previously drawn signature.
Here is the class which draws digital signature.
Signature.java
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.PorterDuff;
import android.provider.MediaStore;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import java.io.File;
import java.io.FileOutputStream;
public class Signature extends View
{
public static final float STROCK_WIDTH = 5f;
public static final float HALF_STROKE_WIDTH = STROCK_WIDTH / 2;
private Paint paint = new Paint();
View mContent;
File mPath1;
private Paint mPaint;
private Bitmap mBitmap;
private Canvas mCanvas;
private Path mPath;
private Paint mBitmapPaint;
boolean clear = false;
public Signature(Context context)
{
super(context);
}
public Signature(Context context, AttributeSet attrs)
{
super(context, attrs);
paint.setAntiAlias(true);
paint.setColor(Color.BLACK);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeJoin(Paint.Join.ROUND);
paint.setStrokeWidth(STROCK_WIDTH);
}
public Signature(Context context, AttributeSet attrs, View view, File pathToSave)
{
this(context, attrs);
mContent = view;
mPath1 = pathToSave;
mPath = new Path();
mBitmapPaint = new Paint(Paint.DITHER_FLAG);
setPaint();
}
public void setPaint()
{
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setDither(true);
mPaint.setColor(0xFFFF0000);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeJoin(Paint.Join.ROUND);
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setStrokeWidth(12);
}
public String save(View v)
{
String imagePath = null;
Log.v("TAG", "Width :" + v.getWidth());
Log.v("TAG", "Height :" + v.getHeight());
if (mBitmap == null)
{
mBitmap = Bitmap.createBitmap(mContent.getWidth(), mContent.getHeight(), Bitmap.Config.RGB_565);
}
mBitmap = v.getDrawingCache();
try
{
FileOutputStream mFileOutStream = new FileOutputStream(mPath1);
mBitmap.compress(Bitmap.CompressFormat.PNG, 90, mFileOutStream);
mFileOutStream.flush();
mFileOutStream.close();
imagePath = MediaStore.Images.Media.insertImage(getContext().getContentResolver(), mBitmap, "title", null);
Log.v("log_tag", "url: " + imagePath + "::we are saving at :" + mPath1);
}
catch (Exception e)
{
Log.v("log_tag", e.toString());
}
return imagePath;
}
public void clear()
{
mPath.reset();
mPaint.reset();
clear = true;
mPath1.delete();
mBitmapPaint = new Paint(Paint.DITHER_FLAG);
postInvalidate();
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh)
{
super.onSizeChanged(w, h, oldw, oldh);
try
{
mBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
mCanvas = new Canvas(mBitmap);
}
catch (Exception e)
{
e.printStackTrace();
}
}
@Override
protected void onDraw(Canvas canvas)
{
if (clear)
{
clear = false;
}
else
{
canvas.drawColor(0xFFFFFFFF);
canvas.drawBitmap(mBitmap, 0, 0, mBitmapPaint);
canvas.drawPath(mPath, mPaint);
}
}
private float mX, mY;
private static final float TOUCH_TOLERANCE = 4;
private void touch_start(float x, float y)
{
mPath.reset();
mPath.moveTo(x, y);
mX = x;
mY = y;
}
private void touch_move(float x, float y)
{
float dx = Math.abs(x - mX);
float dy = Math.abs(y - mY);
if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE)
{
mPath.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2);
mX = x;
mY = y;
}
}
private void touch_up()
{
mPath.lineTo(mX, mY);
// commit the path to our offscreen
mCanvas.drawPath(mPath, mPaint);
// kill this so we don't double draw
mPath.reset();
}
@Override
public boolean onTouchEvent(MotionEvent event)
{
float x = event.getX();
float y = event.getY();
switch (event.getAction())
{
case MotionEvent.ACTION_DOWN:
touch_start(x, y);
invalidate();
break;
case MotionEvent.ACTION_MOVE:
touch_move(x, y);
invalidate();
break;
case MotionEvent.ACTION_UP:
touch_up();
invalidate();
break;
}
return true;
}
}
Activity in which above class is used
MainActivity.java
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.Dialog;
import android.content.ContextWrapper;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.drawable.BitmapDrawable;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.provider.MediaStore;
import android.support.annotation.Nullable;
import android.support.v7.app.ActionBarActivity;
import android.support.v7.app.AppCompatActivity;
import android.util.Base64;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.Toast;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.util.Calendar;
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
Button btnOpen;
String imagePath = null,str_signature = "";
ImageView iv_sign;
public static String tempDir;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
try {
btnOpen= (Button) findViewById(R.id.btnOpen);
btnOpen.setOnClickListener(this);
iv_sign= (ImageView) findViewById(R.id.ivSign);
}
catch (Exception ex)
{
ex.printStackTrace();
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
public void onClick(View v) {
if(v==btnOpen)
{
SignatureFragment dialog = new SignatureFragment(onImageClicked);
dialog.show(MainActivity.this.getSupportFragmentManager(), "NoticeDialogFragment");
}
}
ImageSaved onImageClicked = new ImageSaved() {
@Override
public void onImageSaved(String path) {
imagePath = path;
Uri uri = Uri.parse(imagePath);
Log.d("TAG", "We got image path :" + imagePath);
try {
Bitmap bitmap = MediaStore.Images.Media.getBitmap(getContentResolver(), uri);
iv_sign.setImageBitmap(bitmap);
ImageView img = iv_sign;
BitmapDrawable mBitmapDrawable = (BitmapDrawable) img.getDrawable();
Bitmap mBitmap = mBitmapDrawable.getBitmap();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
mBitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos);
byte[] byteArrayImage = baos.toByteArray();
str_signature = Base64.encodeToString(byteArrayImage,
Base64.DEFAULT);
} catch (IOException e) {
e.printStackTrace();
}
}
};
public static class SignatureFragment extends android.support.v4.app.DialogFragment implements View.OnClickListener {
LinearLayout ll_signature_view;
Signature signature;
Button btn_done, btn_clear,btnCancel;
String EXTERNAL_DIR = "TTD";
ImageSaved saveListner;
File mypath;
@SuppressLint("ValidFragment")
public SignatureFragment(ImageSaved onImageClicked) {
saveListner = onImageClicked;
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
}
@Override
public void onDetach() {
saveListner = null;
super.onDetach();
}
public SignatureFragment() {
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setStyle(android.support.v4.app.DialogFragment.STYLE_NO_FRAME, R.style.Base_Theme_AppCompat_Light_Dialog);
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
Dialog dialog = super.onCreateDialog(savedInstanceState);
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
return dialog;
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.signature_view, container, false);
try
{
ll_signature_view = (LinearLayout) rootView.findViewById(R.id.ll_signature_view);
btn_done = (Button) rootView.findViewById(R.id.btn_done);
btn_clear = (Button) rootView.findViewById(R.id.btn_clear);
btnCancel = (Button) rootView.findViewById(R.id.btn_cancel);
tempDir = Environment.getExternalStorageDirectory() + "/" + EXTERNAL_DIR + "/";
File directory = getActivity().getCacheDir();
prepareDirectory();
String uniqueId = getTodaysDate() + "_" + getCurrentTime() + "_" + Math.random();
String current = uniqueId + ".png";
mypath = new File(directory, current);
signature = new Signature(getActivity(), null, ll_signature_view, mypath);
ll_signature_view.setBackgroundColor(Color.WHITE);
ll_signature_view.addView(signature, LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT);
btn_done.setOnClickListener(this);
btn_clear.setOnClickListener(this);
btnCancel.setOnClickListener(this);
}
catch(Exception ex)
{
ex.printStackTrace();
}
return rootView;
}
@Override
public void onClick(View v) {
if (v == btn_done) {
ll_signature_view.setDrawingCacheEnabled(true);
String imagePath = signature.save(ll_signature_view);
if (saveListner != null) {
saveListner.onImageSaved(imagePath);
}
dismiss();
}
if (v == btnCancel) {
dismiss();
}
if(v==btn_clear)
{
signature.clear();
}
}
private boolean prepareDirectory() {
try {
if (makedirs()) {
return true;
} else {
return false;
}
} catch (Exception e) {
e.printStackTrace();
Toast.makeText(getActivity(), "Could not initiate File System.. Is Sdcard mounted properly?", Toast.LENGTH_SHORT).show();
return false;
}
}
private boolean makedirs() {
File tempdir = new File(tempDir);
if (!tempdir.exists()) {
tempdir.mkdirs();
}
if (tempdir.isDirectory()) {
File[] files = tempdir.listFiles();
for (File file : files) {
if (!file.delete()) {
System.out.println("Failed to delete " + file);
}
}
}
return (tempdir.isDirectory());
}
private String getTodaysDate() {
final Calendar c = Calendar.getInstance();
int todaysDate = (c.get(Calendar.YEAR) * 10000) +
((c.get(Calendar.MONTH) + 1) * 100) +
(c.get(Calendar.DAY_OF_MONTH));
Log.w("DATE:", String.valueOf(todaysDate));
return (String.valueOf(todaysDate));
}
private String getCurrentTime() {
final Calendar c = Calendar.getInstance();
int currentTime = (c.get(Calendar.HOUR_OF_DAY) * 10000) +
(c.get(Calendar.MINUTE) * 100) +
(c.get(Calendar.SECOND));
Log.w("TIME:", String.valueOf(currentTime));
return (String.valueOf(currentTime));
}
}
}
Layout File
signature_view.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:layout_margin="8dp">
<LinearLayout
android:id="@+id/ll_signature_view"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:background="@drawable/edittext_border_white"
android:orientation="horizontal">
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:layout_marginTop="8dp">
<Button
android:id="@+id/btn_done"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="DONE" />
<Button
android:id="@+id/btn_clear"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="CLEAR" />
<Button
android:id="@+id/btn_cancel"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="CANCEL" />
</LinearLayout>
</LinearLayout>
activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="@dimen/activity_vertical_margin"
tools:context=".MainActivity"
android:background="@android:color/white">
<TextView
android:id="@+id/tvWelcome"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/Welcome" />
<Button
android:layout_marginTop="10dp"
android:id="@+id/btnOpen"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/Open"
android:gravity="center"
android:layout_below="@+id/tvWelcome"/>
<ImageView
android:id="@+id/ivSign"
android:layout_marginTop="10dp"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white"
android:layout_below="@+id/btnOpen"
/>
</RelativeLayout>
mCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR)
try this instead
canvas.drawColor(0xFFFFFFFF);
and also set Layer Type
mCanvas.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
Answering my own question here..:)
As per my requirement above,I have closed and reopened an dialog which solved my issue.
In MainActivity.java
if(v==btn_clear)
{
dismiss();
SignatureFragment dialog = new SignatureFragment(saveListner);
dialog.show(getActivity().getSupportFragmentManager(), "NoticeDialogFragment");
}
Use a library: 'com.github.gcacace:signature-pad:1.2.1'
In this you can use clear() method of SignaturePad class. Using clear all entered data will be removed and you can add new content.
User contributions licensed under CC BY-SA 3.0