Основы работы с сенсорным вводом
Цель лекции: Обработка касаний, жестов, событий, написание кода.
Экран телефона чувствителен к прикосновению и существенно отличается от старых сенсорных экранов, в основном повторяющих ввод с мыши, или экранов планшетных устройств, которые могут распознавать рукописный ввод.
Мультисенсорный экран устройства Windows Phone 7 может распознавать одновременное касание как минимум в четырех точках. Именно обработка взаимодействия этих одновременных касаний делает задачу реализации мультисенсорного ввода такой сложной для разработчиков. Мы будем рассматривать сенсорный интерфейс в контексте примеров приложений, которые могут реагировать на простые касания.
Тестирование критически важного кода реализации мультисенсорного ввода необходимо выполнять на реальном устройстве, работающем под управлением Windows Phone 7. Между тем эмулятор телефона будет реагировать на действия мыши и преобразовывать их в сенсорный ввод. Чтобы использовать сенсорный ввод непосредственно на эмуляторе, его необходимо запустить под управлением Windows 7 на устройстве с поддерживающим мультисенсорный ввод экраном.
Приведенные в данной главе программы во многом схожи с простыми приложениями "XnaHelloPhone" из четвёртой лекции. Единственное отличие в том, что при касании текста пальцем, он будет случайным образом менять свой цвет, а при касании вне области текста, он будет опять возвращаться к исходному белому цвету (или любому другому цвету, какой будет применен к тексту при запуске программы).
В программе на Silverlight сенсорный ввод реализован через события. В приложении на XNA сенсорный ввод передается через статический класс, опрашиваемый в ходе выполнения метода Update. Одно из основных назначений XNA-метода Update – проверка состояния сенсорного ввода и внесение изменений, которые отображаются на экране во время выполнения метода Draw.
Обработка простого касания в XNA
В XNA устройства мультисенсорного ввода называют сенсорной панелью. Для обработки такого ввода используются методы статического класса TouchPanel (Сенсорная панель). Имеется также возможность обработки жестов, но пока начнем с более простыми данными касания.
Можно (но это не является обязательным) получать сведения о самом устройстве мультисенсорного ввода через вызов статического метода TouchPanel.GetCapabilities. Объект TouchPanelCapabilities (Возможности сенсорной панели), возвращаемый этим методом, имеет два свойства:
- MaximumTouchCount (Максимальное число касаний) возвращает количество точек касания, как минимум 4 для телефона.
- IsConnected (Подключен) имеет значение true, если сенсорная панель доступна. Для телефона его значение всегда true.
Для большинства задач достаточно использовать один из двух статических методов TouchPanel. Для получения ввода от простого касания при каждом вызове Update после запуска программы, скорее всего, будет вызываться этот метод:
TouchCollection touchLocations = TouchPanel.GetState();
TouchCollection (Коллекция касаний) – это коллекция, включающая нуль или более объектов TouchLocation (Место касания). TouchLocation имеет три свойства:
- State (Состояние), его значениями являются элементы перечисления TouchLocationState (Состояние места касания): Pressed (Нажат), Moved (Перемещен), Released (Высвобожден).
- Position (Местоположение) – это Vector2, обозначающий положение пальца относительно верхнего левого угла окна просмотра.
- Id – целое число, идентифицирующее отдельное касание от состояния Pressed до Released, то есть в течение всего времени касания.
Если ни один палец не касается экрана, коллекция TouchCollection пуста. Когда палец впервые касается экрана, в TouchCollection появляется объект, свойство State которого имеет значение Pressed. Последующие вызовы TouchPanel.GetState покажут, что значение State объекта TouchLocation равно Moved, даже если фактически палец никуда не перемещался. Когда палец будет убран с экрана, свойство State объекта TouchLocation примет значение Released. Последующие вызовы TouchPanel.GetState продемонстрируют, что коллекция TouchCollection опять пуста.
Единственное исключение, если палец быстро "постукивает" по экрану – т.е. поднимается и опускается на экран с частотой примерно 1/30 секунды – свойство State объекта TouchLocation от значения Pressed сразу перейдет к значению Released, минуя состояния Moved.
Как правило, экрана будут касаться множество пальцев; и опускаться, перемещаться и покидать экран они будут независимо друг от друга. Для отслеживания отдельного касания используется свойство Id (Идентификатор). Для одного отдельно взятого касания Id будет неизменным от состояния Pressed, на протяжении всех перемещений (значения Moved) и до состояния Released.
TouchLocation также имеет очень удобный метод TryGetPreviousLocation (Попытаться получить предыдущее местоположение), который вызывается следующим образом:
TouchLocation previousTouchLocation; bool success = touchLocation.TryGetPreviousLocation(out previousTouchLocation);
Вызов этого метода практически всегда происходит, когда touchLocation.State имеет значение Moved, для получения предыдущего местоположения и вычисления разницы. Если значение touchLocation.State равно Pressed, TryGetPreviousLocation возвратит false, и значением previousTouchLocation.State будет элемент перечисления TouchLocationState.Invalid. Такие же результаты будут получены в случае вызова этого метода для TouchLocation, который был возвращен TryGetPreviousLocation.
Разрабатываемое нами приложение будет менять цвет текста при касании пользователем экрана, т.е. обработка TouchPanel.GetStates будет относительно простой. Логика приложения будет проверять только объекты TouchLocation, значение свойства State которых равно Pressed.
Назовем новый проект XnaTouchHello. Как и в предыдущем примере на XNA нам понадобится шрифт, который мы создадим. Назовём его "segoe36" и, открыв файл со шрифтом, увеличим его размер до. 36:
<Size>36</Size>
Потребуется еще несколько дополнительных полей:
Проект XNA: XnaTouchHello Файл: Game1.cs (фрагмент, демонстрирующий поля)
public class Game1 : Microsoft.Xna.Framework.Game { GraphicsDeviceManager graphics; SpriteBatch spriteBatch; Random rand = new Random(); string text = "Hello, Windows Phone 7!"; SpriteFont segoe36; Vector2 textSize; Vector2 textPosition; Color textColor = Color.White; … }
Метод LoadContent аналогичен используемому ранее, за исключением того, что textSize сохраняется как поле. Это обеспечит возможности доступа к нему при последующих вычислениях:
Проект XNA: XnaTouchHello Файл: Game1.cs (фрагмент)
protected override void LoadContent() { spriteBatch = new SpriteBatch(GraphicsDevice); segoe36 = this.Content.Load<SpriteFont>("Segoe36"); textSize = segoe36.MeasureString(text); Viewport viewport = this.GraphicsDevice.Viewport; textPosition = new Vector2((viewport.Width - textSize.X) / 2, (viewport.Height - textSize.Y) / 2); }
Как это свойственно приложениям на XNA, "действие" происходит преимущественно в методе Update. Этот метод вызывает TouchPanel.GetStates и затем поэлементно обходит коллекцию объектов TouchLocation, выбирая те из них, значение State которых равно Pressed.
Проект XNA: XnaTouchHello Файл: Game1.cs (фрагмент)
protected override void Update(GameTime gameTime) { if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed) this.Exit(); TouchCollection touchLocations = TouchPanel.GetState(); foreach (TouchLocation touchLocation in touchLocations) { if (touchLocation.State == TouchLocationState.Pressed) { Vector2 touchPosition = touchLocation.Position; if (touchPosition.X >= textPosition.X && touchPosition.X < textPosition.X + textSize.X && touchPosition.Y >= textPosition.Y && touchPosition.Y < textPosition.Y + textSize.Y) { textColor = new Color((byte)rand.Next(256), (byte)rand.Next(256), (byte)rand.Next(256)); } else { textColor = Color.White; } } } base.Update(gameTime); }
Если Position оказывается где-нибудь внутри области, занимаемой текстовой строкой, полю textColor (Цвет текста) присваивается случайное значение RGB для цвета с помощью одного из конструкторов структуры Color (Цвет). В противном случае textColor присваивается значение Color.White.
Метод Draw практически аналогичен используемому в предыдущих примерах, только цвет текста теперь стал переменным:
Проект XNA: XnaTouchHello Файл: Game1.cs (фрагмент)
protected override void Draw(GameTime gameTime) { this.GraphicsDevice.Clear(Color.Navy); spriteBatch.Begin(); spriteBatch.DrawString(segoe36, text, textPosition, textColor); spriteBatch.End(); base.Draw(gameTime); }
Единственная проблема в том, что касание не так строго детерминировано, как этого хотелось бы. Даже при касании одним пальцем контактов с экраном может быть несколько. В некоторых случаях один и тот же цикл foreach в методе Update может задавать textColor несколько раз.
Запустим наш проект, в результате при касании текста, его цвет будет случайным образом меняться, при касании вне его, цвет текста будет устанавливаться на цвет по умолчанию.
Обработка жестов в XNA
Создадим новый проект под названием XnaTapHello. Повторим все действия, что и для предыдущего приложения до изменения метода LoadContent.
Класс TouchPanel также включает возможности распознавания жестов, что демонстрирует проект XnaTapHello. В данном проекте используются те же поля, что и в XnaTouchHello, но несколько отличается метод LoadContent:
Проект XNA: XnaTapHello Файл: Game1.cs (фрагмент)
protected override void LoadContent() { spriteBatch = new SpriteBatch(GraphicsDevice); segoe36 = this.Content.Load<SpriteFont>("Segoe36"); textSize = segoe36.MeasureString(text); Viewport viewport = this.GraphicsDevice.Viewport; textPosition = new Vector2((viewport.Width - textSize.X) / 2, (viewport.Height - textSize.Y) / 2); TouchPanel.EnabledGestures = GestureType.Tap; }
Обратите внимание на последнее выражение. GestureType (Тип жеста) – это перечисление, элементами которого являются:
- Tap (Касание),
- DoubleTap (Двойное касание),
- Flick (Скольжение),
- Hold (Удержание),
- Pinch1(Сведение) - обычно используется для операции Zoom (Масштабирование), путем сведения или разведения пальцев,
- PinchComplete (Сведение завершено),
- FreeDrag (Произвольное перетягивание),
- HorizontalDrag (Перетягивание по горизонтали),
- VerticalDrag (Перетягивание по вертикали),
- DragComplete (Перетягивание завершено).
Эти элементы определены как битовые флаги, таким образом, они могут комбинироваться с помощью побитового C#-оператора OR.
Метод Update совсем другой.
Проект XNA: XnaTapHello Файл: Game1.cs (фрагмент)
protected override void Update(GameTime gameTime) { // Обеспечиваем возможность выхода из игры if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed) this.Exit(); while (TouchPanel.IsGestureAvailable) { GestureSample gestureSample = TouchPanel.ReadGesture(); if (gestureSample.GestureType == GestureType.Tap) { Vector2 touchPosition = gestureSample.Position; if (touchPosition.X >= textPosition.X && touchPosition.X < textPosition.X + textSize.X && touchPosition.Y >= textPosition.Y && touchPosition.Y < textPosition.Y + textSize.Y) { textColor = new Color((byte)rand.Next(256), (byte)rand.Next(256), (byte)rand.Next(256)); } else { textColor = Color.White; } } } base.Update(gameTime); }
Несмотря на то, что данное приложение рассматривает только один тип жеста, код довольно универсален. Если жест доступен, метод TouchPanel.ReadGesture (Прочитать жест) возвращает его как объект типа GestureSample (Пример жеста). Кроме применяемых здесь GestureType и Position, у этого объекта имеется еще свойство Delta (Приращение), обеспечивающее данные о перемещении в виде объекта Vector2. Для некоторых жестов (таких как Pinch) GestureSample также предоставляет состояние второй точки касания через свойства Position2 и Delta2.
Метод Draw аналогичен используемому в предыдущем случае, но поведение данного приложения будет несколько иным. В предыдущей программе текст меняет цвет, когда палец касается экрана; во втором изменение цвета происходит, когда палец убирается с экрана. Средству распознавания жестов необходимо дождаться завершения жеста, чтобы определить его тип.