Rilevamento con più dita a tocco

Questo argomento illustra come tenere traccia degli eventi di tocco da più dita

Ci sono momenti in cui un'applicazione multitocco deve tenere traccia delle singole dita mentre si spostano contemporaneamente sullo schermo. Un'applicazione tipica è un programma di pittura di dito. Si vuole che l'utente sia in grado di disegnare con un solo dito, ma anche di disegnare con più dita contemporaneamente. Poiché il programma elabora più eventi di tocco, deve distinguere quali eventi corrispondono a ogni dito. Android fornisce un codice ID a questo scopo, ma ottenere e gestire tale codice può essere un po' complicato.

Per tutti gli eventi associati a un dito specifico, il codice ID rimane invariato. Il codice ID viene assegnato quando un dito tocca per la prima volta lo schermo e diventa non valido dopo che il dito si solleva dallo schermo. Questi codici ID sono in genere numeri interi molto piccoli e Android li riutilizza per gli eventi di tocco successivi.

Quasi sempre, un programma che tiene traccia delle singole dita mantiene un dizionario per il rilevamento del tocco. La chiave del dizionario è il codice ID che identifica un dito specifico. Il valore del dizionario dipende dall'applicazione. Nell'esempio FingerPaint ogni tratto del dito (dal tocco al rilascio) è associato a un oggetto che contiene tutte le informazioni necessarie per eseguire il rendering della linea disegnata con il dito. Il programma definisce una piccola FingerPaintPolyline classe a questo scopo:

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

    public Color Color { set; get; }

    public float StrokeWidth { set; get; }

    public Path Path { private set; get; }
}

Ogni polilinea ha un colore, una larghezza del tratto e un oggetto grafico Path Android per accumulare ed eseguire il rendering di più punti della linea mentre viene disegnato.

Il resto del codice illustrato di seguito è contenuto in un View derivato denominato FingerPaintCanvasView. Tale classe mantiene un dizionario di oggetti di tipo FingerPaintPolyline durante il tempo in cui vengono disegnati attivamente da una o più dita:

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

Questo dizionario consente alla visualizzazione di ottenere rapidamente le FingerPaintPolyline informazioni associate a un dito specifico.

La FingerPaintCanvasView classe gestisce anche un List oggetto per le polilinee completate:

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

Gli oggetti in questo List oggetto si trovano nello stesso ordine in cui sono stati disegnati.

FingerPaintCanvasView esegue l'override di due metodi definiti da View: OnDraw e OnTouchEvent. OnDraw Nell'override, la vista disegna le polilinee completate e quindi disegna le polilinee in corso.

L'override del OnTouchEvent metodo inizia ottenendo un pointerIndex valore dalla ActionIndex proprietà . Questo ActionIndex valore distingue tra più dita, ma non è coerente tra più eventi. Per questo motivo, si usa per pointerIndex ottenere il valore del puntatore id dal GetPointerId metodo . Questo ID è coerente tra più eventi:

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

Si noti che l'override usa la ActionMasked proprietà nell'istruzione switch anziché la Action proprietà . Ecco perché:

Quando si ha a che fare con il multitocco, la Action proprietà ha un valore per MotionEventsAction.Down il primo dito per toccare lo schermo, quindi i valori e Pointer2DownPointer3Down come secondo e terzo dita toccano anche lo schermo. Come il quarto e il quinto dita fanno contatto, la Action proprietà ha valori numerici che non corrispondono nemmeno ai membri dell'enumerazione MotionEventsAction . È necessario esaminare i valori dei flag di bit nei valori per interpretare il significato.

Analogamente, quando le dita lasciano il contatto con lo schermo, la Action proprietà ha valori di Pointer2Up e Pointer3Up per il secondo e il terzo dita e Up per il primo dito.

La ActionMasked proprietà accetta un numero minore di valori perché deve essere usata insieme alla ActionIndex proprietà per distinguere tra più dita. Quando le dita toccano lo schermo, la proprietà può essere uguale MotionEventActions.Down solo per il primo dito e PointerDown per le dita successive. Quando le dita lasciano lo schermo, ActionMasked ha valori per Pointer1Up le dita successive e Up per il primo dito.

Quando si usa ActionMasked, la ActionIndex distinzione tra le dita successive da toccare e lasciare lo schermo, ma in genere non è necessario usare tale valore tranne come argomento per altri metodi nell'oggetto MotionEvent . Per il multitocco, uno dei metodi più importanti è GetPointerId chiamato nel codice precedente. Questo metodo restituisce un valore che è possibile utilizzare per una chiave del dizionario per associare eventi specifici alle dita.

L'override OnTouchEvent nell'esempio elabora gli MotionEventActions.Down eventi e PointerDown in modo identico creando un nuovo FingerPaintPolyline oggetto e aggiungendolo al dizionario:

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;
        // ...
    }
    // ...        
}

Si noti che pointerIndex viene usato anche per ottenere la posizione del dito all'interno della visualizzazione. Tutte le informazioni sul tocco sono associate al pointerIndex valore . Identifica id in modo univoco le dita su più messaggi, in modo che venga usata per creare la voce del dizionario.

Analogamente, l'override OnTouchEvent gestisce anche e Pointer1UpMotionEventActions.Up in modo identico trasferendo la polilinea completata alla completedPolylines raccolta in modo che possano essere disegnati durante l'overrideOnDraw. Il codice rimuove anche la id voce dal dizionario:

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;
    }
    // ...        
}

Ora per la parte difficile.

Tra gli eventi down e up, in genere sono presenti molti MotionEventActions.Move eventi. Questi vengono raggruppati in una singola chiamata a OnTouchEvente devono essere gestiti in modo diverso dagli Down eventi e Up . Il pointerIndex valore ottenuto in precedenza dalla ActionIndex proprietà deve essere ignorato. Al contrario, il metodo deve ottenere più pointerIndex valori eseguendo un ciclo tra 0 e la PointerCount proprietà e quindi ottenere un per id ognuno di questi pointerIndex valori:

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;
        // ...
    }
    // ...        
}

Questo tipo di elaborazione consente al campione di tenere traccia delle singole dita e di disegnare i risultati sullo schermo:

Screenshot di esempio di FingerPaint

Ora hai visto come tenere traccia delle singole dita sullo schermo e distinguerle.