Обработка изменений входных данных

Chromebook предлагают пользователям множество различных вариантов ввода: клавиатура, мышь, трекпад, сенсорный экран, стилус, MIDI и геймпад/синие контроллеры. Это означает, что одно и то же устройство может стать диджейской станцией, холстом для художника или предпочтительной платформой для геймеров, желающих транслировать AAA-игры.

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

На этой странице вы найдете основные вопросы, которые следует учитывать при рассмотрении множественных источников информации, а также стратегии для их решения.

Поиск пользователем поддерживаемых методов ввода

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

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

  • Приложение для воспроизведения мультимедиа может поддерживать множество удобных сочетаний клавиш, которые пользователю может быть непросто угадать.
  • Если вы создали приложение для диджеев, пользователь может сначала использовать сенсорный экран и не осознавать, что вы разрешили ему использовать клавиатуру/трекпад для тактильного доступа к некоторым функциям. Аналогично, он может не знать, что вы поддерживаете ряд MIDI-контроллеров для диджеев, и побуждение его к ознакомлению с поддерживаемым оборудованием может сделать процесс диджеинга более аутентичным.
  • Ваша игра может отлично работать с сенсорным экраном и клавиатурой/мышью, но пользователи могут не знать, что она также поддерживает ряд игровых контроллеров Bluetooth. Подключение одного из таких контроллеров может повысить удовлетворенность пользователей и вовлеченность в игру.

Вы можете помочь пользователям находить варианты ввода с помощью сообщений в подходящее время. Реализация будет выглядеть по-разному для каждого приложения. Вот несколько примеров:

  • Всплывающие окна, появляющиеся впервые или являющиеся советом дня.
  • Параметры конфигурации в панели настроек могут косвенно указывать пользователям на наличие поддержки. Например, вкладка «Игровой контроллер» в панели настроек игры указывает на поддержку контроллеров.
  • Контекстные сообщения. Например, если вы обнаружили физическую клавиатуру и выяснили, что пользователь выполняет действие с помощью мыши или сенсорного экрана, вы можете показать полезную подсказку, предлагающую сочетание клавиш.
  • Списки сочетаний клавиш. При обнаружении физической клавиатуры отображение в пользовательском интерфейсе способа вызова списка доступных сочетаний клавиш служит двойной цели: информированию о наличии поддержки клавиатуры и предоставлению пользователям простого способа увидеть и запомнить поддерживаемые сочетания клавиш.

UI-маркировка/макет для вариантов ввода

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

Пользовательский интерфейс, специально разработанный для сенсорного управления

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

Интерфейсы гоночных игр — один с экранным управлением, другой с управлением с клавиатуры.

Экранные подсказки

Ваше приложение может предоставлять пользователям полезные подсказки на экране. Будьте внимательны, чтобы они не зависели от устройства ввода. Например:

  • Проведите пальцем, чтобы…
  • Нажмите в любом месте, чтобы закрыть.
  • Масштабирование с помощью жеста «щипок»
  • Нажмите «X», чтобы…
  • Нажмите и удерживайте для активации.

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

Учет многоязычности

Если ваше приложение поддерживает несколько языков, вам следует тщательно продумать архитектуру строкового ввода. Например, если вы поддерживаете 3 режима ввода и 5 языков, это может означать 15 различных версий каждого сообщения пользовательского интерфейса. Это увеличит объем работы, необходимой для добавления новых функций, и повысит вероятность орфографических ошибок.

Один из подходов заключается в том, чтобы рассматривать действия интерфейса как отдельный набор строк. Например, если вы определите действие «закрыть» как отдельную строковую переменную с вариантами, специфичными для ввода, такими как: «Нажмите в любом месте, чтобы закрыть», «Нажмите 'Esc', чтобы закрыть», «Щелкните в любом месте, чтобы закрыть», «Нажмите любую кнопку, чтобы закрыть», то все ваши сообщения интерфейса, которые должны сообщить пользователю, как закрыть что-либо, могут использовать эту единственную строковую переменную «закрыть». При изменении способа ввода просто измените значение этой единственной переменной.

Экранная клавиатура / ввод IME

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

Выполнение

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

Следующий шаг — отображение корректного пользовательского интерфейса для используемого в данный момент метода ввода. Как точно это определить? Что произойдет, если пользователи переключаются между различными методами ввода во время работы с вашим приложением? А если они используют несколько методов одновременно?

Приоритетный конечный автомат на основе полученных событий

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

