O Android e o ChromeOS oferecem uma variedade de APIs para ajudar você a criar apps que ofereçam
uma experiência excepcional com a stylus. O
Exposição da classe MotionEvent
informações sobre a interação da stylus com a tela, incluindo a pressão da stylus;
orientação, inclinação, passagem do cursor e detecção de palma. Gráficos e movimento de baixa latência
as bibliotecas de previsão melhoram a renderização na tela da stylus para fornecer uma
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, 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 aos seguintes aspectos de uma interface.
evento:
- Ações: interação física com o dispositivo, como tocar na tela mover um ponteiro sobre a superfície da tela, passar o ponteiro sobre a tela superfície
- 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á realizando desempenho.
O MotionEvent
fornece uma ampla variedade de constantes ACTION
que definem o movimento.
eventos. 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. |
Seu app pode realizar tarefas, como iniciar um novo traço, quando ACTION_DOWN
o traço acontece, desenhando o traço com ACTION_MOVE,
e terminando-o quando
ACTION_UP
é acionado.
O conjunto de ações MotionEvent
de ACTION_DOWN
a ACTION_UP
para um determinado
é chamado de conjunto de movimentos.
Ponteiros
A maioria das telas é multitoque: o sistema atribui um ponteiro para cada dedo, stylus, mouse ou outro objeto apontando que interage com a tela. Um ponteiro índice permite que você obtenha informações sobre o eixo de um ponteiro específico, como o na posição do primeiro dedo ou do segundo tocando 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 do primeiro
ponteiro zero (0).
Os objetos MotionEvent
contêm informações sobre o tipo de ponteiro em uso. Você
pode obter o tipo de ponteiro iterando os índices deles e chamando
as
getToolType(pointerIndex)
.
Para saber mais sobre ponteiros, consulte Processar multitoques gestos.
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, x e
coordenadas y, pressão, orientação, inclinação e passar o cursor.
Para permitir o acesso a esses dados, a API MotionEvent
fornece
getAxisValue(int)
,
em que o parâmetro é qualquer um dos seguintes identificadores de eixo:
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
no primeiro ponteiro.
Consulte também Processar multitoque gestos.
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()
![Stylus desenhando na tela com as coordenadas x e y mapeadas.](https://developer.android.com/static/images/develop/ui/compose/touch-input/stylus-input/advanced-stylus/stylus_x_y_coordinates.png?authuser=9&hl=pt-br)
Pressão
Você pode recuperar 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 maiores podem ser retornados dependendo da tela calibragem.
![Traço da stylus que representa um contínuo de pressão baixa para alta. O traço é estreito e sutil à esquerda, indicando baixa pressão. O traço vai ficando mais largo e mais escuro da esquerda para a direita até chegar à borda da tela, atingindo a maior largura e cor mais escura, indicando a maior pressão.](https://developer.android.com/static/images/develop/ui/compose/touch-input/stylus-input/advanced-stylus/stylus_pressure.png?authuser=9&hl=pt-br)
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 (π) 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 o a stylus representa um pincel plano, a largura dele depende do orientação da stylus.
![](https://developer.android.com/static/images/develop/ui/compose/touch-input/stylus-input/advanced-stylus/stylus_orientation.png?authuser=9&hl=pt-br)
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, onde zero é perpendicular à tela e π/2 é plano na superfície.
O ângulo de inclinação pode ser recuperado usando getAxisValue(AXIS_TILT)
(sem atalho para
o primeiro ponteiro).
A inclinação pode ser usada para reproduzir ferramentas da vida real o mais próximo possível, como imitando o sombreamento com um lápis inclinado.
![Stylus inclinada cerca de 40 graus em relação à superfície da tela.](https://developer.android.com/static/images/develop/ui/compose/touch-input/stylus-input/advanced-stylus/stylus_tilt.png?authuser=9&hl=pt-br)
Passar cursor
A distância entre a stylus e a tela pode ser obtida com
getAxisValue(AXIS_DISTANCE)
: O método retorna um valor de 0,0 (contato com
da tela) para valores mais altos à medida que a stylus se afasta da tela. Passar o cursor
a distância 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 uma será selecionado.
![](https://developer.android.com/static/images/develop/ui/compose/touch-input/stylus-input/advanced-stylus/stylus_hover.png?authuser=9&hl=pt-br)
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
o usuário apoia a mão naturalmente na tela enquanto escreve à mão.
A rejeição da palma é um mecanismo que detecta esse comportamento e notifica você de que
o último MotionEvent
definido precisa ser cancelado.
Como resultado, você precisa manter um histórico de entradas do usuário para que os toques indesejados podem ser removidas da tela e as entradas legítimas do usuário podem ser renderizado novamente.
ACTION_CANCEL e FLAG_CANCELED
ACTION_CANCEL
e
FLAG_CANCELED
estão
para informar que o conjunto MotionEvent
anterior deve ser
cancelado no último ACTION_DOWN
para que você possa, por exemplo, desfazer o último
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 definidos quando o usuário toca acidentalmente na tela, por exemplo, segurando
do dispositivo ou colocando a palma da mão sobre a 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 MotionEvent
definido, do último
ACTION_DOWN
a partir deste ponteiro.
Como ACTION_CANCEL
, o ponteiro pode ser encontrado com getPointerId(actionIndex)
.
MotionEvent
. O toque da palma foi cancelado, e a tela foi renderizada novamente.
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 o tela de um aplicativo de desenho ou anotação, deslizando da parte inferior da tela para exibir a navegação ou mover o app para o segundo plano pode resultar em uma toque indesejado na tela.
Para evitar que os gestos acionem toques indesejados no app, você pode:
a vantagem dos encartes e
ACTION_CANCEL
Consulte também Rejeição da palma da mão, navegação e entradas indesejadas nesta seção.
Use o
setSystemBarsBehavior()
e
BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
de
WindowInsetsController
para evitar que 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
![A latência faz com que o traço renderizado fique para trás em relação à posição da stylus. A lacuna entre o traço renderizado e a posição da stylus representa a latência.](https://developer.android.com/static/images/develop/ui/compose/touch-input/stylus-input/advanced-stylus/stylus_latency.png?authuser=9&hl=pt-br)
Origem da latência
- Registrando a stylus com tela 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 uma tela touch 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 com vários buffers e aproveitando uma técnica de renderização de buffer frontal, o que significa escrever diretamente para na tela.
Renderização do buffer frontal
O buffer frontal é a memória que a tela usa para renderização. É o lugar mais próximo podem começar a desenhar diretamente na tela. A biblioteca de baixa latência permite sejam renderizados diretamente no buffer frontal. Isso melhora o desempenho impedindo a troca do buffer, o que pode acontecer na renderização normal de vários buffers ou renderização de buffer duplo (o caso mais comum).
![O app escreve no buffer da tela e lê do buffer da tela.](https://developer.android.com/static/images/develop/ui/compose/touch-input/stylus-input/advanced-stylus/front_buffer_rendering.png?authuser=9&hl=pt-br)
![O app escreve no buffer múltiplo, e troca pelo buffer de tela. O app lê diretamente do buffer da tela.](https://developer.android.com/static/images/develop/ui/compose/touch-input/stylus-input/advanced-stylus/multi-buffer_rendering.png?authuser=9&hl=pt-br)
Embora a renderização do buffer frontal seja uma ótima técnica para renderizar uma pequena área do ele não foi projetado para atualizar toda a tela. Com renderização do buffer frontal, o aplicativo está renderizando conteúdo em um buffer do qual que a tela está lendo. Assim, existe a possibilidade de renderizar artefatos ou rompimento (veja 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 renderização do buffer frontal
implementação. A biblioteca é adicionada como uma dependência no módulo do app.
Arquivo build.gradle
:
dependencies {
implementation "androidx.graphics:graphics-core:1.0.0-alpha03"
}
Callbacks de GLFrontBufferRenderer
A biblioteca de baixa latência inclui
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
substituir 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 a GLFrontBufferedRenderer
fornecendo os SurfaceView
e
os callbacks criados anteriormente. O GLFrontBufferedRenderer
otimiza a renderização
ao 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 o método
commit()
que aciona o callback onDrawMultiDoubleBufferedLayer()
.
No exemplo a seguir, o processo é renderizado no buffer frontal (rápido
renderização) quando o usuário começa a desenhar na tela (ACTION_DOWN
) e se move
o ponteiro ao redor (ACTION_MOVE
). O processo é renderizado no buffer duplo
quando o ponteiro sai da superfície da tela (ACTION_UP
).
Você pode usar
requestUnbufferedDispatch()
solicitar que o sistema de entrada não agrupe eventos de movimento em lote, mas sim entregue
assim que 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 modificados ao mesmo tempo. Uma parte da tela mostra dados novos, enquanto outra mostra dados antigos.
![As partes de cima e de baixo da imagem do Android estão desalinhadas devido a rupturas da tela ao atualizar.](https://developer.android.com/static/images/develop/ui/compose/touch-input/stylus-input/advanced-stylus/tearing.png?authuser=9&hl=pt-br)
Previsão de movimento
Previsão de movimento do Jetpack biblioteca reduz latência percebida ao estimar o caminho do traço do usuário e fornecer dados artificial para o renderizador.
A biblioteca de previsão de movimento recebe entradas reais do usuário como objetos MotionEvent
.
Os objetos contêm informações sobre as coordenadas X e Y, pressão e tempo.
que são aproveitadas pelo preditor de movimento para prever MotionEvent
futuros.
objetos.
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 pelo MotionEvent
real
dados assim que eles são recebidos.
A biblioteca de previsão de movimento está disponível no Android 4.4 (API de nível 19) e versões mais recentes e em dispositivos ChromeOS com o Android 9 (nível 28 da API) e versões mais recentes.
![A latência faz com que o traço renderizado fique para trás em relação à posição da stylus. A lacuna entre o traço e a stylus é preenchida com pontos de previsão. A lacuna restante é a latência percebida.](https://developer.android.com/static/images/develop/ui/compose/touch-input/stylus-input/advanced-stylus/stylus_reduced_latency.png?authuser=9&hl=pt-br)
Dependências
A biblioteca de previsão de movimento fornece a implementação da previsão. O
é 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 as
MotionEventPredictor
que define os seguintes métodos:
record()
Armazena objetosMotionEvent
como um registro das ações do usuáriopredict()
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 Entrada compatibilidade.
Para registrar um app como sendo de anotações no Android, consulte Criar anotações app.
O Android 14 (nível 34 da API) lançou a
ACTION_CREATE_NOTE
que permite que o app inicie uma atividade de anotação na fechadura.
tela.
Reconhecimento de tinta digital com o Kit de ML
Com a tinta digital do Kit de ML reconhecimento de marca, seu app consegue 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
Ink.Stroke.Builder
para criar objetos Ink
que podem ser processados por modelos de machine learning
para converter escrita à mão em texto.
Além do reconhecimento de escrita manual, o modelo é capaz de reconhecer gestos, como "Delete" e "Circle".
Consulte Tinta digital reconhecimento para saber mais.