Dématérialisation – Faire signer sur support tactile

Avec l’avènement des appareils tactiles, les solutions de dématérialisation se multiplient. Certains documents sont plus difficilement dématérialisables, c’est le cas en particulier des documents nécessitant une signature. Il existe évidemment des solutions de signature électronique mais elles ne peuvent pas être mise en place dans toutes les situations.

Imaginons le cas d’un technicien se déplaçant au domicile de son client pour réaliser une intervention, faire un rapport de cette dernière et enfin faire signer ce dernier au client. Il peut s’avérer intéressant, aujourd’hui, de saisir ce rapport sur tablette tactile.
Dans ce billet, je vais vous présenter comment créer un composant de signature pour appareils tactiles Android.
device-2011-09-01-220506
 

View, le composant de base

Lorsqu’on veut créer un nouveau composant visuel, celui-ci doit hériter de la classe View ou d’une de ses classes fille.

public class SignatureView extends View
{
    public SignatureView(Context c, AttributeSet set)
    {
        super(c,set);
     }
}

Une chose importante à savoir est que lorsque le constructeur de votre composant est appelé, sa taille initiale est de 0 sur 0. Vous ne pouvez donc pas y faire les initialisations nécessitant de connaître la taille réelle de votre composant.
Par contre, la méthode onSizeChanged vous informe dès que votre composant change de taille et notamment, lorsque celui-ci est inséré dans la hiérarchie des vues de l’activité. C’est donc ici que nous allons initialiser nos éléments.
On veut pouvoir dessiner sur notre composant mais aussi pouvoir sauvegarder le dessin. Nous allons donc garder une référence sur le Bitmap du Canvas.

    private static final int BACKGROUND = Color.WHITE;
    private Bitmap mBitmap;
    private Canvas mCanvas;
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh)
    {
    	mBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
        mCanvas = new Canvas();
        mCanvas.setBitmap(mBitmap);
        Paint p = new Paint();
        p.setColor(BACKGROUND);
        mCanvas.drawPaint(p);
    }

Saisir la signature

Nous allons utiliser la méthode onTouchEvent de la classe View. Cette méthode est appelée dès que l’utilisateur touche l’écran. L’unique paramètre de cette méthode est un objet MotionEvent. La méthode MotionEvent#getAction nous indique le type de mouvement détecté. Les 2 actions qui nous intéresse sont ACTION_DOWN (un appui) et ACTION_MOVE (un déplacement). Nous allons donc suivre tous les mouvements de l’utilisateur afin de dessiner son tracé.
Dans un souci évident de performances, le système n’appelle pas la méthode onTouchEvent à chaque position de l’utilisateur, mais « bufferise » celles-ci. Ainsi, l’objet MotionEvent contient les coordonnées du point courant où se situe l’utilisateur mais également un historique des points précédents depuis le précédent appel de onTouchEvent.
Voici donc à quoi ressemble notre méthode onTouchEvent.

    @Override
    public boolean onTouchEvent(MotionEvent event)
    {
        int action = event.getAction();
        if(action == MotionEvent.ACTION_MOVE || action == MotionEvent.ACTION_DOWN)
        {
        	int n = event.getHistorySize();
        	for (int i=0; i<n; i++)
        	{
        		// action here with event.getHistoricalX(i) and event.getHistoricalY(i)
        	}
        	// action here with event.getX() and event.getY()
        }
        return true;
    }

Nous allons à présent dessiner ce tracé. Lorsque l’action est ACTION_DOWN, nous dessinons un point aux coordonnées de l’évènement, par contre, lors d’un déplacement, nous devons relier ces points afin de dessiner tout le trajet. Pour cela, nous avons besoin de connaître en permanence le point précédent afin de le relier au point courant.

    private static final int SIZE = 4;
    private static final int FOREGROUND = Color.BLACK;
    private float mCurX;
    private float mCurY;
    @Override
    public boolean onTouchEvent(MotionEvent event)
    {
        int action = event.getAction();
        boolean line = action == MotionEvent.ACTION_MOVE;
        if(line || action == MotionEvent.ACTION_DOWN)
        {
        	final Paint p = new Paint();
        	p.setColor(FOREGROUND);
            p.setAntiAlias(true);
        	p.setStrokeWidth(SIZE);
        	int n = event.getHistorySize();
        	for (int i=0; i<n; i++)
        	{
        		drawPoint(event.getHistoricalX(i), event.getHistoricalY(i), line, p);
        	}
        	drawPoint(event.getX(), event.getY(), line, p);
        }
        return true;
    }
    private void drawPoint(float x, float y, boolean line, Paint p)
    {
        if (mBitmap != null)
        {
            if(line)
            {
            	mCanvas.drawLine(mCurX, mCurY, x, y, p);
            }
            else
            {
            	mCanvas.drawCircle(x, y, SIZE/2, p);
            }
            invalidate();
        }
        mCurX = x;
        mCurY = y;
    }

Effacer l’écran, récupérer l’image

Nous ajoutons 2 méthodes publiques à notre composant afin de pouvoir effacer l’écran et récupérer l’image. Pour effacer, on repeint tout simplement avec la couleur de fond.

    public void clear()
    {
        if (mCanvas != null)
        {
            reset();
            invalidate();
        }
    }
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh)
    {
    	mBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
        mCanvas = new Canvas();
        mCanvas.setBitmap(mBitmap);
    	reset();
    }
    private void reset()
    {
        Paint p = new Paint();
        p.setColor(BACKGROUND);
        mCanvas.drawPaint(p);
    }
    public Bitmap save()
    {
    	return mBitmap;
    }

 
device-2011-09-02-152002_noheader
 

Sauvegarder en base de données

Il est ensuite possible de sauvegarder cette image dans une base de données SQLite, dans une colonne de type BLOB.

        // get Bitmap from SignatureView
    	Bitmap b = mSignatureView.save();
    	// calculate the number of pixels
    	int pixels = b.getHeight() * b.getRowBytes();
    	// instanciate an OutputStream to handle the pixels
    	ByteArrayOutputStream baos = new ByteArrayOutputStream(pixels);
    	// we record it as a PNG (lossless format)
    	b.compress(CompressFormat.PNG, 0, baos);
    	// we get the bytes array
    	byte[] bytes = baos.toByteArray();
    	// ContentValues is a wrapper class used to insert in db
    	ContentValues cv = new ContentValues(1);
    	cv.put(Signature.SIGNATURE, bytes);
    	// here we insert in database, and we get the uri of the image
    	Uri uri = mContext.getContentResolver().insert(Signature.CONTENT_URI, cv);

Restaurer l’image

Enfin, voyons comment afficher l’image après l’avoir récupérée depuis la base de données

        // request with the image uri, so no selection clause needed
        Cursor result = mContext.getContentResolver().query(
					uri,
					Signature.PROJECTION,
					null,
					null,
					Signature.DEFAULT_SORT_ORDER);
        // we check we got something
	if(result != null && result.moveToFirst())
	{
                // we retrieve the raw picture
		byte[] rawPict = result.getBlob(Signature.SIGNATURE_INDEX);
                // then get a Bitmap from it
		Bitmap b = BitmapFactory.decodeByteArray(rawPict, 0, rawPict.length);
                // just set the Bitmap in an ImageView
		mImageView.setImageBitmap(b);
	}

Code Source

Les sources de l’application sont disponibles sur le Github de Zenika

Laisser un commentaire

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.

%d blogueurs aiment cette page :