Android y ChromeOS proporcionan una variedad de APIs para ayudarte a compilar apps que ofrecen
a los usuarios una experiencia excepcional con la pluma stylus. El
La clase MotionEvent
expone
información sobre la interacción de la pluma stylus con la pantalla, incluida su presión
detección de orientación, inclinación, desplazamiento y palma. Gráficos y movimiento de baja latencia
las bibliotecas de predicción mejoran la renderización en pantalla de la pluma stylus para proporcionar un
natural, similar a la del lápiz y el papel.
MotionEvent
La clase MotionEvent
representa las interacciones de entrada del usuario, como la posición.
y el movimiento de los punteros táctiles en la pantalla. Para entrada de pluma stylus, MotionEvent
también expone datos de presión, orientación, inclinación y desplazamiento.
Datos de eventos
Para acceder a los datos de MotionEvent
, agrega un modificador pointerInput
a los componentes:
@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) }
}
}
},
)
}
Un objeto MotionEvent
proporciona datos relacionados con los siguientes aspectos de una IU
evento:
- Acciones: interacción física con el dispositivo (tocar la pantalla, mover un puntero sobre la superficie de la pantalla, colocar el puntero sobre la pantalla superficie
- Punteros: identificadores de objetos que interactúan con la pantalla (dedo, pluma stylus, mouse
- Eje: tipo de datos (coordenadas x e y, presión, inclinación, orientación, y desplazamiento (distancia)
Acciones
Para implementar la compatibilidad con la pluma stylus, debes comprender qué acción realiza el usuario rendimiento.
MotionEvent
proporciona una amplia variedad de constantes ACTION
que definen el movimiento.
eventos. Algunas de las acciones más importantes de la pluma stylus son las siguientes:
Acción | Descripción |
---|---|
ACTION_DOWN ACTION_POINTER_DOWN |
El puntero entró en contacto con la pantalla. |
ACTION_MOVE | El puntero se está moviendo en la pantalla. |
ACTION_UP ACTION_POINTER_UP |
El puntero ya no está en contacto con la pantalla |
ACTION_CANCEL | Cuándo se debe cancelar el conjunto de movimientos anterior o actual. |
Tu app puede realizar tareas, como iniciar un nuevo trazo, cuando ACTION_DOWN
dibujar el trazo con ACTION_MOVE,
y finalizarlo cuando
Se activó ACTION_UP
.
El conjunto de acciones MotionEvent
de ACTION_DOWN
a ACTION_UP
para un determinado
se denomina conjunto de movimientos.
Punteros
La mayoría de las pantallas son multitáctiles: el sistema asigna un puntero para cada dedo, pluma stylus, mouse u otro objeto apuntando que interactúa con la pantalla. Un puntero índice te permite obtener información sobre los ejes de un puntero específico, como el posición del primer dedo que toca la pantalla o el segundo.
Los índices del puntero van de cero a la cantidad de punteros que muestra
MotionEvent#pointerCount()
menos 1.
Se puede acceder a los valores de los ejes de los punteros con el método getAxisValue(axis,
pointerIndex)
.
Cuando se omite el índice del puntero, el sistema muestra el valor del primer
puntero, puntero cero (0).
Los objetos MotionEvent
contienen información sobre el tipo de puntero que se usa. Tú
puedes obtener el tipo de puntero iterando a través de los índices del puntero y llamando
el
getToolType(pointerIndex)
.
Para obtener más información sobre los punteros, consulta Cómo controlar la función multitáctil gestos.
Entradas de la pluma stylus
Puedes filtrar las entradas de la pluma stylus con
TOOL_TYPE_STYLUS
:
val isStylus = TOOL_TYPE_STYLUS == event.getToolType(pointerIndex)
La pluma stylus también puede informar que se usa como borrador con
TOOL_TYPE_ERASER
:
val isEraser = TOOL_TYPE_ERASER == event.getToolType(pointerIndex)
Datos del eje de la pluma stylus
ACTION_DOWN
y ACTION_MOVE
proporcionan datos del eje sobre la pluma stylus, es decir, x y
coordenadas y, presión, orientación, inclinación y desplazamiento.
Para habilitar el acceso a estos datos, la API de MotionEvent
proporciona
getAxisValue(int)
,
donde el parámetro es cualquiera de los siguientes identificadores del eje:
Axis | Valor que se muestra de getAxisValue() |
---|---|
AXIS_X |
Coordenada X de un evento de movimiento. |
AXIS_Y |
Coordenada Y de un evento de movimiento. |
AXIS_PRESSURE |
Para una pantalla táctil o un panel táctil, la presión que se aplica con un dedo, una pluma stylus u otro puntero. Para un mouse o una bola de seguimiento, 1 si se presiona el botón principal, 0 de lo contrario. |
AXIS_ORIENTATION |
En el caso de una pantalla táctil o un panel táctil, la orientación de un dedo, una pluma stylus u otro puntero en relación con el plano vertical del dispositivo. |
AXIS_TILT |
El ángulo de inclinación de la pluma stylus en radianes. |
AXIS_DISTANCE |
La distancia de la pluma stylus a la pantalla. |
Por ejemplo, MotionEvent.getAxisValue(AXIS_X)
devuelve la coordenada x para la
primer puntero.
Consulta también Cómo controlar la función multitáctil gestos.
Posición
Puedes recuperar las coordenadas x e y de un puntero con las siguientes llamadas:
MotionEvent#getAxisValue(AXIS_X)
oMotionEvent#getX()
MotionEvent#getAxisValue(AXIS_Y)
oMotionEvent#getY()
Presionar
Puedes recuperar la presión del puntero con
MotionEvent#getAxisValue(AXIS_PRESSURE)
o, para el primer puntero,
MotionEvent#getPressure()
El valor de presión de las pantallas táctiles o los paneles táctiles es un valor entre 0 (sin presión) y 1, pero se pueden obtener valores más altos según la pantalla calibración.
Orientación
La orientación indica hacia qué dirección apunta la pluma stylus.
La orientación del puntero se puede recuperar con getAxisValue(AXIS_ORIENTATION)
.
getOrientation()
(para el primer puntero).
En una pluma stylus, la orientación se muestra como un valor de radianes entre 0 y pi (π) en el sentido de las manecillas del reloj o 0 a -pi en sentido antihorario.
La orientación te permite implementar un pincel realista. Por ejemplo, si el representa un pincel plano, el ancho del pincel plano depende del orientación de la pluma stylus.
Inclinación
Esta función mide la inclinación de la pluma stylus en relación con la pantalla.
La inclinación muestra el ángulo positivo de la pluma stylus en radianes, donde cero es perpendicular a la pantalla y π/2 es plano sobre la superficie.
El ángulo de inclinación se puede recuperar con getAxisValue(AXIS_TILT)
(sin combinación de teclas para
el primer puntero).
La inclinación se puede usar para reproducir herramientas realistas de la forma más precisa posible, como imitando el sombreado con un lápiz inclinado.
Colocar el cursor sobre un elemento
La distancia de la pluma stylus a la pantalla se puede obtener con
getAxisValue(AXIS_DISTANCE)
El método devuelve un valor de 0.0 (contacto con
la pantalla) a valores más altos a medida que la pluma stylus se aleja de la pantalla. El botón de desplazamiento del mouse
la distancia entre la pantalla y la punta de la pluma stylus depende del
fabricante de la pantalla y de la pluma stylus. Debido a que las implementaciones pueden
no dependan de valores precisos para funcionalidades críticas de la app.
El desplazamiento de la pluma stylus se puede usar para obtener una vista previa del tamaño del pincel o indicar que se se seleccionará el botón.
Nota: Compose proporciona modificadores que afectan el estado interactivo de los elementos de la IU:
hoverable
: Configura este componente para que se pueda colocar el cursor sobre él con los eventos de entrada y salida del puntero.indication
: Dibuja efectos visuales para este componente cuando se producen interacciones.
Rechazo de la palma, navegación y entradas no deseadas
A veces, las pantallas multitáctiles pueden registrar toques no deseados, por ejemplo, cuando un
el usuario apoya naturalmente la mano en la pantalla para descansar mientras escribe.
El rechazo de la palma es un mecanismo que detecta este comportamiento y te notifica que
se debe cancelar el último conjunto de MotionEvent
.
Por ello, debes mantener un historial de entradas del usuario para que los toques no deseados de la pantalla y las entradas legítimas de los usuarios se volvió a renderizar.
ACTION_CANCEL y FLAG_CANCELED
ACTION_CANCEL
y
FLAG_CANCELED
son
diseñados para informarte que el conjunto de MotionEvent
anterior debe
cancelados del último ACTION_DOWN
para que puedas, por ejemplo, deshacer
trazo de una app de dibujo para un puntero determinado.
ACTION_CANCEL
Se agregó en Android 1.0 (nivel de API 1)
ACTION_CANCEL
indica que se debe cancelar el conjunto anterior de eventos de movimiento.
ACTION_CANCEL
se activa cuando se detecta alguna de las siguientes opciones:
- Gestos de navegación
- Rechazo de la palma
Cuando se activa ACTION_CANCEL
, debes identificar el puntero activo con
getPointerId(getActionIndex())
Luego, quita el trazo creado con ese puntero del historial de entradas y vuelve a renderizar la escena.
FLAG_CANCELED
Se agregó en Android 13 (nivel de API 33)
FLAG_CANCELED
indica que el movimiento del puntero hacia arriba fue un toque no intencional del usuario. La bandera es
normalmente se configura cuando el usuario toca la pantalla accidentalmente, por ejemplo, al agarrar
el dispositivo o colocando la palma de la mano sobre la pantalla.
Puedes acceder al valor de la marca de la siguiente manera:
val cancel = (event.flags and FLAG_CANCELED) == FLAG_CANCELED
Si la marca está establecida, debes deshacer el último conjunto de MotionEvent
del último
ACTION_DOWN
de este puntero.
Al igual que ACTION_CANCEL
, el puntero se puede encontrar con getPointerId(actionIndex)
.
Gestos de pantalla completa, de borde a borde y de navegación
Si una aplicación está en pantalla completa y tiene elementos accionables cerca del perímetro, como el lienzo de una app de dibujo o toma de notas, desliza el dedo desde la parte inferior de la pantalla para mostrar la navegación o mover la app a segundo plano podría generar una toque no deseado en el lienzo.
Para evitar que los gestos activen toques no deseados en tu app, puedes tomar
aprovechar las inserciones y
ACTION_CANCEL
Consulta también el artículo Rechazo de la palma, navegación y entradas no deseadas sección.
Usa el
setSystemBarsBehavior()
método y
BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
de
WindowInsetsController
Para evitar que los gestos de navegación causen eventos táctiles no deseados, haz lo siguiente:
// Configure the behavior of the hidden system bars.
windowInsetsController.systemBarsBehavior =
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
Para obtener más información sobre la administración de inserciones y gestos, consulta:
- Cómo ocultar las barras del sistema para el modo envolvente
- Cómo garantizar la compatibilidad con la navegación por gestos
- Cómo mostrar el contenido de borde a borde en la app
Latencia baja
La latencia es el tiempo que requieren el hardware, el sistema y la aplicación para procesar y procesar la entrada del usuario.
Latencia = hardware y procesamiento de entrada del SO + procesamiento de la app + composición del sistema
- renderización de hardware
Fuente de latencia
- Registro de la pluma stylus con pantalla táctil (hardware): Conexión inalámbrica inicial Cuando la pluma stylus y el SO se comunican para registrarse y sincronizarse
- Tasa de muestreo táctil (hardware): Es la cantidad de veces por segundo que una pantalla táctil. comprueba si un puntero toca la superficie, con un rango de 60 a 1000 Hz.
- Procesamiento de entrada (app): Aplicación de color, efectos gráficos y transformación según la entrada del usuario.
- Renderización gráfica (SO + hardware): Cambio de búfer, procesamiento de hardware.
Gráficos de baja latencia
La biblioteca de gráficos de baja latencia de Jetpack Reduce el tiempo de procesamiento entre la entrada del usuario y la renderización en pantalla.
La biblioteca reduce el tiempo de procesamiento, ya que evita la renderización en varios búferes y Aprovecha una técnica de renderización en el búfer frontal, lo que significa escribir directamente en la pantalla.
Renderización del búfer frontal
El búfer frontal es la memoria que usa la pantalla para la renderización. Es el más cercano las apps pueden llegar a dibujar directamente en la pantalla. La biblioteca de latencia baja permite que las apps se rendericen directamente en el búfer frontal. Esto mejora el rendimiento Evitar el intercambio de búfer, que puede ocurrir en la renderización normal de búfer múltiple o en búfer doble (el caso más común).
Si bien la renderización del búfer frontal es una excelente técnica para renderizar un área pequeña de la pantalla, no está diseñada para usarse para actualizar toda la pantalla. Con la renderización del búfer frontal, la app renderiza contenido en un búfer desde el que está leyendo la pantalla. Como resultado, existe la posibilidad de renderizar artefactos o seccionamientos (ver a continuación).
La biblioteca de latencia baja está disponible a partir de Android 10 (nivel de API 29) y versiones posteriores y en dispositivos ChromeOS con Android 10 (nivel de API 29) y versiones posteriores.
Dependencias
La biblioteca de latencia baja proporciona los componentes para la renderización del búfer frontal
para implementarlos. La biblioteca se agrega como una dependencia en el módulo de la app.
Archivo build.gradle
:
dependencies {
implementation "androidx.graphics:graphics-core:1.0.0-alpha03"
}
Devoluciones de llamada de GLFrontBufferRenderer
La biblioteca de latencia baja incluye
GLFrontBufferRenderer.Callback
que define los siguientes métodos:
La biblioteca de baja latencia no se expresa sobre el tipo de datos que usas con
GLFrontBufferRenderer
Sin embargo, la biblioteca procesa los datos como un flujo de cientos de datos. y diseñe sus datos para optimizar el uso y la asignación de memoria.
Devoluciones de llamada
Para habilitar las devoluciones de llamada de renderización, implementa GLFrontBufferedRenderer.Callback
y
se anulan onDrawFrontBufferedLayer()
y onDrawDoubleBufferedLayer()
.
GLFrontBufferedRenderer
usa las devoluciones de llamada para renderizar tus datos de la manera más
manera optimizada posible.
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.
}
}
Cómo declarar una instancia de GLFrontBufferedRenderer
Para preparar el GLFrontBufferedRenderer
, proporciona SurfaceView
y
las devoluciones de llamada que creaste anteriormente. GLFrontBufferedRenderer
optimiza la renderización
al búfer frontal y doble mediante las devoluciones de llamada:
var glFrontBufferRenderer = GLFrontBufferedRenderer<DATA_TYPE>(surfaceView, callbacks)
Renderización
La renderización del búfer frontal comienza cuando llamas al
renderFrontBufferedLayer()
que activa la devolución de llamada onDrawFrontBufferedLayer()
.
La renderización del búfer doble se reanuda cuando llamas al
commit()
función, que activa la devolución de llamada onDrawMultiDoubleBufferedLayer()
.
En el siguiente ejemplo, el proceso se renderiza en el búfer frontal (rápido
renderización) cuando el usuario comienza a dibujar en la pantalla (ACTION_DOWN
) y se mueve.
el puntero alrededor (ACTION_MOVE
). El proceso se renderiza en el búfer doble
cuando el puntero sale de la superficie de la pantalla (ACTION_UP
).
Puedes usar
requestUnbufferedDispatch()
solicitar que el sistema de entrada no agrupe los eventos de movimiento, sino que entregue
en cuanto estén disponibles:
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()
}
}
Sugerencias y precauciones para la renderización
Puede utilizarse en pequeñas partes de la pantalla, escritura a mano, dibujo y esbozo.
Actualización en pantalla completa, desplazamiento lateral y zoom. Puede provocar seccionamientos.
Seccionamientos
Los seccionamientos se producen cuando la pantalla se actualiza mientras se está cargando el búfer de pantalla modificar al mismo tiempo. Una parte de la pantalla muestra datos nuevos, mientras que otra muestra datos antiguos.
Predicción del movimiento
La predicción de movimiento de Jetpack biblioteca reduce la latencia percibida al estimar la ruta del trazo del usuario y brindar información puntos artificiales al renderizador.
La biblioteca de predicción del movimiento obtiene entradas reales del usuario como objetos MotionEvent
.
Los objetos contienen información sobre las coordenadas x e y, la presión y el tiempo.
que el predictor de movimiento aprovecha para predecir MotionEvent
futuros.
objetos.
Los objetos MotionEvent
pronosticados son solo estimaciones. Los eventos predichos pueden reducir
latencia percibida, pero los datos previstos deben reemplazarse por MotionEvent
reales
una vez recibidos.
La biblioteca de predicción del movimiento está disponible en Android 4.4 (nivel de API 19) y y en dispositivos ChromeOS con Android 9 (nivel de API 28) y versiones posteriores.
Dependencias
La biblioteca de predicción del movimiento proporciona la implementación de la predicción. El
Se agrega la biblioteca como una dependencia en el archivo build.gradle
del módulo de la app:
dependencies {
implementation "androidx.input:input-motionprediction:1.0.0-beta01"
}
Implementación
La biblioteca de predicción del movimiento incluye
MotionEventPredictor
que define los siguientes métodos:
record()
: Almacena objetosMotionEvent
como un registro de las acciones del usuario.predict()
: Muestra unMotionEvent
pronosticado.
Declara una instancia de MotionEventPredictor
.
var motionEventPredictor = MotionEventPredictor.newInstance(view)
Cómo alimentar el predictor con datos
motionEventPredictor.record(motionEvent)
Predecir
when (motionEvent.action) {
MotionEvent.ACTION_MOVE -> {
val predictedMotionEvent = motionEventPredictor?.predict()
if(predictedMotionEvent != null) {
// use predicted MotionEvent to inject a new artificial point
}
}
}
Sugerencias y precauciones para la predicción del movimiento
Quita los puntos de predicción cuando se agregue un nuevo punto pronosticado.
No uses puntos de predicción para la renderización final.
Apps para tomar notas
ChromeOS permite que tu app declare algunas acciones para tomar notas.
Si quieres registrar una app para tomar notas en ChromeOS, consulta Entrada compatibilidad.
Si quieres registrar una app para tomar notas en Android, consulta Cómo crear una toma de notas de la app.
Android 14 (nivel de API 34), presentó el
ACTION_CREATE_NOTE
intent, que permite que tu app inicie una actividad para tomar notas en la cerradura
en la pantalla.
Reconocimiento de tinta digital con ML Kit
Con la tinta digital ML Kit reconocimiento de imágenes, tu aplicación puede reconocer texto escrito a mano en una superficie digital en cientos de idiomas. También puedes clasificar bocetos.
ML Kit proporciona la
Ink.Stroke.Builder
clase para crear objetos Ink
que los modelos de aprendizaje automático pueden procesar
para convertir la escritura a mano en texto.
Además del reconocimiento de escritura a mano, el modelo puede reconocer gestos, como borrar y encerrar en un círculo.
Consulta Tinta digital reconocimiento para obtener más información.