Suivi des doigts tactiles multiples

Cette rubrique montre comment suivre les événements tactiles à partir de plusieurs doigts

Il existe des moments où une application tactile doit suivre les doigts individuels lorsqu’ils se déplacent simultanément sur l’écran. Une application classique est un programme de peinture à doigts. Vous souhaitez que l’utilisateur puisse dessiner avec un seul doigt, mais également dessiner avec plusieurs doigts à la fois. Lorsque votre programme traite plusieurs événements tactiles, il doit distinguer les événements correspondant à chaque doigt. Android fournit un code d’ID à cet effet, mais l’obtention et la gestion de ce code peuvent être un peu difficiles.

Pour tous les événements associés à un doigt particulier, le code d’ID reste le même. Le code d’ID est attribué lorsqu’un doigt touche d’abord l’écran et devient non valide une fois le doigt levé à partir de l’écran. Ces codes d’ID sont généralement des entiers très petits et Android les réutilise pour les événements tactiles ultérieurs.

Presque toujours, un programme qui suit les doigts individuels conserve un dictionnaire pour le suivi tactile. La clé de dictionnaire est le code d’ID qui identifie un doigt particulier. La valeur du dictionnaire dépend de l’application. Dans l’exemple FingerPaint, chaque trait de doigt (du toucher à la libération) est associé à un objet qui contient toutes les informations nécessaires pour afficher la ligne dessinée avec ce doigt. Le programme définit une petite FingerPaintPolyline classe à cet effet :

class FingerPaintPolyline
{
    public FingerPaintPolyline()
    {
        Path = new Path();
    }

    public Color Color { set; get; }

    public float StrokeWidth { set; get; }

    public Path Path { private set; get; }
}

Chaque polyligne a une couleur, une largeur de trait et un objet graphique Path Android pour accumuler et afficher plusieurs points de la ligne au fur et à mesure qu’il est dessiné.

Le reste du code indiqué ci-dessous est contenu dans un View dérivé nommé FingerPaintCanvasView. Cette classe gère un dictionnaire d’objets de type FingerPaintPolyline pendant le temps qu’ils sont activement dessinés par un ou plusieurs doigts :

Dictionary<int, FingerPaintPolyline> inProgressPolylines = new Dictionary<int, FingerPaintPolyline>();

Ce dictionnaire permet à la vue d’obtenir rapidement les FingerPaintPolyline informations associées à un doigt particulier.

La FingerPaintCanvasView classe conserve également un List objet pour les polylignes qui ont été terminées :

List<FingerPaintPolyline> completedPolylines = new List<FingerPaintPolyline>();

Les objets dans ce List cas sont dans le même ordre qu’ils ont été dessinés.

FingerPaintCanvasView remplace deux méthodes définies par View: Voir OnDraw et OnTouchEvent. Dans son OnDraw remplacement, la vue dessine les polylignes terminées, puis dessine les polylignes en cours.

La substitution de la OnTouchEvent méthode commence par obtenir une pointerIndex valeur de la ActionIndex propriété. Cette ActionIndex valeur différencie plusieurs doigts, mais elle n’est pas cohérente entre plusieurs événements. Pour cette raison, vous utilisez la pointerIndex méthode pour obtenir la valeur du GetPointerId pointeurid. Cet ID est cohérent entre plusieurs événements :

public override bool OnTouchEvent(MotionEvent args)
{
    // Get the pointer index
    int pointerIndex = args.ActionIndex;

    // Get the id to identify a finger over the course of its progress
    int id = args.GetPointerId(pointerIndex);

    // Use ActionMasked here rather than Action to reduce the number of possibilities
    switch (args.ActionMasked)
    {
        // ...
    }

    // Invalidate to update the view
    Invalidate();

    // Request continued touch input
    return true;
}

Notez que le remplacement utilise la ActionMasked propriété dans l’instruction switch plutôt que la Action propriété. Voici pourquoi :

Lorsque vous traitez de plusieurs touches tactiles, la Action propriété a la valeur du MotionEventsAction.Down premier doigt pour toucher l’écran, puis les valeurs des Pointer2DownPointer3Down deuxième et troisième doigts touchent également l’écran. Comme les quatrième et cinquième doigts font contact, la Action propriété a des valeurs numériques qui ne correspondent même pas aux membres de l’énumération MotionEventsAction  ! Vous devrez examiner les valeurs des indicateurs de bits dans les valeurs pour interpréter ce qu’elles signifient.

De même, lorsque les doigts quittent le contact avec l’écran, la Action propriété a des valeurs et Pointer2UpPointer3Up pour les deuxième et troisième doigts, et Up pour le premier doigt.

