Основы работы с приложениями

Как масштабировать способы ввода за пределы сенсорного экрана с помощью Jetpack Compose

Колонки

  • Алекс Ваньо (Alex Vanyo)

    Разработчик ПО

  • Джереми Уокер (Jeremy Walker)

    Разработчик ПО Compose

О приложении

О выступлении

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

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

Сводка

  • Jetpack Compose обрабатывает входящие события указателя Android через шестифазный конвейер. Этот конвейер поддерживает дерево путей нажатия и отправляет события в три прохода по узлам пользовательского интерфейса.
  • Ввод с сенсорной панели в Compose теперь сопоставляется с событиями мыши, а не касания. Compose также преобразует события имитации пальца в специальные события указателя панорамирования и масштабирования.
  • Сенсорные панели умных очков поддерживают непрямой ввод, а не касания с координатами экрана. Они используют основную ось направленного движения, чтобы сопоставить горизонтальные пролистывания с вертикальной навигацией по следующим или предыдущим элементам.
  • Поддержка тестирования расширена за счет новых методов, таких как performTrackpadInput. Эти API предоставляют привычные тестовые поверхности для проверки того, что приложения правильно обрабатывают устройства без сенсорного экрана.

Gemini Notebook

Темы

  • jetpack-compose
  • pointer-input
  • сенсорные панели
  • жесты
  • intelligent-eyewear
  • специальные возможности

Расшифровка

Введение и принципы ввода

  • Здравствуйте!

  • Надеемся, что вам нравится Саммит для разработчиков Android. Спасибо, что пришли на наше выступление.

  • Меня зовут Алекс Ваньо, я инженер-программист в команде Compose. Со мной Джереми, тоже инженер-программист в команде Compose.

  • Мы оба работали над улучшением системы ввода Jetpack Compose, и сегодня мы поговорим о том, как мы расширили ее за пределы сенсорного экрана, чтобы упростить поддержку несенсорного ввода.

  • Ввод – это любой способ взаимодействия пользователя с приложением.

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

  • К другим типам ввода относятся физические клавиатуры, мыши, сенсорные панели, стилусы, контроллеры и поворотные устройства.

  • Появляются и новые типы ввода, например сенсорные панели на умных очках, отслеживание глаз и рук в XR, а также жесты пальцами и запястьем на Wear OS.

  • Если вы хотите пойти ещё дальше, есть и другие типы ввода, например программные редакторы метода ввода для редактирования текста, микрофоны, камеры и другие датчики устройств.

  • Приложения должны поддерживать разные способы ввода.

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

  • Для каждого типа устройств есть наиболее распространенный способ ввода, но не стоит полагать, что он единственный.

  • Хотя у Chromebook есть встроенная сенсорная панель и клавиатура, эти типы ввода не уникальны для такого форм-фактора.

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

  • Вторая важная причина, по которой следует ожидать и поддерживать несколько типов ввода, – это доступность.

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

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

  • Короче говоря, вы должны обеспечить поддержку различных типов ввода независимо от форм-фактора.

  • Давайте посмотрим, как Jetpack Compose помогает вашим приложениям удовлетворять эти потребности пользователей.

  • Философия Compose в целом заключается в том, что эти различные типы ввода должны вести себя соответствующим образом со стандартными компонентами без необходимости делать что-то особенное.

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

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

  • В этом докладе мы рассмотрим два таких примера, которые недавно были добавлены в Compose.

  • Оба устройства похожи на сенсорную панель, но используются по-разному.

  • Начнем с недавних разработок в области сенсорных панелей для ноутбуков.

  • Сенсорная панель – это устройство ввода, работающее на основе указателя.

  • Точнее, когда вы используете сенсорную панель, вы получаете двумерные координаты.

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

  • Вместо этого для сенсорной панели, похожей на ноутбук, есть перевод для управления курсором.

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

  • Итак, то, что вы получаете на стороне Compose, — это указатель, соответствующий местоположению курсора, который обычно действует как управление мышью.

  • Платформа Android предоставляет информацию о наведении указателя, кликах, в том числе дополнительных, и все эти данные упаковываются в MotionEvents.

  • Чтобы понять, как мы улучшили поведение в последних версиях Compose, давайте сначала рассмотрим, как Compose обрабатывает эти события указателя более подробно.

