Отслеживание нескольких сенсорных пальцев

В этом разделе показано, как отслеживать события касания с нескольких пальцев

Иногда требуется отслеживать отдельные пальцы при одновременном перемещении на экране. Одно из типичных приложений — это программа с пальцем. Вы хотите, чтобы пользователь мог рисовать с одним пальцем, а также рисовать с несколькими пальцами одновременно. При обработке нескольких сенсорных событий программа должна различать, какие события соответствуют каждому пальцу. Android предоставляет код идентификатора для этой цели, но получение и обработка этого кода может быть немного сложной.

Для всех событий, связанных с определенным пальцем, код идентификатора остается неизменным. Код идентификатора назначается, когда пальцем сначала прикасается к экрану, и становится недействительным после того, как палец поднимается с экрана. Эти коды идентификаторов обычно являются очень небольшими целыми числами, и Android повторно использует их для последующих событий касания.

Почти всегда программа, которая отслеживает отдельные пальцы, поддерживает словарь для отслеживания сенсорного ввода. Ключ словаря — это код идентификатора, определяющий определенный палец. Значение словаря зависит от приложения. В примере FingerPaint каждый штрих пальца (от касания к выпуску) связан с объектом, который содержит все сведения, необходимые для отрисовки линии, нарисованной с помощью этого пальца. Программа определяет небольшой FingerPaintPolyline класс для этой цели:

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

    public Color Color { set; get; }

    public float StrokeWidth { set; get; }

    public Path Path { private set; get; }
}

Каждая полилайн имеет цвет, ширину штриха и графический Path объект Android для накапливания и отрисовки нескольких точек линии по мере рисования.

Оставшаяся часть кода, показанная ниже, содержится в производном именованном ViewFingerPaintCanvasViewкоде. Этот класс поддерживает словарь объектов типа FingerPaintPolyline во время их активного рисования одним или несколькими пальцами:

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

Этот словарь позволяет представлению быстро получать FingerPaintPolyline сведения, связанные с определенным пальцем.

Класс FingerPaintCanvasView также поддерживает List объект для завершенных полилайнов:

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

Объекты в этом List порядке находятся в том же порядке, что и они были нарисованы.

FingerPaintCanvasView переопределяет два метода, определенных в следующих Viewметодах: OnDraw и OnTouchEvent. В его OnDraw переопределении представление рисует завершенные полилайны, а затем рисует выполняемые полилайны.

Переопределение OnTouchEvent метода начинается с получения pointerIndex значения из ActionIndex свойства. Это ActionIndex значение отличается от нескольких пальцев, но оно не согласовано в нескольких событиях. По этой причине используется pointerIndex для получения значения указателя id из GetPointerId метода. Этот идентификатор согласован в нескольких событиях:

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

Обратите внимание, что переопределение использует ActionMasked свойство в switch инструкции, а не Action свойство. Для этого есть следующие причины.

При работе с несколькими сенсорными Action экранами свойство имеет значение MotionEventsAction.Down для первого пальца, чтобы коснуться экрана, а затем значения Pointer2Down и Pointer3Down как второй и третий пальцы также касаются экрана. Как четвертый и пятый пальцы делают контакт, свойство имеет числовые значения, Action которые даже не соответствуют членам MotionEventsAction перечисления! Необходимо проверить значения битовых флагов в значениях, чтобы интерпретировать то, что они означают.

Аналогичным образом, как пальцы покидают контакт с экраном, Action свойство имеет значения Pointer2Up и Pointer3Up для второго и третьего пальцев, а Up также для первого пальца.

Свойство ActionMasked занимает меньшее количество значений, так как оно предназначено для использования в сочетании со ActionIndex свойством для различения нескольких пальцев. Когда пальцы касаются экрана, свойство может быть равно MotionEventActions.Down только первому пальцу и PointerDown последующим пальцам. По мере того как пальцы покидают экран, ActionMasked имеют значения Pointer1Up для последующих пальцев и Up для первого пальца.

При использовании ActionMasked, ActionIndex различает следующие пальцы для касания и выхода из экрана, но обычно не нужно использовать это значение, за исключением аргумента других методов в объекте MotionEvent . Для нескольких касаний один из наиболее важных из этих методов вызывается GetPointerId в приведенном выше коде. Этот метод возвращает значение, которое можно использовать для ключа словаря для связывания определенных событий с пальцами.

Переопределение OnTouchEvent в примере обрабатывает MotionEventActions.Down и PointerDown события идентично путем создания нового FingerPaintPolyline объекта и добавления его в словарь:

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

Обратите внимание, что он pointerIndex также используется для получения положения пальца в представлении. Все сенсорные данные связаны со значением pointerIndex . Уникально id идентифицирует пальцы между несколькими сообщениями, поэтому используется для создания записи словаря.

OnTouchEvent Аналогичным образом переопределение также обрабатывает MotionEventActions.Up и Pointer1Up идентично путем передачи завершенной полилайн в completedPolylines коллекцию, чтобы они могли быть нарисованы во время OnDraw переопределения. Код также удаляет id запись из словаря:

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

Теперь для сложной части.

Между событиями вниз и вверх, как правило, есть много MotionEventActions.Move событий. Они упаковываются в один вызов OnTouchEvent, и они должны обрабатываться по-разному от Down событий и Up событий. Значение, полученное pointerIndexActionIndex ранее из свойства, должно игнорироваться. Вместо этого метод должен получить несколько pointerIndex значений, циклив между 0 и PointerCount свойством, а затем получить id для каждого из этих pointerIndex значений:

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

Этот тип обработки позволяет образцу отслеживать отдельные пальцы и выводить результаты на экране:

Пример снимка экрана из примера FingerPaint

Теперь вы узнали, как можно отслеживать отдельные пальцы на экране и различать их.