O Android e o ChromeOS oferecem uma variedade de APIs para ajudar você a criar apps que oferecem
aos usuários uma experiência excepcional com a stylus. A classe
MotionEvent
expõe
informações sobre a interação da stylus com a tela, incluindo pressão da stylus,
orientação, inclinação, passagem do cursor e detecção de palma. As bibliotecas de previsão de movimento e gráficos
de baixa latência melhoram a renderização da stylus na tela para fornecer uma
experiência natural, como papel e caneta.
MotionEvent
A classe MotionEvent
representa as interações de entrada do usuário, como a posição
e o movimento dos ponteiros de toque na tela. Para entrada com stylus, o MotionEvent
também expõe dados de pressão, orientação, inclinação e passagem do cursor.
Dados de eventos
Para acessar os dados de MotionEvent
, adicione um modificador pointerInput
aos 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) }
}
}
},
)
}
Um objeto MotionEvent
fornece dados relacionados a estes aspectos de um evento
da interface:
- Ações: interação física com o dispositivo, como tocar na tela, mover um ponteiro sobre a superfície da tela, passar o cursor sobre ela
- Ponteiros: identificadores de objetos que interagem com a tela (dedo, stylus, mouse)
- Eixo: tipo de dados — coordenadas x e y, pressão, inclinação, orientação e passar o cursor (distância)
Ações
Para implementar o suporte à stylus, você precisa entender qual ação o usuário está executando.
O MotionEvent
fornece uma ampla variedade de constantes ACTION
que definem eventos
de movimento. As ações mais importantes para a stylus incluem:
Ação | Descrição |
---|---|
ACTION_DOWN ACTION_POINTER_DOWN |
O ponteiro fez contato com a tela. |
ACTION_MOVE | O ponteiro está se movendo na tela. |
ACTION_UP ACTION_POINTER_UP |
O ponteiro não está mais em contato com a tela |
ACTION_CANCEL | Quando o conjunto de movimentos anterior ou atual precisa ser cancelado. |
O app pode realizar tarefas como iniciar um novo traço quando ACTION_DOWN
acontece, desenhar o traço com ACTION_MOVE,
e finalizá-lo quando
ACTION_UP
for acionado.
O conjunto de ações de MotionEvent
de ACTION_DOWN
a ACTION_UP
para um determinado
ponteiro é chamado de conjunto de movimentos.
Ponteiros
A maioria das telas é multitoque: o sistema atribui um ponteiro para cada dedo, uma stylus, mouse ou outro objeto que interaja com a tela. Um índice permite que você receba informações sobre o eixo de um ponteiro específico, por exemplo, a posição do primeiro ou segundo dedo que toca na tela.
Os índices de ponteiro variam de zero ao número de ponteiros retornados por
MotionEvent#pointerCount()
menos 1.
Os valores dos eixos dos ponteiros podem ser acessados com o método getAxisValue(axis,
pointerIndex)
.
Quando o índice de ponteiro é omitido, o sistema retorna o valor para o primeiro
ponteiro, o ponteiro zero (0).
Os objetos MotionEvent
contêm informações sobre o tipo de ponteiro em uso. É possível
conferir o tipo de ponteiro iterando os índices dele e chamando
o
método
getToolType(pointerIndex)
.
Para saber mais sobre ponteiros, consulte Processar gestos multitoque.
Entradas da stylus
É possível filtrar as entradas da stylus com
TOOL_TYPE_STYLUS
:
val isStylus = TOOL_TYPE_STYLUS == event.getToolType(pointerIndex)
A stylus também pode informar que está sendo usada como borracha com
TOOL_TYPE_ERASER
:
val isEraser = TOOL_TYPE_ERASER == event.getToolType(pointerIndex)
Dados do eixo da stylus
ACTION_DOWN
e ACTION_MOVE
fornecem dados de eixo sobre a stylus, ou seja, coordenadas x e
y, pressão, orientação, inclinação e passagem do cursor.
Para ativar o acesso a esses dados, a API MotionEvent
fornece
getAxisValue(int)
,
em que o parâmetro é qualquer um dos identificadores de eixo abaixo:
Eixo | Valor de retorno de getAxisValue() |
---|---|
AXIS_X |
Coordenada X de um evento de movimento. |
AXIS_Y |
Coordenada Y de um evento de movimento. |
AXIS_PRESSURE |
Para touchscreen ou touchpad, a pressão aplicada por um dedo, uma stylus ou outro ponteiro. Para um mouse ou trackball, 1 se o botão principal for pressionado. 0 caso contrário. |
AXIS_ORIENTATION |
Para touchscreen ou touchpad, a orientação de um dedo, uma stylus ou outro ponteiro em relação ao plano vertical do dispositivo. |
AXIS_TILT |
O ângulo de inclinação da stylus em radianos. |
AXIS_DISTANCE |
A distância entre a stylus e a tela. |
Por exemplo, MotionEvent.getAxisValue(AXIS_X)
retorna a coordenada x para o
primeiro ponteiro.
Consulte também Processar gestos multitoque.
Cargo
Você pode extrair as coordenadas x e y de um ponteiro com estas chamadas:
MotionEvent#getAxisValue(AXIS_X)
ouMotionEvent#getX()
MotionEvent#getAxisValue(AXIS_Y)
ouMotionEvent#getY()
Pressão
Você pode extrair a pressão do ponteiro com
MotionEvent#getAxisValue(AXIS_PRESSURE)
ou, para o primeiro ponteiro,
MotionEvent#getPressure()
.
O valor da pressão para touchscreens ou touchpads é um valor entre 0 (sem pressão) e 1, mas valores mais altos podem ser retornados dependendo da calibragem da tela.
Orientação
A orientação indica para qual direção a stylus está apontando.
A orientação do ponteiro pode ser extraída usando getAxisValue(AXIS_ORIENTATION)
ou
getOrientation()
(para o primeiro ponteiro).
Para uma stylus, a orientação é retornada como um valor radiano entre 0 e pi (π) no sentido horário ou 0 a -pi no sentido anti-horário.
A orientação permite que você implemente um pincel realista. Por exemplo, se a stylus representar um pincel plano, a largura dele dependerá da orientação da stylus.
Inclinação
A inclinação mede a inclinação da stylus em relação à tela.
A inclinação retorna o ângulo positivo da stylus em radianos, em que zero é perpendicular à tela e π/2 é plano na superfície.
O ângulo de inclinação pode ser extraído usando getAxisValue(AXIS_TILT)
(sem atalho para
o primeiro ponteiro).
A inclinação pode ser usada para reproduzir ferramentas reais da forma mais próxima possível, como imitação do sombreamento com um lápis inclinado.
Passar cursor
A distância entre a stylus e a tela pode ser extraída com
getAxisValue(AXIS_DISTANCE)
. O método retorna um valor de 0,0 (contato com a tela) até valores mais altos à medida que a stylus se afasta da tela. A distância
de passagem do cursor entre a tela e a ponta da stylus depende do
fabricante da tela e da stylus. Como as implementações podem
variar, não confie em valores precisos para a funcionalidade essencial do app.
O recurso de passar o cursor com a stylus pode ser usado para visualizar o tamanho do pincel ou indicar que um botão será selecionado.
Observação:o Compose oferece modificadores que afetam o estado interativo dos elementos da interface:
hoverable
: configura o componente para que seja possível passar o cursor usando eventos de entrada e saída do ponteiro.indication
: renderiza efeitos visuais para o componente quando houver interações.
Rejeição da palma da mão, navegação e entradas indesejadas
Às vezes, telas multitoque podem registrar toques indesejados, por exemplo, quando um
usuário apoia naturalmente a mão na tela enquanto escreve à mão.
A rejeição da palma é um mecanismo que detecta esse comportamento e informa que
o último MotionEvent
definido precisa ser cancelado.
Como resultado, é necessário manter um histórico de entradas do usuário para que os toques indesejados possam ser removidos da tela e as entradas do usuário legítimas possam ser renderizadas.
ACTION_CANCEL e FLAG_CANCELED
ACTION_CANCEL
e
FLAG_CANCELED
foram
projetados para informar que o MotionEvent
definido anterior precisa ser
cancelado do último ACTION_DOWN
para que você possa, por exemplo, desfazer o último
traço de um app de desenho para um determinado ponteiro.
ACTION_CANCEL
Adicionado no Android 1.0 (nível 1 da API)
ACTION_CANCEL
indica que o conjunto anterior de eventos de movimento precisa ser cancelado.
ACTION_CANCEL
é acionado quando uma destas situações é detectada:
- Gestos de navegação
- Rejeição da palma da mão
Quando ACTION_CANCEL
for acionado, identifique o ponteiro ativo com
getPointerId(getActionIndex())
. Em seguida, remova o traço criado com esse ponteiro do histórico de entrada e renderize novamente a cena.
FLAG_CANCELED
Adicionado no Android 13 (nível 33 da API)
FLAG_CANCELED
indica que o ponteiro subir foi um toque do usuário não intencional. A sinalização normalmente é
definida quando o usuário toca acidentalmente na tela, por exemplo, segurando
o dispositivo ou colocando a palma da mão na tela.
Acesse o valor da flag desta maneira:
val cancel = (event.flags and FLAG_CANCELED) == FLAG_CANCELED
Se a flag estiver definida, será necessário desfazer o último conjunto de MotionEvent
do último
ACTION_DOWN
desse ponteiro.
Como ACTION_CANCEL
, o ponteiro pode ser encontrado com getPointerId(actionIndex)
.
Gestos de navegação em tela cheia, de ponta a ponta e de ponta a ponta
Se um app estiver em tela cheia e tiver elementos acionáveis próximos à borda, como a tela de um app de desenho ou anotação, deslizar de baixo para cima para mostrar a navegação ou mover o app para o segundo plano pode resultar em um toque indesejado na tela.
Para evitar que os gestos acionem toques indesejados no app, você pode aproveitar
os encartes e
ACTION_CANCEL
.
Consulte também a seção Rejeição da palma da mão, navegação e entradas indesejadas.
Use o método
setSystemBarsBehavior()
e
BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
de
WindowInsetsController
para evitar que os gestos de navegação causem eventos de toque indesejados:
// Configure the behavior of the hidden system bars.
windowInsetsController.systemBarsBehavior =
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
Para saber mais sobre o inset e o gerenciamento de gestos, consulte:
- Ocultar barras de sistema para o modo imersivo
- Garantir a compatibilidade com a navegação por gestos
- Mostrar conteúdo de ponta a ponta no app
Baixa latência
Latência é o tempo exigido pelo hardware, sistema e aplicativo para processar e renderizar a entrada do usuário.
Latência = processamento de entrada de hardware e de SO + processamento de apps + composição de sistemas
- renderização de hardware
Origem da latência
- Registro da stylus com touchscreen (hardware): conexão sem fio inicial quando a stylus e o SO se comunicam para serem registrados e sincronizados.
- Taxa de amostragem de toque (hardware): o número de vezes por segundo em que uma tela touchscreen verifica se um ponteiro está tocando a superfície, variando de 60 a 1.000 Hz.
- Processamento de entrada (app): aplicação de cores, efeitos gráficos e transformação na entrada do usuário.
- Renderização gráfica (SO + hardware): troca de buffer, processamento de hardware.
Gráficos de baixa latência
A biblioteca de gráficos de baixa latência do Jetpack reduz o tempo de processamento entre a entrada do usuário e a renderização na tela.
A biblioteca reduz o tempo de processamento ao evitar a renderização de vários buffers e aproveitar uma técnica de renderização do buffer frontal, o que significa escrever diretamente na tela.
Renderização do buffer frontal
O buffer frontal é a memória que a tela usa para renderização. Ele é a forma mais rápida de apps desenharem diretamente na tela. A biblioteca de baixa latência permite que os apps sejam renderizados diretamente no buffer frontal. Isso melhora o desempenho evitando a troca do buffer, o que pode acontecer na renderização normal de vários buffers ou na renderização de buffer duplo (o caso mais comum).
Embora a renderização do buffer frontal seja uma ótima técnica para renderizar uma pequena área da tela, ela não foi projetada para ser usada para atualizar toda a tela. Com a renderização do buffer frontal, o app está renderizando o conteúdo em um buffer do qual a tela está lendo. Como resultado, há a possibilidade de renderizar artefatos ou romper (confira abaixo).
A biblioteca de baixa latência está disponível no Android 10 (nível 29 da API) e versões mais recentes e em dispositivos ChromeOS com o Android 10 (nível 29 da API) e versões mais recentes.
Dependências
A biblioteca de baixa latência fornece os componentes para a implementação da renderização do
buffer frontal. A biblioteca é adicionada como uma dependência no arquivo
build.gradle
do módulo do app:
dependencies {
implementation "androidx.graphics:graphics-core:1.0.0-alpha03"
}
Callbacks de GLFrontBufferRenderer
A biblioteca de baixa latência inclui a interface
GLFrontBufferRenderer.Callback
, que define os seguintes métodos:
A biblioteca de baixa latência não opina sobre o tipo de dados que você usa com
GLFrontBufferRenderer
.
No entanto, a biblioteca processa os dados como um fluxo de centenas de pontos de dados. Por isso, projete seus dados para otimizar o uso e a alocação de memória.
Callbacks
Para ativar os callbacks de renderização, implemente GLFrontBufferedRenderer.Callback
e
substitua onDrawFrontBufferedLayer()
e onDrawDoubleBufferedLayer()
.
O GLFrontBufferedRenderer
usa os callbacks para renderizar seus dados da maneira
mais otimizada possível.
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.
}
}
Declarar uma instância do GLFrontBufferedRenderer
Prepare o GLFrontBufferedRenderer
fornecendo o SurfaceView
e
os callbacks criados anteriormente. GLFrontBufferedRenderer
otimiza a renderização
para o buffer duplo e frontal usando seus callbacks:
var glFrontBufferRenderer = GLFrontBufferedRenderer<DATA_TYPE>(surfaceView, callbacks)
Renderização
A renderização do buffer frontal começa quando você chama o método
renderFrontBufferedLayer()
, que aciona o callback onDrawFrontBufferedLayer()
.
A renderização do buffer duplo é retomada quando você chama a função
commit()
, que aciona o callback onDrawMultiDoubleBufferedLayer()
.
No exemplo a seguir, o processo é renderizado no buffer frontal (renderização
rápida) quando o usuário começa a desenhar na tela (ACTION_DOWN
) e move
o ponteiro (ACTION_MOVE
). O processo é renderizado no buffer duplo
quando o ponteiro sai da superfície da tela (ACTION_UP
).
Você pode usar
requestUnbufferedDispatch()
para pedir que o sistema de entrada não agrupe eventos de movimento em lote, mas os entregue
assim que eles estiverem disponíveis:
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()
}
}
O que fazer e o que não fazer
Renderize apenas pequenas partes da tela, escrita à mão, desenhos, esboços.
Não renderize atualizações em tela cheia, com efeito panorâmico ou zoom. Isso pode causar rupturas.
Ruptura
A ruptura acontece quando a tela é atualizada enquanto o buffer está sendo modificado ao mesmo tempo. Uma parte da tela mostra dados novos, enquanto outra mostra dados antigos.
Previsão de movimento
A biblioteca de previsão de movimento do Jetpack reduz a latência percebida ao estimar o caminho do traço do usuário e fornecer pontos artificiais temporários ao renderizador.
A biblioteca de previsão de movimento recebe entradas reais do usuário como objetos MotionEvent
.
Os objetos contêm informações sobre coordenadas X e Y, pressão e tempo, que são usadas pelo preditor de movimento para prever futuros objetos MotionEvent
.
Os objetos MotionEvent
previstos são apenas estimativas. Os eventos previstos podem reduzir a latência percebida, mas os dados previstos precisam ser substituídos por dados MotionEvent
reais depois de recebidos.
A biblioteca de previsão de movimento está disponível no Android 4.4 (nível 19 da API) e versões mais recentes e em dispositivos ChromeOS com o Android 9 (nível 28 da API) e versões mais recentes.
Dependências
A biblioteca de previsão de movimento fornece a implementação da previsão. A
biblioteca é adicionada como uma dependência no arquivo build.gradle
do módulo do app:
dependencies {
implementation "androidx.input:input-motionprediction:1.0.0-beta01"
}
Implementação
A biblioteca de previsão de movimento inclui a interface MotionEventPredictor
, que define os seguintes métodos:
record()
: armazena objetosMotionEvent
como um registro das ações do usuário.predict()
: retorna umMotionEvent
previsto.
Declare uma instância de MotionEventPredictor
.
var motionEventPredictor = MotionEventPredictor.newInstance(view)
Alimentar o previsor com dados
motionEventPredictor.record(motionEvent)
Previsão
when (motionEvent.action) {
MotionEvent.ACTION_MOVE -> {
val predictedMotionEvent = motionEventPredictor?.predict()
if(predictedMotionEvent != null) {
// use predicted MotionEvent to inject a new artificial point
}
}
}
O que fazer e o que não fazer na previsão de movimentos
Remova os pontos de previsão quando um novo ponto previsto for adicionado.
Não use pontos de previsão para a renderização final.
Apps de anotações
O ChromeOS permite que o app declare algumas ações de anotação.
Para registrar um app como sendo de anotações no ChromeOS, consulte Compatibilidade de entrada.
Para registrar um app como sendo de anotações no Android, consulte Criar um app de anotações.
O Android 14 (nível 34 da API) introduziu a intent
ACTION_CREATE_NOTE
,
que permite que o app inicie uma atividade de anotação na tela
de bloqueio.
Reconhecimento de tinta digital com o Kit de ML
Com o reconhecimento de tinta digital do Kit de ML, seu app pode reconhecer texto escrito à mão em uma plataforma digital em centenas de idiomas. Você também pode classificar os esboços.
O Kit de ML fornece a classe
Ink.Stroke.Builder
para criar objetos Ink
que podem ser processados por modelos de machine learning
para converter a escrita à mão em texto.
Além do reconhecimento de escrita manual, o modelo é capaz de reconhecer gestos, como excluir e círculo.
Consulte Reconhecimento de tinta digital para saber mais.