Шесть этапов обработки ввода указателя

  • Я разделю его на шесть разных этапов, и мы по очереди рассмотрим их все.

  • Первый этап – перемещение MotionEvents в Compose.

  • По сути, Compose – это просто представление. Очень сложное представление, которое много чего делает, но все же представление. Это значит, что оно должно работать в рамках общей системы Android для ввода указателем.

  • Поскольку Compose находится внутри обычного окна в обычном действии, он получает MotionEvents – MotionEvents платформы Android.

  • Затем Compose должен обработать их и сделать все, что вы хотите сделать в Compose, связанное с вводом указателя.

  • Первый этап – преобразование MotionEvent во внутреннее, независимое от платформы представление в Compose, которое мы называем PointerInputEvent.

  • Это внутренний тип, поэтому вы не увидите его напрямую.

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

  • Итак, теперь у нас есть независимое от платформы представление MotionEvent в Compose.

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

  • Вы получаете множество событий ввода одно за другим, поэтому очень полезно знать, что происходит с течением времени.

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

  • Итак, следующий этап в этом конвейере обработки — добавить эту информацию о времени, и он собирается выполнить некоторую предварительную обработку на основе последовательных событий указателя.

  • В коде это выглядит так: мы берем текущее событие PointerInputEvent, объединяем его с предыдущим и создаем событие InternalPointerEvent, учитывающее время.

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

  • Это наш первый.

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

  • Их несколько.

  • Если вы используете несколько пальцев на сенсорном экране, у вас может быть несколько указателей одновременно, и нам нужно с этим справиться.

  • Если вы хотите узнать больше, посмотрите PointerInputChangeEventProducer, который делает все это, но мы пропустим его.

  • Нам ещё многое предстоит сделать.

  • Теперь у нас есть представление ввода Compose, и у него есть информация о времени.

  • Следующий шаг – определить, что делать с этим событием.

  • Куда его отправить?

  • И это приводит к тестированию Hit Path.

  • Итак, представим ситуацию, когда у нас есть MotionEvent, который отправляется, и этот ComposeView заполняет все окно, возможно, весь экран.

  • Нам нужно понять, куда его отправить.

  • И поскольку Compose – это просто представление, но внутри него есть множество разных узлов, кнопок и других элементов, с которыми вы взаимодействуете, нам нужно сделать так, чтобы Compose определял, куда отправлять событие, происходящее в определенной позиции, и какой узел на самом деле в нем заинтересован.

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

  • Но это тоже не самый общий взгляд.

  • Оказывается, нам нравится помещать интерактивные элементы внутрь других интерактивных элементов, например кнопку внутрь списка.

  • Оказывается, мы делаем это часто.

  • Таким образом, в общем случае конкретное событие ввода создает полный путь нажатия, который проходит через каждый интерактивный узел, затронутый координатами события ввода.

  • В данном случае в иерархии вложенности три уровня.

  • Итак, у нас есть путь обращения, но даже этого недостаточно.

  • Помните, я говорил, что у нас есть несколько событий ввода указателя, несколько указателей, которые могут быть нажаты?

  • Нам нужно полное дерево путей обращений.

  • Если у вас несколько указателей, то путей нажатия будет несколько.

  • Итак, это следующий этап, создание и обновление этого дерева путей обращений.

  • Это более подробный взгляд на то, что происходит.

  • Слева у нас есть узлы, которые визуально расположены, а затем в левом нижнем углу вы можете увидеть иерархическое представление, какие узлы находятся внутри других узлов, какие из них являются предками или потомками друг друга.

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

  • Когда мы получаем второй указатель, этот путь становится деревом.

  • Она может пересекаться с некоторыми из тех же узлов, что и другая, но может взаимодействовать и с другими.

  • Справа показано дерево путей обращений, которое мы сейчас поддерживаем.

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

  • Теперь

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

  • Теперь мы знаем, куда отправлять события ввода указателя и какие узлы заинтересованы в каждом конкретном событии.

  • Теперь, когда мы это знаем, мы можем выполнить преобразование координат.

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

  • Итак, как только мы это узнаем, мы можем взять событие ввода указателя, объединить его с координатами интересующего узла, и тогда это, наконец, создаст общедоступное событие PointerEvent, которое будет отправлено всем вам и модификатору ввода указателя.

  • Это последний этап, шестой, – отправка события.

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

  • Это делается в три этапа.

  • У нас есть начальный проход, основной проход и финальный проход.

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

  • Итак, первый проход, начальный проход, идет от предка к потомку.

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

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

  • Второй проход – основной.

  • И это единственный, который идет от потомка к предку.

  • Как следует из названия, это основной пропуск для выполнения действий.

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

  • И затем последний проход, финальный проход, выглядит так же, как и начальный проход, где он идет от предка к потомку.

  • И это то, где ребенок может сказать родителю, что родитель потребил часть события.

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

  • Итак, это три прохода и шесть этапов.

  • Это основа для всего, что находится поверх системы ввода указателя.

  • Все ваши жесты, прокручиваемые, перетаскиваемые, кликабельные и любые пользовательские указатели построены на этой основе.