Расставьте приоритеты

Почему важно уделять приоритетное внимание состояниям ввода? Пользователи взаимодействуют со своими устройствами самыми разными способами, и ваше приложение должно поддерживать их выбор. Например, если пользователь решит использовать сенсорный экран и Bluetooth-мышь одновременно, это должно поддерживаться. Но какие подсказки и элементы управления на экране следует отображать? Мышь или сенсорный экран?

Определение каждого набора запросов как «входного состояния» и последующая приоритизация могут помочь принимать решения согласованным образом.

Полученные входные события определяют состояние.

Почему нужно реагировать только на полученные входные события? Возможно, вы подумаете, что можно отслеживать соединения Bluetooth, чтобы определить, подключен ли контроллер Bluetooth, или наблюдать за соединениями USB для обнаружения USB-устройств. Однако такой подход не рекомендуется по двум основным причинам.

Во-первых, информация о подключенных устройствах, которую можно угадать на основе переменных API, непостоянна, а количество устройств Bluetooth/оборудования/стилусов постоянно растет.

Вторая причина использовать полученные события вместо статуса подключения заключается в том, что у пользователей может быть подключена мышь, Bluetooth-контроллер, MIDI-контроллер и т. д., но они могут не использовать их активно для взаимодействия с вашим приложением или игрой.

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

Пример: игра с поддержкой сенсорного управления, клавиатуры/мыши и контроллера.

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

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

Вы хотите добавить поддержку клавиатуры и Bluetooth-игрового контроллера. С чего начать?

Гоночная игра с сенсорным управлением

Входные состояния

Для начала определите все состояния ввода, в которых может находиться ваша игра, а затем перечислите все параметры, которые вы хотели бы изменить в каждом состоянии.

Трогать Клавиатура/Мышь Игровой контроллер

Реагирует на

Все входные данные

Все входные данные

Все входные данные

Экранные элементы управления

- Экранный джойстик
- Кнопка «Нитро»

- Без джойстика
- Нет кнопки нитро

- Без джойстика
- Нет кнопки нитро

Текст

Нажмите, чтобы включить нитро!

Нажмите "N" для включения нитро!

Нажмите "A" для включения нитро!

Учебное пособие

Изображение джойстика для управления скоростью/направлением.

Изображение клавиш со стрелками и клавиш WASD для управления скоростью/направлением.

Изображение геймпада для отображения скорости/направления.

Отслеживайте состояние «активного поля ввода» и при необходимости обновляйте пользовательский интерфейс в зависимости от этого состояния.

Примечание: Помните, что ваша игра/приложение должна реагировать на все методы ввода, независимо от состояния. Например, если пользователь управляет автомобилем с помощью сенсорного экрана, но нажимает клавишу N на клавиатуре, должно сработать действие нитроускорения.

Приоритетные изменения состояния

Некоторые пользователи могут одновременно использовать сенсорный экран и клавиатуру. Другие могут начать играть в игру/приложение на диване в режиме планшета, а затем переключиться на использование клавиатуры на столе. Третьи могут подключать или отключать игровые контроллеры в середине игры.

В идеале, нежелательно наличие некорректных элементов пользовательского интерфейса — например, указание пользователю нажимать клавишу «n» при использовании сенсорного экрана. В то же время, в случае одновременного использования несколькими устройствами ввода, такими как сенсорный экран и клавиатура, нежелательно постоянное переключение интерфейса между этими двумя состояниями.

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

Если бы события с клавиатуры и сенсорного экрана поступали одновременно, игра должна была бы оставаться в режиме сенсорного экрана, хотя и продолжала бы реагировать на ввод с клавиатуры. Если бы в течение 5 секунд не поступало никаких данных с сенсорного экрана, а события с клавиатуры продолжали поступать, возможно, экранные элементы управления исчезли бы, и игра перешла бы в режим клавиатуры.

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

Ниже представлена ​​диаграмма состояний и таблица переходов для примера. Адаптируйте эту идею для своего приложения или игры.

Приоритетный конечный автомат — сенсорный экран, клавиатура/мышь, игровой контроллер

#1 Сенсорный экран #2 Клавиатура #3 Геймпад

Переместиться на #1

Н/Д

— Получен сенсорный ввод
- Немедленно перейти в состояние сенсорного ввода

— Получен сенсорный ввод
- Немедленно перейти в состояние сенсорного ввода

Перейти к #2

