Digital Signature Clear issue in Android app

2

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> 
java
android
digital-signature
asked on Stack Overflow Jul 20, 2015 by Nitesh • edited Oct 5, 2017 by Vadim Kotov

3 Answers

0
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);
answered on Stack Overflow Oct 16, 2015 by Bhavesh Desai
0

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");
 } 
answered on Stack Overflow Oct 17, 2015 by Nitesh
0

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.

answered on Stack Overflow Mar 3, 2018 by snehal • edited Sep 30, 2018 by snehal

User contributions licensed under CC BY-SA 3.0