Обновления сенсорной панели и жесты несколькими пальцами

  • Теперь мы можем поговорить о том, как именно мы улучшили поддержку сенсорных панелей в Compose.

  • Поэтому до версии Compose 1.12 события ввода с сенсорной панели регистрировались как тип указателя "касание", поскольку палец физически взаимодействует с сенсорной панелью для управления курсором.

  • Однако пользователи ожидают, что этот тип ввода будет действовать так же, как физическая мышь.

  • Поэтому мы изменили тип указателя с PointerType.Touch на PointerType.Mouse.

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

  • Например, подсказки должны работать одинаково и для мыши, и для сенсорной панели.

  • Второе важное изменение, которое мы внесли в сенсорную панель, связано с жестами несколькими пальцами, которые вы можете делать на сенсорной панели.

  • Раньше Compose использовал умную, но немного жутковатую концепцию фальшивых пальцев платформы Android.

  • Давайте посмотрим, что это значит.

  • Когда вы используете два пальца на сенсорной панели для панорамирования, Android генерирует один виртуальный палец сенсорного экрана, который опускается туда, где находится курсор.

  • Когда вы перемещаете пальцы по сенсорной панели, виртуальный палец на сенсорном экране перемещается на нужное расстояние в нужном направлении, имитируя желаемое поведение, а затем отпускает.

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

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

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

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

  • Масштабирование работает аналогичным образом.

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

  • Поэтому, начиная с Compose 1.12 для панорамирования и Compose 1.13 для масштабирования, мы перехватываем эти ложные касания.

  • Мы не отправляем их в том виде, в котором их предоставляет платформа, а интерпретируем их в новые типы событий указателя: PanStart, PanMove и PanEnd для панорамирования и ScaleStart, ScaleChange и ScaleEnd для масштабирования.

  • И мы делаем это на этапе преобразования событий Android-to-Compose, о котором я говорил ранее.

  • Поэтому мы преобразуем события Fake Finger в соответствующие типы, а затем отправляем эти жесты, определенные платформой, в Hit Path, соответствующий курсору.

  • Давайте посмотрим, почему это важно.

  • Зачем мы это делаем?

  • Допустим, у нас есть изображение, которое мы хотим увеличить.

  • Когда вы используете два пальца на сенсорной панели для масштабирования, один из двух виртуальных пальцев может не попасть по элементу.

  • Его путь попадания не пересекается с изображением и начинается за пределами границ.

  • Вместо масштабирования изображение может прокручиваться.

  • Затем, если бы за пределами изображения был другой прокручиваемый элемент, возможно, он тоже начал бы прокручиваться вместе с ним.

  • Это не то, что нужно пользователю.

  • Они хотели масштабировать бизнес.

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

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

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

  • А если вы используете что-то, созданное на их основе, вы получите улучшенное поведение, просто обновив версию Compose до последней.

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

  • И, конечно, мы добавили API для тестирования, чтобы вы могли проверить, правильно ли ваше приложение обрабатывает все эти типы ввода.

  • Таким образом, performTrackpadInput очень похож на существующие API тестирования для мыши и сенсорного ввода.

    onNodeWithContentDescription("Photo")
        .performTrackpadInput {
            click(center)
    
            pan(Offset(0f, 100f))
    
            scale(0.9f)
        }
  • Используя все эти инструменты, вы можете убедиться, что ваше приложение работает правильно независимо от того, какое устройство ввода использует пользователь на вашем устройстве.

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

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

