Сенсорный ввод - манипуляции и инерция

58

Манипуляции

Простые касания замечательно подходят для приложений, которые используют события касания самым непосредственным образом, как в примере с перетаскиванием овалов или в программе рисования. Но если вам понадобится поддерживать стандартные сенсорные жесты, простые касания не облегчат эту задачу. Например, для поддержки поворотов необходимо распознать две точки касания на одном элементе, отследить их движения и как-то определить, что одна из них вращается вокруг другой. А кроме этого, еще нужен код, который непосредственно отрабатывает соответствующий эффект вращения.

К счастью, WPF не оставляет вас один на один с этой задачей. В ней имеется высокоуровневая поддержка для жестов, которая называется манипуляцией (manipulation) касания. Достаточно настроить элемент, чтобы он поддерживал манипуляции, установив его свойство IsManipulationEnabled равным true. После этого элемент будет реагировать на четыре события манипуляции: ManipulationStarting, ManipulationStarted, ManipulationDelta и ManipulationCompleted.

Ниже приведен пример манипуляции: три изображения первоначально упорядочены на холсте, а затем пользователь с помощью жестов прокрутки, поворота и изменения масштаба может перемещать, вращать, сжимать или увеличивать их. Для создания этого примера вначале нужно определить холст (Canvas) и поместить на него три элемента изображения (Image).

Для упрощения события ManipulationStarting и ManipulationDelta будут обрабатываться в Canvas, после того как они всплывут из соответствующего элемента изображения.

<Canvas Name="cnv">
        <Image Source="img1.jpg" Width="300" IsManipulationEnabled="True"
               Canvas.Top="10" Canvas.Left="42" ManipulationDelta="Image_ManipulationDelta"
               ManipulationStarting="Image_ManipulationStarting">
            <Image.RenderTransform>
                <MatrixTransform></MatrixTransform>
            </Image.RenderTransform>
        </Image>
        <Image Source="img2.jpg" Width="292" Canvas.ZIndex="1" IsManipulationEnabled="True"
               Canvas.Top="200" Canvas.Left="230" ManipulationDelta="Image_ManipulationDelta"
               ManipulationStarting="Image_ManipulationStarting">
            <Image.RenderTransform>
                <MatrixTransform></MatrixTransform>
            </Image.RenderTransform>
        </Image>
        <Image Source="img3.jpg" Width="300" IsManipulationEnabled="True"
               Canvas.Top="18" Canvas.Left="419" ManipulationDelta="Image_ManipulationDelta" 
               ManipulationStarting="Image_ManipulationStarting">
            <Image.RenderTransform>
                <MatrixTransform></MatrixTransform>
            </Image.RenderTransform>
        </Image>
</Canvas>

Обратите внимание на один момент в этой разметке. Каждый элемент изображения содержит объект MatrixTransform, который облегчает применение сочетаний манипуляций: перемещения, вращения и масштабирования. В данный момент объекты MatrixTransform не делают ничего, но они будут изменены в коде, когда возникнут события манипуляции.

Когда пользователь прикоснется к одному из изображений, возникнет событие ManipulationStarting. В это время нужно обеспечить контейнер манипуляции, который будет использоваться как точка отсчета для всех получаемых позднее координат манипуляций. В данном случае естественно выбрать в качестве контейнера холст, который содержит изображения. Кроме того, можно указать типы манипуляций, которые разрешены с изображениями. Если этого не сделать, WPF будет отслеживать все распознаваемые жесты: прокрутка, масштабирование и поворот.

private void Image_ManipulationDelta(object sender, ManipulationDeltaEventArgs e)
        {
            UIElement element = (UIElement)e.Source;

            // Манипуляция с внешним видом элемента и его позицией
            Matrix matrix = ((MatrixTransform)element.RenderTransform).Matrix;
            ManipulationDelta md = e.DeltaManipulation;
        }

        private void Image_ManipulationStarting(object sender, ManipulationStartingEventArgs e)
        {
            // Указываем базовый контейнер
            e.ManipulationContainer = cnv;

            // Указание разрешенных манипуляций
            e.Mode = ManipulationModes.All;
        }

Событие ManipulationDelta возникает тогда, когда выполняется (но не обязательно закончена) манипуляция. Например, если пользователь начал поворачивать изображение, событие ManipulationDelta будет постоянно возникать, пока поворот не будет завершен, т.е. пользователь поднимет пальцы.

Текущее состояние жеста отображается в объекте ManipulationDelta, который доступен через свойство ManipulationDeltaEventArgs.DeltaManipulation. В этом объекте записываются количества масштабирования, вращения и прокрутки, которое нужно применить к объекту; эти количества доступны соответственно через свойства Scale, Rotation и Translation. Остается использовать приведенную информацию для настройки элемента в пользовательском интерфейсе.

Теоретически данные о масштабе и вращении можно отрабатывать, изменяя размер и положение элемента. Но поворот таким образом не выполнить, да и код получается запутанным. Гораздо лучше использовать преобразования (transform) — объекты, которые позволяют математически изменить внешний вид любого элемента WPF. Для этого нужно взять информацию, которая содержится в объекте ManipulationDelta, и использовать ее для конфигурирования объекта MatrixTransform. Звучит сложновато, но необходимый для этого код по сути один и тот же для любого приложения, которое использует данную возможность.

Обычное приложение
Использование сложных манипуляций

Инерция

В WPF имеется еще один уровень средств, основанный на поддержке базовых манипуляций — инерция (inertia). По сути, инерция позволяет выполнять более реалистичные, плавные манипуляции с элементами.

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

Чтобы добавить в предыдущий пример инерцию, необходимо просто обрабатывать событие ManipulationInertiaStarting. Как и любое другое событие манипуляции, оно начинается в одном из изображений и всплывает до объекта Canvas. Событие ManipulationInertiaStarting возникает, когда пользователь заканчивает жест и "отпускает" элемент, т.е. поднимает пальцы. В этот момент с помощью объекта ManipulationInertiaStartingEventArgs можно определить текущую скорость — скорость, с которой элемент перемещался перед самым окончанием манипуляции — и задать необходимую скорость замедления.

Чтобы элементы естественно отскакивали от преград, нужно проверять в событии ManipulationDelta, не попали ли они в недопустимое место. При обнаружении пересечения границы вы должны сообщить об этом, вызвав метод ManipulationDeltaEventArgs.ReportBoundaryFeedback().

Пройди тесты
Лучший чат для C# программистов