Опубликован: 10.04.2013 | Уровень: для всех | Доступ: платный
Лекция 4:

Многоточечные жесты

< Лекция 3 || Лекция 4: 12345 || Лекция 5 >
Аннотация: Все, о чем мы говорили до сих пор, касалось жестов с одной точкой касания, но то же самое справедливо и для многоточечных жестов. Также в лекции рассмотрены ввод данных с клавиатуры и рукописный ввод.

Когда объект MSGesture получает несколько указателей через свое событиет addPointer, он так же запускает события MSGestureStart, MSGestureChange, MSGestureEnd для жестов поворота и масштабирования, вместе с событием MSInertiaStart. В подобных случаях свойства scale, rotation, velocityAngular, expansion, и velocityExpansion в объекте eventArgs приобретают смысл.

Вы можете выборочно просматривать эти свойства для событий MSGestureChange посредством выпадающего списка, который расположен в правом верхнем углу, в упражнении PointerEvents. Вы можете заметить, что, когда вы выполняете многоточечные жесты в имитаторе Visual Studio, вы никогда не видите событие MSGestureTap для отдельных точек касания. Это происходит потому, что распознаватель жестов может видеть подобные множественные события MSPointerDown, которые происходят почти одновременно (здесь играет роль свойство hwTimestamp) и тут же объединяет их в одно событие MSGestureStart (например, начинает жест масштабирования или вращения).

Теперь я уверен, что вы готовы задать несколько важных вопросов. До сих пор я говорил о сенсорных жестах изменения масштаба и вращения, о жестах переноса, как о рахных вещах, но как, на самом деле, различать их, если все они поступют в приложение посредством одного и того же события MSGestureChange? Разве это не запутывает все? Какова стратегия для работы с жестами переноса, вращения и масштабирования?

Хорошо, ответ заключается в том, что вам не нужно их разделять! Что, если вы подумаете немного о том, как вы обрабатываете события MSGestureChange и данные, которые каждое из них содержит в зависимости от вида манипуляции, который вы поддерживаете в пользовательском интерфейсе:

  • Если вы поддерживаете лишь перенос элемента (translation), вы просто никогда не обращаете внимание на свойства наподобие scale и rotation и применяете только необходимые, такие, как translationX и translationY. Это было бы ожидаемым поведением при выборе элемента в элементе управления коллекцией, например (или в элементе управления, который позволяет манипулировать элементами по технологии "перетащить и опустить", меняя порядок их расположения).
  • Если вы поддерживаете лишь изменение масштаба (scale), вы игнорируете все свойства, которые имеют отношение к позиционированию и работаете со свойствами scale, expansion, и/или со свойством velocityExpansion. Это будет тем поведением, которое вы хотите реализовать для элемента управления, который поддерживает оптическое или семантическое масштабирование.
  • Если вас интересует лишь вращение (rotation), то ваши друзья – это свойства rotation и velocityAngular.

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

Ввод данных и жесты, допускающие создание экземпляров