Введение в косвенный ввод

  • Спасибо.

  • Теперь

  • Здравствуйте, меня зовут Джереми Уокер.

  • Я много работаю над вводом и подумал, что было бы интересно рассказать вам о некоторых новых вещах, которые мы делаем с вводом Compose, и о том, как это влияет на вас.

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

  • Итак, когда вы вводите данные с помощью очков, вы можете делать это несколькими способами: вы можете смотреть на что-то, вы можете использовать свой голос, вы можете использовать жесты, но я собираюсь конкретно поговорить о сенсорной панели.

  • Они немного отличаются от тех, о которых говорил Алекс. Они расположены по бокам головы, вертикальные, очень широкие и очень короткие.

  • Поэтому, когда мы думали о том, как это будет работать в Compose, если вы представите, что смотрите перед собой на маленькую кнопку, если вы перемещаете курсор, чтобы попытаться коснуться ее, это не будет отличным опытом, верно?

  • Как мы будем его поддерживать?

  • Напоминаем, как выглядит интерфейс очков.

  • Они разработаны так, чтобы контент органично вписывался в поле зрения.

  • Но это очень просто, мы стараемся не усложнять.

  • Если мы посмотрим на пример, то увидим, что это список.

  • В центре внимания находится определенный элемент.

  • Что мы действительно хотим сделать?

  • Мы хотим выбрать его или, возможно, перейти к предыдущему или следующему.

  • Указывать местоположение экрана необязательно.

  • А вот это немного сложнее, но вы видите, что в основном есть три варианта: вы можете повесить трубку, включить звук или выключить видео.

  • Но на самом деле вы хотите выбрать его или перейти к другому и выбрать его.

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

  • Вот как это выглядит.

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

  • Нажмите на нее.

  • Мы посмотрели на Android, чтобы понять, что существует и что мы можем использовать для поддержки этого.

  • И если вы когда-нибудь, если вы получите О, извините.

  • Это забегание вперед.

  • Вернуться назад

  • Так вот…

  • Теперь

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

  • Например, если вы подключили пульт к Google TV и у вас есть целая сетка фильмов, вы можете нажимать кнопки со стрелками, чтобы перейти к нужному, и нажать кнопку "ОК", чтобы выбрать его.

  • Если у вас есть меню игры и этот контроллер, вы можете использовать d-pad или джойстик, чтобы перейти к элементу интерфейса, например в меню, и выбрать его.

  • То же самое с клавиатурой.

  • Это как раз то, что нам нужно в Compose.

  • И снова, вот эти вещи, чтобы вы могли получить представление о том, как это выглядит.

  • А если мы вернемся немного назад, то увидим, что существовал тип сенсорной панели, которая была сопряжена с телевизором, и в ней использовалось то, что называется навигационным касанием.

  • Идея в том, что координаты x и y вообще не привязаны к экрану.

  • Они используются только для распознавания жестов.

  • Если вы проведете вправо, это будет равносильно нажатию на стрелку.

  • Если вы проведете влево, то переместитесь влево, вверх и вниз.

  • Итак, это дает нам в основном то, что мы хотим.

  • Вы используете x и y только для отслеживания скорости, чтобы определить эти перетаскивания и пролистывания и т. д., так что это дает нам большую часть того, что мы хотим начать с Compose.

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

  • Но это дает нам своего рода отправную точку.

  • Чтобы мы могли использовать этот тип сенсорной навигации.

