Android application development practice-implement handwritten signature function in your own App

Due to the need for informatization, colleagues in my company hope to develop a mobile app that can be used to issue on-site penalties for violations discovered during grassroots inspections, instead of paper tickets. One of the software development requirements requires inspection units and inspected units to perform handwritten electronic signatures on mobile phones. It just so happened that the book “Advanced Android App Development and Practical Project Practice” edited by Mr. Ouyang Shen that I bought contained content about implementing handwritten electronic signatures, which was a good reference. The textbook provides relevant source code, but it needs to be modified according to my actual project. This log records the modification process memo.

First send a screenshot of the effect after implementation.

1. Reference materials

For the implementation of the handwritten signature function, refer to the content of Chapter 2 “2.2.3 Tracking the Sliding Trajectory to Realize Handwritten Signature” in “Advanced Android App Development and Project Practice”. The custom handwritten signature control code is provided in the book, and other codes are in the source code electronic document provided with the textbook. In order to respect the author’s work, I won’t post it.

2. Implement handwritten signatures in your own App

The two files “PathPosition.java” and “SignatureView.java” in the textbook can be used directly. The “SignatureActivity.java” and “activity_signature.xml” used to implement handwritten signatures need to be modified according to my own development needs.

The modifications include the following points:

1. The source code functions and interfaces provided by the textbook are somewhat complicated, which is good for learning, but for practical applications it is cumbersome and needs to be simplified. My own handwritten signature interface only has a customized SignatureView, clear button and submit button.

2. After clicking “End Signature” on the source code, the signature content will be displayed in the ImageView at the bottom of the interface. Click “Save Image File” to save the signature content as a JPG format file to the mobile phone storage space. In my app, After the signature is completed, click the “Submit Button” to return the signature content to the activity that calls “SignatureActivity.java” as a Bitmap object.

Achieved effects:

In the activity of issuing a ticket, clicking on the signature position will call “SignatureActivity.java”. After the user completes the handwritten signature, click the “Submit” button, and the handwritten signature will be returned to the activity of issuing a ticket. As shown in the following two pictures:

My modified code for “SignatureActivity.java”

import android.app.Activity;
import android.content.Intent;
import android.graphics.Bitmap;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;

import android.view.View;
import android.view.View.OnClickListener;
import android.widget.TextView;
import android.widget.Toast;

import com.bahamutj.penalty.widget.SignatureView;

import java.io.ByteArrayOutputStream;

public class SignatureActivity extends AppCompatActivity implements OnClickListener {

    private final static String TAG = "SignatureActivity";
    private SignatureView view_signature; // Declare a signature view object

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_signature);
        TextView tv_title = findViewById(R.id.tv_title); // title
        tv_title.setText(this.getString(R.string.SignatureActivity_title)); //Set the title name
        view_signature = findViewById(R.id.view_signature); // Signature area
        view_signature.setDrawingCacheEnabled(true); //Set the cache area to be available
        findViewById(R.id.iv_back).setOnClickListener(this); // return
        findViewById(R.id.btn_clear_signature).setOnClickListener(this); // Clear
        findViewById(R.id.btn_complete_signature).setOnClickListener(this); // Submit
    } // onCreate-end

    @Override
    public void onClick(View v) {
        if (v.getId() == R.id.iv_back) { // Clicked return icon
            finish(); //End the current active page
        } else if (v.getId() == R.id.btn_clear_signature) { // Clear button clicked
            view_signature.clear(); // Clear signature view
        } else if (v.getId() == R.id.btn_complete_signature) { // Clicked the submit button
            int mPathListSize = view_signature.getPathListSize(); // Get the size of the drawn path list for determination
            if (mPathListSize == 0) { // Equal to 0 means no path is drawn
                Toast.makeText(this, "Please sign before submitting", Toast.LENGTH_LONG).show();
                return;
            }
            Bitmap bitmap = view_signature.getDrawingCache(); // Get the bitmap object from the drawing cache
            if ( (bitmap == null) || (bitmap.isRecycled())) { // Do not submit if the object is empty
                Toast.makeText(this, "Please sign before submitting", Toast.LENGTH_LONG).show();
                return;
            }
            Intent intent = new Intent();
            // Store the bitmap as a byte array. The image data comes from the signature view object. The data does not need to be saved as a file.
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos);
            byte[] bytes = baos.toByteArray();
            // Store the byte array into the package
            intent.putExtra("bitmapbytes",bytes);
            // Return to the previous page with intent. RESULT_OK indicates successful processing
            setResult(Activity.RESULT_OK, intent);
            finish();
        }

    } // onClick-end
}