- Бесконтактная оплата в течение 5 секунд
- Получен ввод с клавиатуры
- Переход в состояние ввода с клавиатуры

Н/Д

- Получен ввод с клавиатуры
(Немедленно перейти в режим ввода с клавиатуры)

Перейти к #3

- Бесконтактная оплата в течение 5 секунд
- Нет клавиатуры для 5s
- Получен ввод с геймпада
- Переключиться в режим ввода с геймпада

- Нет клавиатуры для 5s
- Получен ввод с геймпада
- Переключиться в режим ввода с геймпада

Н/Д

Примечание: Обратите внимание, как приоритизация помогает понять, какой тип входных данных должен быть доминирующим. Состояние входных данных мгновенно повышается в приоритете:

3. Геймпад -> 2. Клавиатура -> 1. Сенсорный экран

Как только используется устройство с более высоким приоритетом, оно постепенно «снижает» свой приоритет, только после периода задержки и только в том случае, если устройство с более низким приоритетом активно используется.

Входные события

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

Кнопки клавиатуры и контроллера

// Drive the state machine based on the received input type
// onKeyDown drives the state machine, but does not trigger game actions
// Both keyboard and game controller events come through as key events
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
    if (event != null) {
        // Check input source
        val outputMessage = when (event.source) {
            SOURCE_KEYBOARD -> {
                MyStateMachine.KeyboardEventReceived()
                "Keyboard event"
            }
            SOURCE_GAMEPAD -> {
                MyStateMachine.ControllerEventReceived()
                "Game controller event"
            }
            else -> "Other key event: ${event.source}"
        }
        // Do something based on source type
        findViewById(R.id.text_message).text = outputMessage
    }
    // Pass event up to system
    return super.onKeyDown(keyCode, event)
}
// Trigger game events based on key release
// Both keyboard and game controller events come through as key events
override fun onKeyUp(keyCode: Int, event: KeyEvent?): Boolean {
   when(keyCode) {
       KeyEvent.KEYCODE_N -> {
           MyStateMachine.keyboardEventReceived()
           engageNitro()
           return true // event handled here, return true
       }
   }
   // If event not handled, pass up to system
   return super.onKeyUp(keyCode, event)
}

Примечание: для событий KeyEvents вы можете использовать либо onKeyDown() , либо onKeyUp() . В данном случае onKeyDown() используется для управления конечным автоматом, а onKeyUp() — для запуска игровых событий.

Если пользователь нажимает и удерживает кнопку, onKeyUp() будет срабатывать только один раз за каждое нажатие, тогда как onKeyDown() будет вызываться несколько раз. Если вы хотите реагировать на нажатие, вам следует обрабатывать игровые события в onKeyDown() и реализовать логику для обработки повторяющихся событий. Дополнительную информацию см. в документации по обработке действий клавиатуры .

Сенсорный экран и стилус

// Touch and stylus events come through as touch events
override fun onTouchEvent(event: MotionEvent?): Boolean {
   if (event != null) {
       // Get tool type
       val pointerIndex = event.action and ACTION_POINTER_INDEX_MASK shr ACTION_POINTER_INDEX_SHIFT
       val toolType = event.getToolType(pointerIndex)

       // Check tool type
       val outputMessage = when (toolType) {
           TOOL_TYPE_FINGER -> {
               MyStateMachine.TouchEventReceived()
               "Touch event"
           }
           TOOL_TYPE_STYLUS -> {
                MyStateMachine.StylusEventReceived()
               "Stylus event"
           }
           else -> "Other touch event: ${toolType}"
       }

       // Do something based on tool type, return true if event handled
       findViewById(R.id.text_message).text = outputMessage
   }
   // If event not handled, pass up to system
   return super.onGenericMotionEvent(event)
}

Мышь и джойстик

// Mouse and joystick events come through as generic events
override fun onGenericMotionEvent(event: MotionEvent?): Boolean {
   if (event != null) {
       // Check input source
       val outputMessage = when (event.source) {
           SOURCE_JOYSTICK -> {
                MyStateMachine.ControllerEventReceived()
               "Controller event"
           }
           SOURCE_MOUSE -> {
                MyStateMachine.MouseEventReceived()
               "Mouse event"
           }
           else -> "Other generic event: ${event.source}"
       }
       // Do something based on source type, return true if event handled
       findViewById(R.id.text_message).text = outputMessage
   }
   // If event not handled, pass up to system
   return super.onGenericMotionEvent(event)
}