La ActionMasked propriété prend moins de valeurs, car elle est destinée à être utilisée conjointement avec la ActionIndex propriété pour faire la distinction entre plusieurs doigts. Lorsque les doigts touchent l’écran, la propriété ne peut être égale MotionEventActions.Down que pour le premier doigt et PointerDown pour les doigts suivants. À mesure que les doigts quittent l’écran, ActionMasked il y a des valeurs pour Pointer1Up les doigts suivants et Up pour le premier doigt.

Lorsque vous utilisez ActionMasked, les ActionIndex distinctions entre les doigts suivants pour toucher et quitter l’écran, mais vous n’avez généralement pas besoin d’utiliser cette valeur, sauf en tant qu’argument pour d’autres méthodes dans l’objet MotionEvent . Pour les interactions tactiles multiples, l’une de ces méthodes les plus importantes est GetPointerId appelée dans le code ci-dessus. Cette méthode retourne une valeur que vous pouvez utiliser pour une clé de dictionnaire pour associer des événements particuliers aux doigts.

Le OnTouchEvent remplacement dans l’exemple traite les MotionEventActions.Down événements de PointerDown manière identique en créant un objet FingerPaintPolyline et en l’ajoutant au dictionnaire :

public override bool OnTouchEvent(MotionEvent args)
{
    // Get the pointer index
    int pointerIndex = args.ActionIndex;

    // Get the id to identify a finger over the course of its progress
    int id = args.GetPointerId(pointerIndex);

    // Use ActionMasked here rather than Action to reduce the number of possibilities
    switch (args.ActionMasked)
    {
        case MotionEventActions.Down:
        case MotionEventActions.PointerDown:

            // Create a Polyline, set the initial point, and store it
            FingerPaintPolyline polyline = new FingerPaintPolyline
            {
                Color = StrokeColor,
                StrokeWidth = StrokeWidth
            };

            polyline.Path.MoveTo(args.GetX(pointerIndex),
                                 args.GetY(pointerIndex));

            inProgressPolylines.Add(id, polyline);
            break;
        // ...
    }
    // ...        
}

Notez que l’utilisateur pointerIndex est également utilisé pour obtenir la position du doigt dans la vue. Toutes les informations tactiles sont associées à la pointerIndex valeur. L’identificateur id unique identifie les doigts entre plusieurs messages, de sorte qu’il est utilisé pour créer l’entrée de dictionnaire.

De même, le OnTouchEvent remplacement gère également le MotionEventActions.Up polyline terminé et Pointer1Up de la même façon en transférant le polyline terminé vers la completedPolylines collection afin qu’ils puissent être dessinés pendant le OnDraw remplacement. Le code supprime également l’entrée id du dictionnaire :

public override bool OnTouchEvent(MotionEvent args)
{
    // ...
    switch (args.ActionMasked)
    {
        // ...
        case MotionEventActions.Up:
        case MotionEventActions.Pointer1Up:

            inProgressPolylines[id].Path.LineTo(args.GetX(pointerIndex),
                                                args.GetY(pointerIndex));

            // Transfer the in-progress polyline to a completed polyline
            completedPolylines.Add(inProgressPolylines[id]);
            inProgressPolylines.Remove(id);
            break;

        case MotionEventActions.Cancel:
            inProgressPolylines.Remove(id);
            break;
    }
    // ...        
}

Maintenant, pour la partie délicate.

Entre les événements bas et haut, il existe généralement de nombreux MotionEventActions.Move événements. Ceux-ci sont regroupés dans un seul appel à OnTouchEvent, et ils doivent être gérés différemment des événements et Up des Down événements. La pointerIndex valeur obtenue précédemment à partir de la ActionIndex propriété doit être ignorée. Au lieu de cela, la méthode doit obtenir plusieurs pointerIndex valeurs en boucle entre 0 et la PointerCount propriété, puis obtenir une id pour chacune de ces pointerIndex valeurs :

public override bool OnTouchEvent(MotionEvent args)
{
    // ...
    switch (args.ActionMasked)
    {
        // ...
        case MotionEventActions.Move:

            // Multiple Move events are bundled, so handle them differently
            for (pointerIndex = 0; pointerIndex < args.PointerCount; pointerIndex++)
            {
                id = args.GetPointerId(pointerIndex);

                inProgressPolylines[id].Path.LineTo(args.GetX(pointerIndex),
                                                    args.GetY(pointerIndex));
            }
            break;
        // ...
    }
    // ...        
}

Ce type de traitement permet à l’exemple de suivre les doigts individuels et de dessiner les résultats à l’écran :

Exemple de capture d’écran de l’exemple FingerPaint

Vous avez maintenant vu comment vous pouvez suivre des doigts individuels sur l’écran et faire la distinction entre eux.