My modified “activity_signature.xml” layout file

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:orientation="vertical"
    android:background="@color/gray_245"
    tools:context=".SignatureActivity">

    <include layout="@layout/title_bar"/>
    
    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:padding="20dp"
        android:text="Please sign by hand in the signature area below"
        android:textColor="@color/gray_150"
        android:textSize="16sp"
        android:textStyle="bold" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:padding="10dp">

        <com.bahamutj.penalty.widget.SignatureView
            android:id="@ + id/view_signature"
            android:layout_width="match_parent"
            android:layout_height="200dp"
            android:background="@color/white"
            app:paint_color="#000000"
            app:stroke_width="10" />
        <!-- You can modify the color and line width here -->
        <!-- The color and line width are customized in attrs.xml -->

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="60dp"
            android:layout_marginTop="20dp"
            android:orientation="horizontal" >

            <Button
                android:id="@ + id/btn_clear_signature"
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:text="@string/signature_btn_clear"
                android:textColor="@color/white"
                android:backgroundTint="@color/blue_415"
                android:textSize="17sp"
                android:layout_marginEnd="20dp"
                style="?android:attr/buttonBarButtonStyle" />

            <Button
                android:id="@ + id/btn_complete_signature"
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:text="@string/signature_btn_complete"
                android:textColor="@color/white"
                android:backgroundTint="@color/blue_415"
                android:textSize="17sp"
                android:layout_marginStart="20dp"
                style="?android:attr/buttonBarButtonStyle" />
        </LinearLayout>

    </LinearLayout>

</LinearLayout>

The key code for calling “SignatureActivity.java” in the activity of issuing penalty orders

public class PenaltyActivity extends AppCompatActivity implements
        View.OnClickListener, DatePickerDialog.OnDateSetListener {

    private final int INSPECTED_CODE = 1; // Signature intent code of the person being checked
    private final int INSPECTOR_CODE = 2; // Check the signature intent code
    private ImageView iv_Inspected_photo, iv_Inspector_photo; // Declare two image view objects to store the inspected signature and the inspector's signature

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_penalty);

        iv_Inspected_photo = findViewById(R.id.iv_Inspected_photo); // Signature of the inspected unit (person)
        iv_Inspector_photo = findViewById(R.id.iv_Inspector_photo); // Check the signature of the person

        // Set up the listener
        findViewById(R.id.iv_Inspected_photo).setOnClickListener(this); // Get the signature of the inspected unit (person)
        findViewById(R.id.iv_Inspector_photo).setOnClickListener(this); // Get the inspector's signature

    


    @Override
    public void onClick(View v) {
        if (v.getId() == R.id.iv_Inspected_photo) { // Signature of the inspected unit (person)
            Intent intent = new Intent(this, SignatureActivity.class);
            startActivityForResult(intent, INSPECTED_CODE); // Obtain the handwritten signature of the unit (person) being inspected
        } else if (v.getId() == R.id.iv_Inspector_photo) { // Signature of the person being inspected
            Intent intent = new Intent(this, SignatureActivity.class);
            startActivityForResult(intent, INSPECTOR_CODE); // Get the examiner's handwritten signature
        }
    }



    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent intent) {
        super.onActivityResult(requestCode, resultCode, intent);
        if (intent!=null & amp; & amp; resultCode == RESULT_OK & amp; & amp; requestCode == INSPECTED_CODE) {
            // Retrieve the handwritten signature of the inspected unit (person)
            byte[] bytes =intent.getByteArrayExtra("bitmapbytes");
            Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
            iv_Inspected_photo.setImageBitmap(bitmap);
        } else if (intent!=null & amp; & amp; resultCode == RESULT_OK & amp; & amp; requestCode == INSPECTOR_CODE) {
            // Retrieve the examiner's handwritten signature
            byte[] bytes =intent.getByteArrayExtra("bitmapbytes");
            Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
            iv_Inspector_photo.setImageBitmap(bitmap);
        }
    }



}