Android и ChromeOS предоставляют различные API-интерфейсы, которые помогут вам создавать приложения, предлагающие пользователям исключительные возможности использования стилуса. Класс MotionEvent
предоставляет информацию о взаимодействии стилуса с экраном, включая давление стилуса, ориентацию, наклон, наведение и обнаружение ладони. Библиотеки графики с малой задержкой и прогнозирования движения улучшают рендеринг на экране с помощью стилуса, обеспечивая естественное взаимодействие с ручкой и бумагой.
MotionEvent
Класс MotionEvent
представляет взаимодействия пользователя с вводом данных, такие как положение и перемещение сенсорных указателей на экране. Для ввода стилусом MotionEvent
также предоставляет данные о давлении, ориентации, наклоне и наведении.
Данные о событии
如需访问 MotionEvent
数据,请为组件添加 pointerInput
修饰符:
@Composable
fun Greeting() {
Text(
text = "Hello, Android!", textAlign = TextAlign.Center, style = TextStyle(fontSize = 5.em),
modifier = Modifier
.pointerInput(Unit) {
awaitEachGesture {
while (true) {
val event = awaitPointerEvent()
event.changes.forEach { println(it) }
}
}
},
)
}
Объект MotionEvent
предоставляет данные, относящиеся к следующим аспектам события пользовательского интерфейса:
- Действия: Физическое взаимодействие с устройством — прикосновение к экрану, перемещение указателя по поверхности экрана, наведение указателя на поверхность экрана.
- Указатели: идентификаторы объектов, взаимодействующих с экраном: палец, стилус, мышь.
- Ось: тип данных — координаты x и y, давление, наклон, ориентация и наведение (расстояние).
Действия
Чтобы реализовать поддержку стилуса, нужно понимать, какое действие выполняет пользователь.
MotionEvent
предоставляет широкий спектр констант ACTION
, определяющих события движения. К наиболее важным действиям со стилусом относятся следующие:
Действие | Описание |
---|---|
ACTION_DOWN ACTION_POINTER_DOWN | Указатель коснулся экрана. |
ACTION_MOVE | Указатель перемещается по экрану. |
ACTION_UP ACTION_POINTER_UP | Указатель больше не касается экрана |
ACTION_CANCEL | Когда предыдущий или текущий набор движений должен быть отменен. |
Ваше приложение может выполнять такие задачи, как начало новой обводки при срабатывании ACTION_DOWN
, рисование обводки с помощью ACTION_MOVE,
и завершение обводки при срабатывании ACTION_UP
.
Набор действий MotionEvent
от ACTION_DOWN
до ACTION_UP
для данного указателя называется набором движений.
Указатели
Большинство экранов являются мультитач: система назначает указатель для каждого пальца, стилуса, мыши или другого указывающего объекта, взаимодействующего с экраном. Индекс указателя позволяет получить информацию об оси для определенного указателя, например положение первого или второго пальца, касающегося экрана.
Индексы указателей варьируются от нуля до количества указателей, возвращаемых функцией MotionEvent#pointerCount()
минус 1.
Доступ к значениям осей указателей можно получить с помощью метода getAxisValue(axis, pointerIndex)
. Если индекс указателя опущен, система возвращает значение первого указателя, нулевой указатель (0).
Объекты MotionEvent
содержат информацию о типе используемого указателя. Вы можете получить тип указателя, перебирая индексы указателей и вызывая метод getToolType(pointerIndex)
.
Дополнительные сведения об указателях см. в разделе «Обработка жестов мультитач» .
Стилусные входы
Вы можете фильтровать ввод стилуса с помощью TOOL_TYPE_STYLUS
:
val isStylus = TOOL_TYPE_STYLUS == event.getToolType(pointerIndex)
Стилус также может сообщить, что он используется как ластик с помощью TOOL_TYPE_ERASER
:
val isEraser = TOOL_TYPE_ERASER == event.getToolType(pointerIndex)
Данные оси щупа
ACTION_DOWN
и ACTION_MOVE
предоставляют данные оси стилуса, а именно координаты x и y, давление, ориентацию, наклон и наведение.
Чтобы обеспечить доступ к этим данным, API MotionEvent
предоставляет getAxisValue(int)
, где параметром является любой из следующих идентификаторов оси:
Ось | Возвращаемое значение getAxisValue() |
---|---|
AXIS_X | Координата X события движения. |
AXIS_Y | Координата Y события движения. |
AXIS_PRESSURE | Для сенсорного экрана или сенсорной панели — давление, оказываемое пальцем, стилусом или другим указателем. Для мыши или трекбола — 1, если нажата основная кнопка, и 0 — в противном случае. |
AXIS_ORIENTATION | Для сенсорного экрана или сенсорной панели — ориентация пальца, стилуса или другого указателя относительно вертикальной плоскости устройства. |
AXIS_TILT | Угол наклона стилуса в радианах. |
AXIS_DISTANCE | Расстояние стилуса от экрана. |
Например, MotionEvent.getAxisValue(AXIS_X)
возвращает координату x для первого указателя.
См. также раздел «Обработка мультитач-жестов» .
Позиция
Вы можете получить координаты x и y указателя с помощью следующих вызовов:
-
MotionEvent#getAxisValue(AXIS_X)
илиMotionEvent#getX()
-
MotionEvent#getAxisValue(AXIS_Y)
илиMotionEvent#getY()
![Рисование стилусом на экране с сопоставлением координат x и y.](https://developer.android.google.cn/static/images/develop/ui/compose/touch-input/stylus-input/advanced-stylus/stylus_x_y_coordinates.png?authuser=3&hl=ru)
Давление
Вы можете получить давление указателя с помощью MotionEvent#getAxisValue(AXIS_PRESSURE)
или, для первого указателя, MotionEvent#getPressure()
.
Значение давления для сенсорных экранов или сенсорных панелей — это значение от 0 (нет давления) до 1, но в зависимости от калибровки экрана могут быть возвращены более высокие значения.
![Ход иглы, представляющий собой непрерывное давление от низкого до высокого. Штрих слева узкий и слабый, что указывает на низкое давление. Штрих становится шире и темнее слева направо, пока не станет самым широким и темным в крайнем правом углу, что указывает на максимальное давление.](https://developer.android.google.cn/static/images/develop/ui/compose/touch-input/stylus-input/advanced-stylus/stylus_pressure.png?authuser=3&hl=ru)
Ориентация
Ориентация указывает, в каком направлении указывает стилус.
Ориентацию указателя можно получить с помощью getAxisValue(AXIS_ORIENTATION)
или getOrientation()
(для первого указателя).
Для стилуса ориентация возвращается в виде значения в радианах от 0 до пи (𝛑) по часовой стрелке или от 0 до -пи против часовой стрелки.
Ориентация позволяет реализовать реальную кисть. Например, если стилус представляет собой плоскую кисть, ширина плоской кисти зависит от ориентации стилуса.
![](https://developer.android.google.cn/static/images/develop/ui/compose/touch-input/stylus-input/advanced-stylus/stylus_orientation.png?authuser=3&hl=ru)
Наклон
Tilt измеряет наклон стилуса относительно экрана.
Наклон возвращает положительный угол стилуса в радианах, где ноль перпендикулярен экрану, а 𝛑/2 лежит ровно на поверхности.
Угол наклона можно получить с помощью getAxisValue(AXIS_TILT)
(без ярлыка для первого указателя).
Наклон можно использовать для максимально точного воспроизведения реальных инструментов, таких как имитация штриховки наклоненным карандашом.
![Стилус наклонен примерно на 40 градусов от поверхности экрана.](https://developer.android.google.cn/static/images/develop/ui/compose/touch-input/stylus-input/advanced-stylus/stylus_tilt.png?authuser=3&hl=ru)
Наведите указатель мыши
Расстояние стилуса от экрана можно получить с помощью getAxisValue(AXIS_DISTANCE)
. Метод возвращает значение от 0,0 (контакт с экраном) до более высоких значений по мере удаления стилуса от экрана. Расстояние наведения между экраном и кончиком (точкой) стилуса зависит от производителя как экрана, так и стилуса. Поскольку реализации могут различаться, не полагайтесь на точные значения критически важных для приложения функций.
Наведение стилуса можно использовать для предварительного просмотра размера кисти или для указания того, что кнопка будет выбрана.
![](https://developer.android.google.cn/static/images/develop/ui/compose/touch-input/stylus-input/advanced-stylus/stylus_hover.png?authuser=3&hl=ru)
Примечание. Compose предоставляет модификаторы, которые влияют на интерактивное состояние элементов пользовательского интерфейса:
-
hoverable
: Настройте компонент для наведения курсора с помощью событий входа и выхода указателя. -
indication
: рисует визуальные эффекты для этого компонента при возникновении взаимодействия.
Отказ от ладони, навигация и нежелательный ввод
Иногда мультитач-экраны могут регистрировать нежелательные прикосновения, например, когда пользователь естественным образом кладет руку на экран для поддержки во время рукописного ввода. Отклонение ладони — это механизм, который обнаруживает такое поведение и уведомляет вас о том, что последний набор MotionEvent
должен быть отменен.
В результате вы должны вести историю вводимых пользователем данных, чтобы можно было удалить нежелательные касания с экрана и повторно отобразить законные вводимые пользователем данные.
ACTION_CANCEL и FLAG_CANCELED
ACTION_CANCEL
и FLAG_CANCELED
предназначены для информирования вас о том, что предыдущий набор MotionEvent
должен быть отменен с момента последнего ACTION_DOWN
, поэтому вы можете, например, отменить последний штрих для приложения для рисования для данного указателя.
ACTION_CANCEL
Добавлено в Android 1.0 (уровень API 1).
ACTION_CANCEL
указывает, что предыдущий набор событий движения должен быть отменен.
ACTION_CANCEL
срабатывает при обнаружении любого из следующих событий:
- Жесты навигации
- Отказ от пальмы
Когда срабатывает ACTION_CANCEL
, вы должны идентифицировать активный указатель с помощью getPointerId ( getActionIndex() )
. Затем удалите обводку, созданную этим указателем, из истории ввода и повторно отрисуйте сцену.
ФЛАГ_ОТМЕНЕН
Добавлено в Android 13 (уровень API 33).
FLAG_CANCELED
указывает, что подъем указателя вверх был непреднамеренным прикосновением пользователя. Флаг обычно устанавливается, когда пользователь случайно касается экрана, например, сжимая устройство или помещая ладонь на экран.
Вы получаете доступ к значению флага следующим образом:
val cancel = (event.flags and FLAG_CANCELED) == FLAG_CANCELED
Если флаг установлен, вам необходимо отменить последний установленный MotionEvent
, начиная с последнего ACTION_DOWN
из этого указателя.
Как и ACTION_CANCEL
, указатель можно найти с помощью getPointerId(actionIndex)
.
MotionEvent
. Касание ладонью отменяется, и изображение на дисплее отображается заново. Полноэкранный режим, от края до края и жесты навигации
Если приложение полноэкранное и имеет элементы действия по краям, например холст приложения для рисования или создания заметок, смахивание от нижней части экрана для отображения навигации или перемещение приложения на фон может привести к нежелательному результату. коснуться холста.
Чтобы жесты не вызывали нежелательные касания в вашем приложении, вы можете воспользоваться вставками и ACTION_CANCEL
.
См. также раздел «Отклонение Palm, навигация и нежелательные вводы» .
Используйте метод setSystemBarsBehavior()
и BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
WindowInsetsController
, чтобы предотвратить возникновение нежелательных событий касания жестами навигации:
// Configure the behavior of the hidden system bars.
windowInsetsController.systemBarsBehavior =
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
Дополнительные сведения об управлении вставками и жестами см. в разделах:
- Скрыть системные панели для режима погружения
- Обеспечьте совместимость с навигацией с помощью жестов.
- Отображение контента от края до края в вашем приложении
Низкая задержка
Задержка — это время, необходимое оборудованию, системе и приложению для обработки и отображения пользовательского ввода.
Задержка = аппаратная обработка ввода и ОС + обработка приложения + компоновка системы.
- аппаратный рендеринг
![Задержка приводит к тому, что визуализируемый штрих отстает от положения стилуса. Разрыв между отображаемым штрихом и положением стилуса представляет собой задержку.](https://developer.android.google.cn/static/images/develop/ui/compose/touch-input/stylus-input/advanced-stylus/stylus_latency.png?authuser=3&hl=ru)
Источник задержки
- Регистрация стилуса на сенсорном экране (аппаратное обеспечение): первоначальное беспроводное соединение, когда стилус и ОС обмениваются данными для регистрации и синхронизации.
- Частота дискретизации касания (аппаратное обеспечение): количество раз в секунду сенсорный экран проверяет, касается ли указатель поверхности, в диапазоне от 60 до 1000 Гц.
- Обработка ввода (приложение): применение цвета, графических эффектов и преобразований к пользовательскому вводу.
- Графический рендеринг (ОС + аппаратное обеспечение): подкачка буфера, аппаратная обработка.
Графика с низкой задержкой
Графическая библиотека Jetpack с малой задержкой сокращает время обработки между пользовательским вводом и рендерингом на экране.
Библиотека сокращает время обработки, избегая многобуферного рендеринга и используя технику рендеринга с передним буфером, что означает запись непосредственно на экран.
Рендеринг в переднем буфере
Передний буфер — это память, которую экран использует для рендеринга. Это самое близкое приложение к рисованию прямо на экране. Библиотека с низкой задержкой позволяет приложениям выполнять рендеринг непосредственно в передний буфер. Это повышает производительность за счет предотвращения замены буферов, что может произойти при обычном рендеринге с несколькими буферами или рендеринге с двойным буфером (наиболее распространенный случай).
![Приложение записывает в буфер экрана и читает из буфера экрана.](https://developer.android.google.cn/static/images/develop/ui/compose/touch-input/stylus-input/advanced-stylus/front_buffer_rendering.png?authuser=3&hl=ru)
![Приложение записывает в мультибуфер, который заменяется экранным буфером. Приложение читает из экранного буфера.](https://developer.android.google.cn/static/images/develop/ui/compose/touch-input/stylus-input/advanced-stylus/multi-buffer_rendering.png?authuser=3&hl=ru)
Хотя рендеринг переднего буфера — отличный метод для рендеринга небольшой области экрана, он не предназначен для обновления всего экрана. При рендеринге в переднем буфере приложение отображает контент в буфер, из которого считывается дисплей. В результате возникает вероятность появления артефактов рендеринга или разрывов (см. ниже).
Библиотека с низкой задержкой доступна в Android 10 (уровень API 29) и выше, а также на устройствах ChromeOS под управлением Android 10 (уровень API 29) и выше.
Зависимости
Библиотека с низкой задержкой предоставляет компоненты для реализации рендеринга в переднем буфере. Библиотека добавляется как зависимость в файл build.gradle
модуля приложения:
dependencies {
implementation "androidx.graphics:graphics-core:1.0.0-alpha03"
}
Обратные вызовы GLFrontBufferRenderer
Библиотека с низкой задержкой включает интерфейс GLFrontBufferRenderer.Callback
, который определяет следующие методы:
Библиотека с низкой задержкой не имеет никакого мнения о типе данных, которые вы используете с GLFrontBufferRenderer
.
Однако библиотека обрабатывает данные как поток сотен точек данных; и поэтому спроектируйте свои данные так, чтобы оптимизировать использование и распределение памяти.
Обратные вызовы
Чтобы включить обратные вызовы рендеринга, реализуйте GLFrontBufferedRenderer.Callback
и переопределите onDrawFrontBufferedLayer()
и onDrawDoubleBufferedLayer()
. GLFrontBufferedRenderer
использует обратные вызовы для наиболее оптимизированного отображения ваших данных.
val callback = object: GLFrontBufferedRenderer.Callback<DATA_TYPE> {
override fun onDrawFrontBufferedLayer(
eglManager: EGLManager,
bufferInfo: BufferInfo,
transform: FloatArray,
param: DATA_TYPE
) {
// OpenGL for front buffer, short, affecting small area of the screen.
}
override fun onDrawMultiDoubleBufferedLayer(
eglManager: EGLManager,
bufferInfo: BufferInfo,
transform: FloatArray,
params: Collection<DATA_TYPE>
) {
// OpenGL full scene rendering.
}
}
Объявите экземпляр GLFrontBufferedRenderer
Подготовьте GLFrontBufferedRenderer
, предоставив SurfaceView
и обратные вызовы, которые вы создали ранее. GLFrontBufferedRenderer
оптимизирует рендеринг в передний и двойной буфер, используя ваши обратные вызовы:
var glFrontBufferRenderer = GLFrontBufferedRenderer<DATA_TYPE>(surfaceView, callbacks)
Рендеринг
Рендеринг в переднем буфере начинается при вызове метода renderFrontBufferedLayer()
, который запускает обратный вызов onDrawFrontBufferedLayer()
.
Рендеринг в двойном буфере возобновляется при вызове функции commit()
, которая запускает обратный вызов onDrawMultiDoubleBufferedLayer()
.
В следующем примере процесс выполняет рендеринг в передний буфер (быстрый рендеринг), когда пользователь начинает рисовать на экране ( ACTION_DOWN
) и перемещает указатель ( ACTION_MOVE
). Процесс рендерится в двойной буфер, когда указатель покидает поверхность экрана ( ACTION_UP
).
Вы можете использовать requestUnbufferedDispatch()
, чтобы попросить систему ввода не группировать события движения, а доставлять их, как только они станут доступны:
when (motionEvent.action) {
MotionEvent.ACTION_DOWN -> {
// Deliver input events as soon as they arrive.
view.requestUnbufferedDispatch(motionEvent)
// Pointer is in contact with the screen.
glFrontBufferRenderer.renderFrontBufferedLayer(DATA_TYPE)
}
MotionEvent.ACTION_MOVE -> {
// Pointer is moving.
glFrontBufferRenderer.renderFrontBufferedLayer(DATA_TYPE)
}
MotionEvent.ACTION_UP -> {
// Pointer is not in contact in the screen.
glFrontBufferRenderer.commit()
}
MotionEvent.CANCEL -> {
// Cancel front buffer; remove last motion set from the screen.
glFrontBufferRenderer.cancel()
}
}
Рендеринг, что можно и чего нельзя
Небольшие порции экрана, рукописный ввод, рисование, зарисовка.
Полноэкранное обновление, панорамирование, масштабирование. Может привести к разрыву.
разрывая
Разрыв происходит, когда экран обновляется, в то время как буфер экрана изменяется. В одной части экрана отображаются новые данные, в другой — старые данные.
![Верхняя и нижняя части изображения Android смещены из-за разрывов при обновлении экрана.](https://developer.android.google.cn/static/images/develop/ui/compose/touch-input/stylus-input/advanced-stylus/tearing.png?authuser=3&hl=ru)
Прогнозирование движения
Библиотека прогнозирования движения Jetpack уменьшает воспринимаемую задержку, оценивая траекторию удара пользователя и предоставляя средству рендеринга временные искусственные точки.
Библиотека прогнозирования движения получает реальные данные пользователя в виде объектов MotionEvent
. Объекты содержат информацию о координатах X и Y, давлении и времени, которые используются предсказателем движения для прогнозирования будущих объектов MotionEvent
.
Прогнозируемые объекты MotionEvent
являются лишь оценками. Прогнозируемые события могут уменьшить воспринимаемую задержку, но прогнозируемые данные должны быть заменены фактическими данными MotionEvent
после их получения.
Библиотека прогнозирования движения доступна в Android 4.4 (уровень API 19) и выше, а также на устройствах ChromeOS под управлением Android 9 (уровень API 28) и выше.
![Задержка приводит к тому, что визуализируемый штрих отстает от положения стилуса. Промежуток между штрихом и стилусом заполняется точками прогнозирования. Остающийся разрыв — это воспринимаемая задержка.](https://developer.android.google.cn/static/images/develop/ui/compose/touch-input/stylus-input/advanced-stylus/stylus_reduced_latency.png?authuser=3&hl=ru)
Зависимости
Библиотека прогнозирования движения обеспечивает реализацию прогнозирования. Библиотека добавляется как зависимость в файл build.gradle
модуля приложения:
dependencies {
implementation "androidx.input:input-motionprediction:1.0.0-beta01"
}
Выполнение
Библиотека прогнозирования движения включает интерфейс MotionEventPredictor
, который определяет следующие методы:
-
record()
: сохраняет объектыMotionEvent
как запись действий пользователя. -
predict()
: возвращает прогнозируемоеMotionEvent
Объявите экземпляр MotionEventPredictor
var motionEventPredictor = MotionEventPredictor.newInstance(view)
Накормите предиктор данными
motionEventPredictor.record(motionEvent)
Предсказывать
when (motionEvent.action) {
MotionEvent.ACTION_MOVE -> {
val predictedMotionEvent = motionEventPredictor?.predict()
if(predictedMotionEvent != null) {
// use predicted MotionEvent to inject a new artificial point
}
}
}
Прогнозирование движения, что можно и чего нельзя делать
Удалите точки прогнозирования при добавлении новой прогнозируемой точки.
Не используйте точки прогнозирования для окончательного рендеринга.
Приложения для заметок
ChromeOS позволяет вашему приложению объявлять некоторые действия по созданию заметок.
Чтобы зарегистрировать приложение в качестве приложения для создания заметок в ChromeOS, см. раздел Совместимость ввода .
Чтобы зарегистрировать приложение для создания заметок на Android, см. раздел Создание приложения для создания заметок .
В Android 14 (уровень API 34) представлено намерение ACTION_CREATE_NOTE
, которое позволяет вашему приложению начинать создание заметок на экране блокировки.
Распознавание цифровых чернил с помощью ML Kit
Благодаря распознаванию цифровых рукописных знаков ML Kit ваше приложение может распознавать рукописный текст на цифровой поверхности на сотнях языков. Вы также можете классифицировать эскизы.
ML Kit предоставляет класс Ink.Stroke.Builder
для создания объектов Ink
, которые можно обрабатывать с помощью моделей машинного обучения для преобразования рукописного текста в текст.
Помимо распознавания рукописного ввода, модель умеет распознавать жесты , такие как удаление и обведение.
Дополнительную информацию см. в разделе Распознавание цифровых рукописей .