Поскольку новые типы устройств выходят за рамки сенсорного экрана, система ввода Compose была усовершенствована, чтобы соответствовать требованиям. Многоуровневая архитектура позволяет поддерживать как сложные жесты на сенсорной панели, так и новые аппаратные устройства ввода.
Мы разберем архитектуру и рассмотрим примеры кода, чтобы проследить жизненный цикл события ввода от уровня платформы ОС до детекторов жестов Compose и компонентов пользовательского интерфейса. Вы узнаете, как отслеживать события во всем стеке, применять ключевые архитектурные шаблоны для новых типов ввода и уверенно отлаживать сложные взаимодействия жестов в своих приложениях.
performTrackpadInput. Эти API предоставляют привычные тестовые поверхности для проверки того, что приложения правильно обрабатывают устройства без сенсорного экрана.Расшифровка
Здравствуйте!
Надеемся, что вам нравится Саммит для разработчиков 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?
В 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 я хочу сохранить привычный интерфейс и парадигму, чтобы вам было как можно проще перейти на эти новые типы и протестировать их.
Спасибо! Надеюсь, эта информация была вам полезна.
onNodeWithContentDescription("Photo")
.performTrackpadInput {
click(center)
pan(Offset(0f, 100f))
scale(0.9f)
}InputDevice.ViewBehavior
.getPrimaryDirectionalMotionAxis()// Glimmer Library
Box(
modifier =
Modifier.fillMaxSize()
.onIndirectPointerGesture(
enabled = true,
onSwipeForward = { /* onSwipeForward */ },
onSwipeBackward = { /* onSwipeBackward */ },
onClick = { /* onClick */ },
)
.focusTarget()
) {
// App()@Test
clickTest {
rule.onNodeWithTag(defaultTag)
.performTouchInput
{ click(center) }
}@Test
clickTest {
rule.onNodeWithTag(defaultTag)
// .performTouchInput
.performIndirectPointerInput (
IndirectPointerEventPrimaryDirectionalMotionAxis.X,
inputDeviceSize,
)
{ click(center) }
}@Test
clickTest {
rule.onNodeWithTag(defaultTag)
// .performTouchInput
.performIndirectPointerInput
{ click(center) }
}