В то время, как упражнение PointerEvents, включенное в эту лекцию, давало нам низкоуровневый вид событий указателей и жестов, для приложений по-настоящему важно то, как применять эти события для реальной манипуляции экранными объектами, что является реализацией частей языка касаний, такими, как жесты сжатия/растяжения и вращения. Для того, чтобы увидеть это, мы можем обратиться к примеру "Ввод: жесты, допускающие создание экземпляров" (http://code.msdn.microsoft.com/windowsapps/Input-Instantiable-deda69ca).

Этот пример, в основном, показывает, как использовать события жестов одновременно с несколькими элементами. В Сценарии 1 и 2, приложение реализует простой пример головоломки, как упоминалось выше. Каждым закрашенным прямоугольником можно управлять по отдельности, используя перетаскивание для перемещения (с инерцией и без), жест сжатия-растяжения для изменения масштаба, и жест вращения для вращения, как показано на рис. 4.1.

Пример "Ввод: жесты, допускающие создание экземпляров" после того, как с ним немного поработали. Словосочетание "допускающие создание экземпляров" (instantiable) использовано потому, что нам нужно создать экземпляр (instance) объекта MSGesture для приема событий жестов

Рис. 4.1. Пример "Ввод: жесты, допускающие создание экземпляров" после того, как с ним немного поработали. Словосочетание "допускающие создание экземпляров" (instantiable) использовано потому, что нам нужно создать экземпляр (instance) объекта MSGesture для приема событий жестов

В Сценарии 1 (js/instantiableGesture.js) объект MSGesture создается для кажого экранного элемента вместе с еще одним для черного фона, "стола" при инициализации (в функции initialize). Мы уже видели подобное. Похожим бразом, обработчик MSPointerDown (onPointerDown) добавляет указатели к объектам жестов для каждого элемента, уделяя немного больше внимания обработке их положения по оси Z. Это предовращает одновременные касания, указатели мыши и пера работают с тем же самым элементом (что было бы странно!):

function onPointerDown(e) {
if (e.target.gesture.pointerType === null) {  
 // Первый контакт e.target.gesture.addPointer(e.pointerId); 
 // Присоединение указателя к элементу
e.target.gesture.pointerType = e.pointerType;
}
else if (e.target.gesture.pointerType === e.pointerType) 
{ // Контакты сходного типа e.target.gesture.addPointer(e.pointerId);         
   // Присоединение указателя к элементу
}

// ZIndex изменяется при опускании указателя. 
// Элемент, на который опустился указатель, становится самым верхним 
var zOrderCurr = e.target.style.zIndex;
var elts = document.getElementsByClassName("GestureElement");
for (var i = 0; i < elts.length; i++) {
if (elts[i].style.zIndex === 3) {
elts[i].style.zIndex = zOrderCurr;
}
e.target.style.zIndex = 3;
}
}
 

Обработчик MSGestureChange выполняется для каждого отдельного фрагмента (onGestureChange), затем собирает все данные по переносу, вращению, масштабированию в объекте eventArgs и применяет их с помощью CSS. Это показывает, как удобно то, что все эти свойства уже описаны в нужной нам системе координат:

function onGestureChange(e) {
var elt = e.target;
var m = new MSCSSMatrix(elt.style.msTransform);

 elt.style.msTransform = m.	
translate(e.offsetX, e.offsetY).	
translate(e.translationX, e.translationY).
rotate(e.rotation * 180 / Math.PI).	
scale(e.scale).	
translate(-e.offsetX, -e.offsetY);	
}	
 

В коде примера выполняется немного больше всего, но то, что мы показали здесь – это самые важные части. Очевидно, если вам не нужна поддержка манипуляций определенного вида, вы можете просто игнорировать определенные свойства в объекте eventArgs.

Сценарий 2 этого примера формирует такое же изображение, но он реализован несколько иначе. Как вы можете видеть, в его функции initialize (js/gesture.js), изначально регистрируется лишь одно событие, применяющееся к "столу", который имеет черный фон и окружающую его границу.

Объекты жестов для отдельных фрагментов головоломки создаются и прикрепляются к указателям в событии MSPointerDown (onTableTopPointerDown). Этот подход более эффективен и масштабируем для приложения-головоломки, которое может иметь сотни или даже тысячи фрагментов, так как объекты жестов хранятся лишь до тех пор, пока осуществляется манипуляция конкретным фрагментом. Эти манипуляции так же похожи на те, что были в Сценарии 1, где свойства MSGestureChange применялись посредством CSS-трансформаций. Для того чтобы узнать подробности, обратитесь к комментариям к коду в js/gesture.js, так как они весьма обширны.

Сценарий 3 этого примера предоставляет еще одну демонстрацию выполнения жестов переноса, сжатия-расширения и вращения с использованием колеса мыши. Как показано в упражнении PointerEvents, единственное, что нужно для того, чтобы сделать это – это обработать событие wheel, установить eventArgs.pointerId в 1 и передать это в обработчик MSPointerDown, который затем добавит указатель к объекту жеста:

 function onMouseWheel(e) {
e.pointerId = 1; // Фиксированный pointerId для MouseWheel
onPointerDown(e);	
}	
 

Опять же, это все, что нужно. (Мне нравится, когда все так просто!). В качестве упражнения, вы можете попытаться добавить этот код в Сценарии 1 и 2.

Средство распознавания жестов

В случае с инерционными жестами, которые продолжают посылать некоторое количество событий MSGestureChange, когда указатель освобожден, вы можете задаться вопросами: "А что, на самом деле, управляет этими событиями? Очевидно, существует особая модель уменьшения скорости, встроенная в эти события, а именно та, на основе которой построены внешний вид и впечатления от взаимодействия с Windows. Но что, если мне нужно другое поведение? И что, если я хочу совсем иначе интерпретировать события указателя?"

Вспомогательный механизм, который интерпретирует события указателя, превращая их в жесты, называется средством распознавания жестов (gesture recognizer). Его вы можете получить напрямую посредством объекта Windows.UI.Input.GestureRecognizer (http://msdn.microsoft.com/library/windows/apps/windows.ui.input.gesturerecognizer.aspx). После создания экземпляра этого объекта с помощью команды new, вы можете устанавливать его свойства gestureSettings (http://msdn.microsoft.com/library/windows/apps/windows.ui.input.gesturerecognizer.gesturesettings.aspx) для тех видов манипуляций и жестов, которые вам интересны. Документация по Windows.UI.Input.GestureSettings предоставляет полный набор возможностей, в том числе, такие жесты, как tap, doubleTap, hold, holdWithMouse, rightTap, drag, переносы, вращения, масштабирование, перемещение с инерцией, и crossSlide (скольжение по диагонали). Например, в примере "Ввод: манипуляции и жесты" (http://code.msdn.microsoft.com/windowsapps/Manipulations-and-gestures-26918bb3) (ballineight.js) мы можем видеть, как настраивают средство распознавания жестов для жестов касания (tap), вращения (rotate), переноса (translate) и масштабирования (scale) (с инерцией):

 gr = new Windows.UI.Input.GestureRecognizer();

// Настройка GestureRecognizer для определения манипуляций вращения, переноса, масштабирования,
// с инерцией для данных трех компонентов манипуляции и обработки касания
gr.gestureSettings =
Windows.UI.Input.GestureSettings.manipulationRotate |
Windows.UI.Input.GestureSettings.manipulationTranslateX | Windows.UI.Input.GestureSettings.manipulationTranslateY | 
Windows.UI.Input.GestureSettings.manipulationScale | 
Windows.UI.Input.GestureSettings.manipulationRotateInertia | Windows.UI.Input.GestureSettings.manipulationScaleInertia | 
Windows.UI.Input.GestureSettings.manipulationTranslateInertia |
Windows.UI.Input.GestureSettings.tap;

// Выключение обратной связи пользовательского интерфейса для жестов 
// (обратная связь все еще используется для PointerPoints)
gr.showGestureFeedback = false;

Объект GestureRecognizer так же имеет множество свойств для настройки его специфических событий. Например, для жестов скольжения по диагонали (cross-slides) вы можете задать свойства crossSlideThresholds, crossSlideExact, и crossSlideHorizontally. Вы можете задать быстроту снижения скорости (в пикселях/мс2) посредством свойств inertiaExpansionDeceleration, inertiaRotationDeceleration, и inertiaTranslationDeceleration.

После настройки, вы начинаете передавать событий MSPointer* к объекту средсва распознавания жестов, в частности, его методам, которые называются processDownEvent, processMoveEvents, и processUpEvent (так же, если нужно, processMouseWheelEvent, и processInertia). В ответ, в зависимости от конфигурации, средство распознавания жестов запустит некоторое количество собственных событий. Во-первых, это дискретные события, наподобие crossSliding, dragging, holding, rightTapped, и tapped. Для остальных он запустит серию событий manipuationStarted, manipulationUpdated, manipulationInertiaStarting, и manipulationCompleted. Обратите внимание на то, что все эти события исходят из WinRT, поэтому убедитесь, что вызвали, когда нужно, removeEventListener.

Когда вы используете средство распознавания жестов напрямую, другими совами, прослушиваете события MSPointer*, передаете их средству распознавания жестов и затем прослушиваете его специфические события и действуете, опираясь на них, как в вышеприведенном примере, вместо работы с событиями MSGesture*, которые исходят от средства распознавания жестов по умолчанию, сконфигурированного с помощью объекта MSGesture.

Обратитесь к документации Windows.UI.Input.GestureRecognizer (http://msdn.microsoft.com/library/windows/apps/windows.ui.input.gesturerecognizer.aspx) для того, чтобы узнать подробности и к примерам, чтобы посмотреть на образцы кода. В качестве дополнительного примера, вот фрагмент кода, который перехватывает небольшие горизонтальные перемещения с использованием установки manipulationTranslateX:

var recognizer = new Windows.UI.Input.GestureRecognizer();
recognizer.gestureSettings = Windows.UI.Input.GestureSettings.manipulationTranslateX;
var DELTA = 10;

myElement.addEventListener('MSPointerDown', function (e) {
recognizer.processDownEvent(e.getCurrentPoint(e.pointerId));
});
myElement.addEventListener('MSPointerUp', function (e) {
recognizer.processUpEvent(e.getCurrentPoint(e.pointerId));
});
myElement.addEventListener('MSPointerMove', function (e) {
recognizer.processMoveEvents(e.getIntermediatePoints(e.pointerId));
});

// Помните, что при работе с этими событиями нужен removeEventListener 
recognizer.addEventListener('manipulationcompleted', function (args) {
var pt = args.cumulative.translation;
if (pt.x < -DELTA) {
// переместить вправо
}
else if (pt.x > DELTA) {
// переместить влево
}
});
 

Помимо использования средства распознавания жестов, помните, что вы всегда можете перейти на более низкий уровень и выполнить собственную обработку событий MSPointer* , если вам это нужно, и, таким образом, полностью обойти средство распознавания жестов. Это может быть нужно, если конфигурации, допустимые в средстве распознавания жестов, не соответствуют вашим особенным нуждам. В то же время, сейчас неплохая возможность для того, чтобы перечитать врезку "Создание совершенно новых жестов?" в конце раздела о языке касаний. Она посвящена некоторым вопросам о том, когда действительно нужны пользовательские жесты.

< Лекция 3 || Лекция 4: 12345 || Лекция 5 >
Дмитрий Мельник
Дмитрий Мельник
Беларусь
Сергей Ширяев
Сергей Ширяев
Россия, г. Москва