Partilhar via


Rastreamento de dedos multitoque

Este tópico demonstra como controlar eventos de toque de vários dedos

Há momentos em que um aplicativo multitoque precisa rastrear dedos individuais enquanto eles se movem simultaneamente na tela. Uma aplicação típica é um programa de pintura a dedo. Você quer que o usuário seja capaz de desenhar com um único dedo, mas também desenhar com vários dedos ao mesmo tempo. Como seu programa processa vários eventos de toque, ele precisa distinguir quais eventos correspondem a cada dedo. O Android fornece um código de identificação para essa finalidade, mas obter e manipular esse código pode ser um pouco complicado.

Para todos os eventos associados a um dedo específico, o código de ID permanece o mesmo. O código de identificação é atribuído quando um dedo toca pela primeira vez na tela e se torna inválido depois que o dedo é levantado da tela. Esses códigos de identificação geralmente são inteiros muito pequenos, e o Android os reutiliza para eventos de toque posteriores.

Quase sempre, um programa que rastreia dedos individuais mantém um dicionário para rastreamento por toque. A chave do dicionário é o código de identificação que identifica um dedo específico. O valor do dicionário depende do aplicativo. No exemplo FingerPaint, cada traçado do dedo (do toque à liberação) é associado a um objeto que contém todas as informações necessárias para renderizar a linha desenhada com esse dedo. O programa define uma pequena FingerPaintPolyline classe para este fim:

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

    public Color Color { set; get; }

    public float StrokeWidth { set; get; }

    public Path Path { private set; get; }
}

Cada polilinha tem uma cor, uma largura de traçado e um objeto gráfico Path Android para acumular e renderizar vários pontos da linha à medida que ela está sendo desenhada.

O restante do código mostrado abaixo está contido em uma View derivada chamada FingerPaintCanvasView. Essa classe mantém um dicionário de objetos do tipo FingerPaintPolyline durante o tempo em que eles estão sendo ativamente desenhados por um ou mais dedos:

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

Este dicionário permite que a visualização obtenha rapidamente as FingerPaintPolyline informações associadas a um dedo específico.

A FingerPaintCanvasView classe também mantém um List objeto para as polilinhas que foram concluídas:

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

Os objetos estão List na mesma ordem em que foram desenhados.

FingerPaintCanvasView substitui dois métodos definidos por View: OnDraw e OnTouchEvent. Em sua OnDraw substituição, a exibição desenha as polilinhas concluídas e, em seguida, desenha as polilinhas em andamento.

A substituição do OnTouchEvent método começa obtendo um pointerIndex valor da ActionIndex propriedade. Esse ActionIndex valor diferencia entre vários dedos, mas não é consistente em vários eventos. Por esse motivo, use o pointerIndex para obter o valor de ponteiro id do GetPointerId método. Essa ID é consistente em vários eventos:

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

Observe que a substituição usa a ActionMasked propriedade na instrução em switch vez da Action propriedade. Eis o motivo:

Quando você está lidando com multitoque, a Action propriedade tem um valor de MotionEventsAction.Down para o primeiro dedo tocar na tela e, em seguida, valores de Pointer2Down e Pointer3Down como o segundo e terceiro dedos também tocam na tela. Como o quarto e o quinto dedos fazem contato, a Action propriedade tem valores numéricos que nem sequer correspondem aos membros da MotionEventsAction enumeração! Você precisaria examinar os valores dos sinalizadores de bits nos valores para interpretar o que eles significam.

Da mesma forma, como os dedos deixam contato com a tela, a Action propriedade tem valores de Pointer2Up e Pointer3Up para o segundo e terceiro dedos, e Up para o primeiro dedo.

A ActionMasked propriedade assume um número menor de valores porque se destina a ser usada em conjunto com a ActionIndex propriedade para diferenciar entre vários dedos. Quando os dedos tocam na tela, a propriedade só pode ser igual MotionEventActions.Down para o primeiro dedo e PointerDown para os dedos subsequentes. Como os dedos saem da tela, ActionMasked tem valores de Pointer1Up para os dedos subsequentes e Up para o primeiro dedo.

Ao usar ActionMaskedo ActionIndex , o distingue entre os dedos subsequentes para tocar e sair da tela, mas você geralmente não precisa usar esse valor, exceto como um argumento para outros métodos no MotionEvent objeto. Para multitoque, um dos métodos mais importantes é GetPointerId chamado no código acima. Esse método retorna um valor que você pode usar para uma chave de dicionário para associar eventos específicos aos dedos.

A OnTouchEvent substituição no exemplo processa os MotionEventActions.Down eventos e PointerDown de forma idêntica, criando um novo FingerPaintPolyline objeto e adicionando-o ao dicionário:

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

Observe que o pointerIndex também é usado para obter a posição do dedo dentro da vista. Todas as informações de toque estão associadas ao pointerIndex valor. O id identifica exclusivamente os dedos em várias mensagens, de modo que é usado para criar a entrada do dicionário.

Da mesma forma, a OnTouchEvent substituição também manipula o MotionEventActions.Up e Pointer1Up de forma idêntica, transferindo a polilinha concluída para a completedPolylines coleção para que eles possam ser desenhados durante a OnDraw substituição. O código também remove a id entrada do dicionário:

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

Agora para a parte complicada.

Entre os eventos para baixo e para cima, geralmente há muitos MotionEventActions.Move eventos. Eles são agrupados em uma única chamada para OnTouchEvento , e devem ser tratados de forma diferente dos Down eventos e Up . O pointerIndex valor obtido anteriormente da ActionIndex propriedade deve ser ignorado. Em vez disso, o método deve obter vários pointerIndex valores fazendo um loop entre 0 e a PointerCount propriedade e, em seguida, obter um para cada um id desses pointerIndex valores:

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

Esse tipo de processamento permite que a amostra rastreie dedos individuais e desenhe os resultados na tela:

Exemplo de captura de tela do exemplo do FingerPaint

Agora você viu como você pode rastrear dedos individuais na tela e distinguir entre eles.