Проектирование API для непрямого ввода и новых типов устройств

  • Итак, хорошо.

  • Как будет выглядеть дизайн API?

  • В Compose наиболее распространенным вводом является касание, верно?

  • Так что у нас много работы в этом направлении.

  • Поэтому нам пришлось выяснить, как это связано.

  • Напоминаем, что PointerEvent выглядит следующим образом:

  • Здесь вы можете увидеть, что у вас есть тип, который является типом перемещения вниз вверху, а затем у вас есть этот PointerInputChange, который представляет собой просто список. Вы можете думать об этом как о пальцах.

  • Если вы опустите три пальца, в списке будет три пункта.

  • Поэтому мы хотели, чтобы он выглядел примерно так.

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

  • Они будут находиться на уровне выше, на базовом уровне.

  • И именно здесь находятся все наши детекторы жестов.

  • Потому что разработчикам не нужно заботиться о таких мелких деталях.

  • Вам нужно только знать, произошло ли перетаскивание, клик или свайп, и затем выполнить действие на основе этого.

  • Поэтому у нас есть код, который делает это за вас, и он работает на основе этих технологий.

  • Итак, мы начинаем думать, хорошо, мы должны сделать так, чтобы код, существующий в foundation, мог легко поддерживать эти новые типы.

  • Поэтому мы создали новый тип, который выглядит практически так же.

  • Оно просто называется косвенным.

  • А косвенное означает, что это не прямое касание экрана.

  • Так что это позволяет вам знать, что, эй, это идет Не думайте об этом с точки зрения прикосновения.

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

  • Тип одинаковый.

  • Это список пальцев.

  • В данном случае это IndirectPointerInputChange.

  • Но мы сделали это, и все получилось.

  • Но затем мы наткнулись на ещё один элемент, который называется основной направленной осью движения.

  • Это довольно сложно, но вы всё поймёте, когда я расскажу, как это произошло.

  • Как же это произошло?

  • Ну, опять же, у вас есть эта сенсорная панель сбоку на голове, и она широкая и короткая.

  • И вот, на этом этапе у нас возникла идея: у нас есть сенсорная навигация.

  • Так что мы можем провести пальцем влево и вправо.

  • И это работает.

  • Но если вы подумаете о том, как сейчас существуют трекпады, верно?

  • Когда вы перемещаетесь влево или вправо, вы собираетесь перемещаться влево или вправо по сенсорной панели с содержимым вашего пользовательского интерфейса.

  • Если вы проведете пальцем вверх или вниз, то прокрутите список в соответствующем направлении.

  • Если вы попробуете сделать это в очках, то получится неплохо, правда?

  • Но если вы делаете это, это не очень хороший опыт.

  • Мы обнаружили, что наиболее естественным движением является, очевидно, нажатие, но также и проведение пальцем по боковой стороне очков.

  • Мы провели ряд исследований с пользователями, и они действительно ожидают, что когда вы проводите пальцем по горизонтали, вы на самом деле не перемещаете контент по горизонтали, вы перемещаете его по вертикали.

  • Вы можете представить это в уме. Например, когда вы проводите пальцем по экрану, если перед вами список, вы можете двигаться вперед по списку или назад по списку.

  • То есть мы берем двумерные входные данные и пытаемся представить их в одномерном виде.

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

  • Поэтому вы можете указать на самом устройстве.

  • Вы можете сказать, что это не обычный трекпад.

  • Я хочу, чтобы основным направлением было горизонтальное, и это, по сути, единственная ось, по которой я хочу двигаться.

  • Вот откуда это взялось.

  • Окей.

  • Так что мы чувствуем себя довольно хорошо.

  • Compose обрабатывает все.

  • И у вас есть эта хорошая вертикальная реализация.

  • Вы можете подумать об этом.

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

  • У нас нет верха, низа, лева, права.

  • Только вперед и назад.

  • Затем мы можем сопоставить это, если вы знакомы с фокусом, со следующим и предыдущим.

  • Теперь мы просто переходим к следующему и предыдущему.

  • Это отлично подходит для вертикальных видео.

  • И даже лучше.

  • Он работает с горизонтальными по умолчанию, верно?

  • Потому что у вас просто есть куча сфокусированных элементов.

  • Вы просто переходите к следующему или предыдущему, и это работает с обоими.

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

  • Поэтому нам пришлось вернуться к платформе и сказать: "Хорошо, нам нужно определить это в платформе, чтобы мы могли стандартизировать это, и эти устройства сами могли бы это указать, чтобы Compose мог полностью поддерживать это до самого конца".

  • Так что это случай, когда мы работали в Compose, продвигались вперед в Compose, возвращались назад и как бы брали его с собой.

  • Именно это мы и сделали.

  • Мы создали этот новый API, чтобы вы или устройство могли указать это.

    InputDevice.ViewBehavior
    .getPrimaryDirectionalMotionAxis()
  • И если вы помните, ранее, когда я говорил о сенсорном вводе, я говорил о том, что он предназначен в основном для телевизоров.

  • Мы не хотели нарушать эти реализации.

  • Поэтому мы добавили ещё одно поле, чтобы указать этот новый тип ввода стекла.

  • Окей.

  • Что это значит для вас?

  • Это более конкретная версия того, что вы получаете в качестве детектора жестов.

    // Glimmer Library
    Box(
        modifier =
            Modifier.fillMaxSize()
                .onIndirectPointerGesture(
                    enabled = true,
                    onSwipeForward = { /* onSwipeForward */ },
                    onSwipeBackward = { /* onSwipeBackward */ },
                    onClick = { /* onClick */ },
                )
                .focusTarget()
    ) {
        // App()
  • Если вы используете Foundation, то все будет работать сразу.

  • Мы сделали все за вас, поэтому вам не нужно ничего делать.

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

Тестирование ввода с помощью косвенного указателя

  • Окей.

  • Итак, мы рассмотрели работу самого API на платформе и в Compose.

  • Что насчет тестирования?

  • Вот как выглядит тестирование сенсорного экрана.

    @Test
    clickTest {
    
    rule.onNodeWithTag(defaultTag)
    .performTouchInput
    { click(center) }
    
    }
  • Просто скажите performTouchInput и покажите жест.

  • Жестов много, и мы создаем их из множества мелких событий.

  • Как это работает на высоком уровне?

  • У вас есть модуль Compose UI, у вас есть модуль UI Testing, и они как бы взаимодействуют, по крайней мере, с точки зрения ввода, используя MotionEvents.

  • Кроме того, в модуле тестирования пользовательского интерфейса используется механизм очередей, основанный на MotionEvent.

  • Почему я вам это рассказываю?

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

  • Но я это сделала.

  • Что это значит для вас?

  • Если вы привыкли к тестированию сенсорного ввода, то это то, что вы получаете за косвенный ввод.

  • Вы просто добавляете к названию слово "косвенный".

    @Test
    clickTest {
    
    rule.onNodeWithTag(defaultTag)
    // .performTouchInput
    .performIndirectPointerInput (
    IndirectPointerEventPrimaryDirectionalMotionAxis.X,
    inputDeviceSize,
    )
    { click(center) }
    }
  • Теперь я передаю некоторые дополнительные параметры.

  • Вы, вероятно, узнаете первый, где вы указываете горизонталь.

  • А размер входных данных, очевидно, будет размером сенсорной панели.

  • Это не сильно меняет ситуацию.

  • Если вы посмотрите на исходный код, то увидите, что я просто написал функцию расширения, и она выглядит вот так.

    @Test
    clickTest {
    
    rule.onNodeWithTag(defaultTag)
    // .performTouchInput
    .performIndirectPointerInput
    { click(center) }
    
    }
  • Окей.

  • Это должно дать вам представление о том, какую работу мы проделали, чтобы добавить поддержку сенсорных панелей в умные очки.

  • Но мы делаем эти, я делаю эти упражнения для отслеживания глаз и рук, так что впереди ещё много интересного.

  • Итак, что я хочу, чтобы вы запомнили из моей части?

Краткий обзор и основные выводы

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

  • Поэтому, если вы используете эти детекторы жестов Foundation, все работает без дополнительных настроек.

  • Это первое.

  • Во-вторых, Compose – это основной набор инструментов для создания пользовательского интерфейса. Мы продолжим изучать и поддерживать новые типы ввода как на платформе, так и в наборе инструментов, как я сделал здесь.

  • И, конечно, для тестирования API я хочу сохранить привычный интерфейс и парадигму, чтобы вам было как можно проще перейти на эти новые типы и протестировать их.

  • Спасибо! Надеюсь, эта информация была вам полезна.

Код

Фрагменты кода

Фрагмент кода на Kotlin в видео "Обновления сенсорной панели и жесты несколькими пальцами"
onNodeWithContentDescription("Photo")
    .performTrackpadInput {
        click(center)

        pan(Offset(0f, 100f))

        scale(0.9f)
    }
Фрагмент кода на Kotlin в видео "API design for indirect input and new form factors" (25:08)
InputDevice.ViewBehavior
.getPrimaryDirectionalMotionAxis()
Фрагмент кода на Kotlin в видео "Разработка API для непрямого ввода и новых форм-факторов"
// Glimmer Library
Box(
    modifier =
        Modifier.fillMaxSize()
            .onIndirectPointerGesture(
                enabled = true,
                onSwipeForward = { /* onSwipeForward */ },
                onSwipeBackward = { /* onSwipeBackward */ },
                onClick = { /* onClick */ },
            )
            .focusTarget()
) {
    // App()
Фрагмент кода на Kotlin в разделе "Тестирование ввода с помощью непрямого указателя"
@Test
clickTest {

rule.onNodeWithTag(defaultTag)
.performTouchInput
{ click(center) }

}
Фрагмент кода на Kotlin в разделе "Тестирование непрямого ввода указателя"
@Test
clickTest {

rule.onNodeWithTag(defaultTag)
// .performTouchInput
.performIndirectPointerInput (
IndirectPointerEventPrimaryDirectionalMotionAxis.X,
inputDeviceSize,
)
{ click(center) }
}
Фрагмент кода Kotlin на 27:23 в разделе "Тестирование ввода с помощью косвенного указателя"
@Test
clickTest {

rule.onNodeWithTag(defaultTag)
// .performTouchInput
.performIndirectPointerInput
{ click(center) }

}