Récupération de la signature d’un client dans une application

Salut à tous, Olivier au rapport.

Imaginons que dans le cadre de votre entreprise, vous êtes amené à effectuer des livraisons ou des interventions chez des clients. Alors, il est fort probable que vous ayez besoin de conserver une confirmation du bon déroulement de la livraison/l’intervention.
Et bien, cet article va vous permettre d’intégrer à votre application une des solutions possibles : à savoir, la signature digitale. Pour cela, nous allons créer une CustomView, ici appelée Signature.

Signature : notre CustomView

Signature est une vue assez simple qui permet d’afficher une zone délimitée par des bordures. C’est dans cette zone que tous les mouvements de l’utilisateur sur l’écran sont captés puis dessinés (voir onTouchEvent). Ensuite, il vous suffit d’appeler la méthode getBitmap pour récupérer, sous forme d’une Bitmap, le dessin fait par l’utilisateur dans l’espace dédié.

    public class Signature extends View {

        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 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(STROKE_WIDTH);
        }

        public Bitmap getBitmap() {
            return loadBitmapFromView(mLinear, false);
        }

        public Bitmap loadBitmapFromView(View rootView, boolean takeChild) {

            final ViewGroup view;

            if (takeChild)
                view = (ViewGroup) ((ViewGroup) rootView).getChildAt(0);
            else
                view = (ViewGroup) rootView;

            view.setDrawingCacheEnabled(true);
            view.buildDrawingCache();

            Bitmap b = Bitmap.createBitmap(
                    view.getWidth(),
                    view.getHeight(),
                    Bitmap.Config.RGB_565);

            Canvas c = new Canvas(b);

            view.draw(c);

            return b;
        }

        @Override
        protected void onDraw(Canvas canvas) {
            canvas.drawPath(path, 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:
                    debug("Ignored touch event: " + event.toString());
                    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 debug(String string) {

            Log.v("log_tag", string);

        }

        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);
        }
    }

Intégration de notre CustomView

Maintenant que nous avons vu tous les tenants et aboutissants de notre vue, il faut l’intégrer pour pouvoir ensuite l’utiliser.

Tout d’abord, côté XML, nous allons direct à l’essentiel : un LinearLayout qui contiendra notre Signature, deux EditText « Nom » et « Métier. Ceci afin d’avoir un complément d’informations sur la personne qui a signé et un AppCompatButton pour afficher une preview des données recueillies. Pour tout envoyer sur vos serveurs, par exemple.

<?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"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/white"
    android:orientation="vertical"
    android:padding="30dp">

        <LinearLayout
            android:id="@+id/signature_view"
            android:layout_width="match_parent"
            android:layout_height="250dp"
            android:orientation="vertical" />

        <LinearLayout
            android:id="@+id/signature_edit_name_root"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            android:weightSum="100">

            <TextView
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="20"
                android:text="@string/name"
                android:textColor="@color/colorPrimary"
                android:textSize="14sp"
                android:textStyle="normal" />

            <EditText
                android:id="@+id/signature_edit_name"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="80"
                android:inputType="text"
                android:lines="1"
                android:maxLines="1"
                android:theme="@style/StyleEdittext" />

        </LinearLayout>

        <LinearLayout
            android:id="@+id/signature_edit_job_root"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            android:weightSum="100">

            <TextView
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="20"
                android:text="@string/job"
                android:textColor="@color/colorPrimary"
                android:textSize="14sp"
                android:textStyle="normal" />

            <EditText
                android:id="@+id/signature_edit_job"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="80"
                android:inputType="text"
                android:lines="1"
                android:maxLines="1"
                android:theme="@style/StyleEdittext" />

        </LinearLayout>

    <android.support.v7.widget.AppCompatButton
        android:id="@+id/signature_save_btn"
        android:layout_width="150dp"
        android:layout_height="wrap_content"
        android:padding="15dp"
        android:background="@color/cpb_blue"
        android:text="@string/show_preview"
        android:layout_gravity="center_horizontal"
        android:layout_marginTop="15dp"/>

</LinearLayout>

Dans un soucis de clarté pour la rédaction de cet article, nous plaçons notre CustomView directement à la fin de notre SignatureActivity.  Pour autant, rien ne vous empêche de l’externaliser dans un package/module à part.

package com.olivier.project.activities;

import android.app.Activity;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
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.support.v4.content.ContextCompat;
import android.support.v7.app.AlertDialog;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.Toast;

import butterknife.BindView;
import butterknife.OnClick;
import com.olivier.project.R;
import com.olivier.project.views.SignaturePreview;

public class SignatureActivity extends BaseActivity {

    private final static String LOG_TAG = SignatureActivity.class.getSimpleName();

    @BindView(R.id.signature_view)
    LinearLayout mLinear;

    @BindView(R.id.signature_edit_name)
    EditText mNameEdit;

    @BindView(R.id.signature_edit_job)
    EditText mJobEdit;
    
    @OnClick(R.id.signature_save_btn)
    void save(){
        if(everythingIsOk()){
            showPreview();
        }else{
            Toast.makeText(this, getString(R.string.siganture_error), Toast.LENGTH_SHORT).show();
        }
    }

    private Signature mSignature;

    public static void startActivity(Activity activity) {
        Intent intent = new Intent(activity, SignatureActivity.class);
        activity.startActivity(intent);
    }

    @Override
    protected int getLayoutId() {
        return R.layout.activity_signature;
    }

    @Override
    protected void init() {
        initSignature();
    }
    
    private void initSignature(){
        mSignature = new Signature(this, null);
        mSignature.setBackground(ContextCompat.getDrawable(this, R.drawable.background_white_border_grey));

        mLinear.addView(mSignature, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
    }

    private void showPreview(){
        AlertDialog.Builder builder = new AlertDialog.Builder(this, R.style.UtilsDialogTheme);
        builder.setView(getPreview());
        builder.setPositiveButton(
                getString(R.string.ok),
                new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        dialog.dismiss();
                    }
                });
        builder.setCancelable(false);
        builder.show();
    }

    private SignaturePreview getPreview(){
        SignaturePreview preview = new SignaturePreview(this);
        preview.initView(mSignature.getBitmap(), mNameEdit.getText().toString(), mJobEdit.getText().toString());
        return preview;
    }

    private boolean everythingIsOk(){
        return (mSignature.getBitmap() != null)
                && !TextUtils.isEmpty(mNameEdit.getText().toString())
                && !TextUtils.isEmpty(mJobEdit.getText().toString());
    }

    /**
     * CLASS
     */
    public class Signature extends View {
       ...
    }
}

Enfin il convient d’initialiser nos trois vues principales (mLinear, mNameEdit et mJobEdit). Puis d’ajouter un onClickListener à notre bouton de preview. Il est conseillé d’associer une vérification des données entrées par l’utilisateur (voir la méthode everyThingIsOk).

Si tout se passe bien, vous devriez obtenir quelque chose de ce genre :

Articles liés
Refresh token
Refresh token – Android
Créer une application pour Apple TV
DataBinding pour les applications Windows
Android Tv et Nexus player

Laissez votre commentaire

Votre commentaire*

Votre Nom*
Votre site internet

20 − quatre =