Apps foundation

Como dimensionar interações de entrada além da tela touch screen com o Jetpack Compose

Alto-falantes

  • Alex Vanyo

    Engenheiro de software

  • Jeremy Walker

    Engenheiro de software do Compose

Sobre

Sobre a palestra

À medida que novos formatos vão além da tela touch screen, o sistema de entrada do Compose evolui para atender ao desafio. A arquitetura em camadas é a base para oferecer suporte a tudo, desde gestos avançados de trackpad até novas entradas de hardware, sem problemas.

Com detalhamentos claros da arquitetura e exemplos práticos de código, acompanhe o ciclo de vida de um evento de entrada desde a camada da plataforma do SO até os detectores de gestos e componentes de interface do Compose. Você vai descobrir como rastrear eventos em toda a pilha, aplicar padrões arquitetônicos importantes para novos tipos de entrada e depurar com confiança interações complexas de gestos nos seus próprios apps.

Resumo

  • O Jetpack Compose processa eventos de ponteiro do Android recebidos por um pipeline de seis fases. Esse pipeline mantém uma árvore de caminho de hit e envia eventos em três passagens pelos nós da interface.
  • As entradas do trackpad no Compose agora são mapeadas para eventos de mouse em vez de toque. O Compose também converte eventos de toque falso da plataforma em eventos de ponteiro de movimentação e dimensionamento dedicados.
  • Os trackpads de óculos inteligentes introduzem tipos de entrada indireta em vez de toques de coordenadas de tela. Eles usam um eixo de movimento direcional principal para mapear deslizamentos horizontais em navegação vertical para o próximo ou anterior.
  • O suporte a testes foi expandido além do toque com novos métodos, como performTrackpadInput. Essas APIs oferecem plataformas de teste conhecidas para validar se os apps processam corretamente dispositivos sem tela touchscreen.

Gemini Notebook

Tópicos

  • jetpack-compose
  • pointer-input
  • trackpads
  • gestos
  • intelligent-eyewear
  • acessibilidade

Transcrição

Introdução e filosofia de entrada

  • Olá a todos!

  • Esperamos que você esteja gostando da Conferência de Desenvolvedores Android e agradecemos por assistir à nossa palestra.

  • Sou Alex Vanyo, engenheiro de software da equipe do Compose, e estou aqui com Jeremy, que também é engenheiro de software da equipe do Compose.

  • Nós dois estamos trabalhando para melhorar o sistema de entrada do Jetpack Compose. Hoje, vamos falar sobre como escalonamos esse sistema além da tela touch screen para facilitar o suporte à entrada sem toque.

  • De modo geral, a entrada captura qualquer forma de interação do usuário com o app.

  • As telas touch screen são provavelmente a primeira coisa que você pensa para um app para dispositivos móveis, mas definitivamente não são o único tipo de entrada.

  • Outros tipos de entrada incluem teclados físicos, mouses, trackpads, stylus, controles e dispositivos giratórios.

  • Também estamos vendo novos tipos de entrada, como trackpads em óculos inteligentes, rastreamento de olhos e mãos em XR e gestos de dedos e pulso no Wear.

  • E se você quiser ir ainda mais longe, há outros tipos de entrada, como IMEs de software para edição de texto, microfones, câmeras e outros sensores de dispositivo.

  • É preciso esperar que seus apps sejam usados com uma grande variedade de tipos de entrada.

  • Um dos motivos é a variedade cada vez maior de dispositivos em que os aplicativos são executados e a adaptabilidade crescente desses dispositivos.

  • Os formatos de dispositivo podem ter um método de entrada mais comum, mas é importante não presumir que um formato específico implica o uso de um tipo de entrada específico.

  • Embora um Chromebook tenha um trackpad e um teclado integrados, isso não torna esses tipos de entrada exclusivos desse formato.

  • Teclados físicos, trackpads e mouses também podem ser usados em smartphones, tablets e TVs, e algumas TVs têm até telas touch screen.

  • Um segundo grande motivo para esperar e oferecer suporte a vários tipos de entrada é a acessibilidade.

  • Permitir que os usuários usem o dispositivo de entrada que preferirem e que alternem entre eles conforme acharem adequado é a maneira certa de tornar os aplicativos mais acessíveis e fáceis de usar.

  • Dependendo da jurisdição, pode ser um requisito legal ter um comportamento específico e compatível no aplicativo para esses tipos de entrada, especialmente em relação a teclados e mouses.

  • Em resumo, é preciso criar suporte para diferentes tipos de entrada, independentemente do formato.

  • Vamos ver como o Jetpack Compose ajuda seus apps a atender a essas necessidades dos usuários.

  • A filosofia do Compose, em geral, é que esses diferentes tipos de entrada devem se comportar de maneira adequada com componentes padrão, sem que você precise fazer nada de especial.

  • E, em seguida, você terá as ferramentas necessárias para personalizar e ajustar o comportamento como quiser.

  • Além desses tipos de entrada iniciais, conforme mais tipos de entrada forem aceitos pela plataforma, vamos criar suporte para eles nos componentes do Compose, melhorando o comportamento dos seus aplicativos apenas com a atualização da versão do Compose.

  • Vamos analisar dois exemplos nesta palestra, ambos adições recentes ao Compose.

  • Por coincidência, ambos são dispositivos semelhantes a trackpads, mas, como você vai ver, eles são usados de maneiras muito diferentes.

  • Vamos começar com um trabalho recente em trackpads de laptops.

  • Um trackpad é um dispositivo de entrada baseado em ponteiro.

  • Mais especificamente, ao usar um trackpad, você recebe coordenadas bidimensionais.

  • No entanto, ao contrário de uma tela touch screen, as coordenadas dos dedos no trackpad não estão diretamente no espaço de coordenadas da interface.

  • Em vez disso, para um trackpad semelhante ao de um laptop, há uma tradução para controlar um cursor.

  • Nessa tradução, você pode esperar uma série de conversões de plataforma complicadas, como aceleração do cursor e rejeição de palma.

  • Então, o que você recebe no Compose é um ponteiro correspondente ao local do cursor que geralmente funciona como uma entrada do mouse.

  • Na plataforma Android, recebemos informações de passagem do cursor, cliques, incluindo cliques secundários ou terciários, e todas essas informações são agrupadas em MotionEvents.

  • Para entender como melhoramos o comportamento nas versões mais recentes do Compose, vamos analisar de perto como o Compose lida com esses eventos de ponteiro.

As seis fases do processamento de entrada do ponteiro

  • Vou dividir isso em seis fases diferentes, e vamos passar por todas elas, uma de cada vez.

  • A primeira fase é a migração de MotionEvents para o Compose.

  • Portanto, o Compose é, na verdade, apenas uma visualização: uma visualização muito complicada que faz muita coisa, mas é apenas uma visualização, o que significa que ela precisa funcionar no sistema geral do Android para entrada de ponteiro.

  • No Compose, isso significa que, como ele fica dentro de uma janela normal em uma atividade normal, ele recebe MotionEvents: os MotionEvents da plataforma Android.

  • Em seguida, o Compose precisa processá-los e fazer tudo o que você quiser com a entrada de ponteiro.

  • A primeira fase é traduzir esses MotionEvents em uma representação interna, independente de plataforma, no Compose, que chamamos de PointerInputEvent.

  • Esse é um tipo interno, então você não vai vê-lo diretamente.

  • Se você quiser conferir todos os detalhes do que está acontecendo aqui, acesse MotionEventAdapter, mas vamos pular esses detalhes aqui.

  • Agora, temos uma representação independente de plataforma de um MotionEvent no Compose.

  • Em seguida, acontece que a entrada geralmente ocorre ao longo do tempo.

  • Você recebe vários eventos de entrada um após o outro, então é muito útil saber o que está acontecendo ao longo do tempo.

  • Gestos, rastreamento de velocidade, detecção de inclinação, tudo isso precisa de uma compreensão do que está acontecendo em cada momento.

  • Portanto, o próximo estágio nesse pipeline de processamento é adicionar essas informações de tempo, e ele vai fazer um pré-processamento com base em eventos de ponteiro sucessivos.

  • No código, isso é como pegar o PointerInputEvent atual, combiná-lo com o que aconteceu com o anterior e criar um InternalPointerEvent que considera o tempo.

  • Esse é outro tipo interno, como o nome sugere, mas você pode pensar nele como um mapa de diferentes IDs de ponteiro para PointerInputChanges, que é um tipo público.

  • Essa é a primeira.

  • Ao pensar em diferentes IDs de ponteiro, você pode notar que, agora que estamos cientes do rastreamento de tempo, também podemos acompanhar quando os IDs de ponteiro aparecem e desaparecem.

  • Não há apenas um.

  • Se você tiver vários dedos em uma touch screen, poderá ter vários ponteiros ao mesmo tempo, e precisamos lidar com isso.

  • Se você quiser saber mais detalhes, consulte PointerInputChangeEventProducer, que faz tudo isso, mas vamos pular essa parte.

  • Ainda temos muito para ver.

  • Agora temos uma representação do Compose da entrada, que tem informações de tempo.

  • A próxima etapa é descobrir o que fazer com esse evento.

  • Para onde enviamos?

  • E isso leva ao teste de caminho de hit.

  • Imagine uma situação em que um MotionEvent é enviado e essa ComposeView preenche toda a janela, talvez até a tela inteira.

  • Precisamos descobrir para onde enviá-lo.

  • Como o Compose é apenas uma visualização, mas tem muitos elementos, como nós, botões e itens com que você interage, precisamos fazer todo o trabalho de descobrir para onde enviar um evento que acontece em uma posição específica e descobrir qual nó está realmente interessado nele.

  • Esses nós de layout têm coordenadas, limites e formas. Por isso, precisamos descobrir qual deles, se houver, é atingido.

  • Mas essa também não é a visão mais geral.

  • É comum colocarmos elementos interativos dentro de outros elementos interativos, como um botão dentro de uma lista.

  • Fazemos isso com frequência.

  • Portanto, o caso geral completo aqui é que um evento de entrada específico cria um caminho de hit completo que passa por todos os nós interativos atingidos pelas coordenadas do evento de entrada.

  • Nesse caso, há três níveis na hierarquia de aninhamento.

  • Temos um caminho de hit, mas isso não é suficiente.

  • Lembra que eu disse que temos vários eventos de entrada de ponteiro, vários ponteiros que podem estar pressionados?

  • Na verdade, precisamos ter uma árvore de caminho de hit completa.

  • Não há apenas um caminho de hit quando você tem vários ponteiros.

  • Essa é a próxima fase: criar e atualizar essa árvore de caminhos de hit.

  • Esta é uma visão mais detalhada do que está acontecendo.

  • À esquerda, temos nós que são dispostos visualmente e, no canto inferior esquerdo, é possível ver a representação hierárquica, quais nós estão dentro de outros nós, quais são ancestrais ou descendentes uns dos outros.

  • Quando um ponteiro é pressionado, rastreamos esse caminho de clique, o caminho completo pela árvore, e recebemos um caminho de clique para esse ponteiro específico.

  • Esse caminho de hit se torna uma árvore quando recebemos o segundo ponteiro.

  • Ela pode se cruzar com alguns dos mesmos nós que a outra, mas também pode interagir com outros.

  • À direita, temos a árvore de caminhos de hit que estamos mantendo.

  • E os ponteiros podem aparecer e desaparecer, então precisamos manter isso ao longo do tempo para garantir que tenhamos a lista completa de nós interessados em cada evento em andamento no momento.

  • Certo.

  • Agora temos uma árvore de caminho de hit que estamos mantendo para esses eventos de entrada, o que significa que estamos na próxima fase.

  • Agora, sabemos para onde vamos enviar esses eventos de entrada de ponteiro, ou seja, quais nós estão interessados em cada evento específico.

  • Agora que sabemos disso, podemos fazer uma transformação de coordenadas.

  • É muito bom quando o nó recebe eventos de entrada no próprio espaço de coordenadas local, para que você não precise fazer um monte de cálculos de coordenadas.

  • Assim, quando sabemos disso, podemos pegar o evento de entrada do ponteiro, combiná-lo com as coordenadas do nó em questão e, por fim, criar o PointerEvent público que será enviado para todos vocês e para o modificador de entrada do ponteiro.

  • E essa é a última coisa na nossa fase, a fase 6, que é finalmente enviar o evento.

  • Esse é um evento de entrada de ponteiro que ocorreu e está sendo enviado ao nó para que ele faça o que quiser com ele.

  • Fazemos isso em três etapas diferentes.

  • Temos uma passagem inicial, uma principal e uma final.

  • O motivo de ter três passagens diferentes é que isso permite que os nós se comuniquem entre si e comuniquem o interesse em partes específicas de eventos de entrada de ponteiro específicos e, em seguida, consumam partes diferentes deles como parte do envio.

  • A primeira passagem, a inicial, vai do ancestral ao descendente.

  • Um exemplo de quando isso é útil é se algum nó de layout externo for um contêiner de rolagem, souber que começou a rolar e quiser impedir que todos os eventos de ponteiro alcancem os filhos.

  • Isso, na passagem inicial, permite que ele o consuma, e os filhos não vão receber mais eventos.

  • A segunda é a principal.

  • E esse é o único que vai do descendente para o ancestral.

  • Como o nome sugere, esse é o passe principal para fazer as coisas.

  • Por exemplo, é aqui que um descendente vai consumir um evento de pressionamento, iniciando o processamento de um clique, e terá a chance de fazer isso antes de qualquer um dos pais se houver elementos clicáveis aninhados.

  • E a última passagem, a final, é igual à inicial, em que vai do ancestral para o descendente.

  • É assim que um filho pode informar a um pai que ele consumiu parte de um evento.

  • Assim, se um filho começar a processar um clique, mas um pai começar a rolar, o filho poderá ver que o pai fez isso e cancelar o clique que começou a processar.

  • Essas são as três passagens e as seis fases.

  • Essa é a base de tudo que está acima do sistema de entrada de ponteiro.

  • Todo o processamento de gestos, rolagem, arrasto, clique e qualquer entrada de ponteiro personalizada são criados com base nessa base.

Atualizações do trackpad e gestos com vários dedos

  • Com tudo isso, agora podemos falar um pouco sobre as especificidades de como melhoramos o suporte ao comportamento do trackpad no Compose.

  • Portanto, antes do Compose 1.12, os eventos de entrada do trackpad eram informados como um tipo de toque de ponteiro, já que é um dedo interagindo fisicamente com um trackpad para controlar o cursor.

  • No entanto, a expectativa do usuário é que esse tipo de entrada funcione de forma muito mais parecida com um mouse físico.

  • Por isso, mudamos o tipo de ponteiro de PointerType.Touch para PointerType.Mouse.

  • Com essa mudança, os usuários terão uma experiência de mouse e trackpad muito mais consistente em qualquer componente que use essas informações.

  • Um exemplo é o comportamento da dica: você provavelmente quer o mesmo comportamento para um trackpad e um mouse.

  • A segunda grande mudança que fizemos no trackpad está relacionada aos gestos com vários dedos que você faz nele.

  • Anteriormente, o Compose usava o conceito inteligente, mas um pouco assustador, de dedos falsos da plataforma Android.

  • Vamos ver o que isso significa.

  • Quando você usa dois dedos em um trackpad para movimentar a tela, o Android gera um único dedo falso de tela touch screen que desce até onde o cursor está.

  • Em seguida, quando você move os dedos no trackpad, o dedo falso na tela touch screen se move na distância e direção corretas para imitar o comportamento desejado e, em seguida, é liberado.

  • Assim, dois dedos se movendo para cima no trackpad são traduzidos como um único dedo falso na tela touch, também se movendo para cima, o que, por sua vez, faz com que a tela role para baixo.

  • Uma coisa divertida aqui é que, se você, como eu, usa o recurso de rolagem reversa do trackpad, vai querer que a rolagem vá para o outro lado.

  • Para isso, basta inverter a direção do dedo falso.

  • Agora, dois dedos no trackpad que se movem para cima são traduzidos em um único dedo falso na tela touch screen que se move para baixo, o que, por sua vez, faz com que a tela role para cima, o oposto do que tínhamos antes.

  • O escalonamento funciona de maneira semelhante.

  • Quando você usa dois dedos em um trackpad para dimensionar, o Android gera dois dedos falsos de tela touch screen que descem e se aproximam ou se afastam, dependendo do gesto, para imitar o comportamento desejado de aumentar ou diminuir o zoom.

  • Por isso, desde o Compose 1.12 para movimentação e o Compose 1.13 para dimensionamento, interceptamos esses dedos falsos.

  • Não os enviamos como a plataforma os fornece, mas os reinterpretamos em novos tipos de eventos de ponteiro: PanStart, PanMove e PanEnd para movimentação, e ScaleStart, ScaleChange e ScaleEnd para dimensionamento.

  • Fazemos isso na fase de conversão de eventos do Android para o Compose que mencionei anteriormente.

  • Por isso, convertemos os eventos de dedo falso nos tipos adequados e enviamos esses gestos definidos pela plataforma para o caminho de clique que corresponde ao cursor.

  • Vamos entender por que isso é importante.

  • Por que fazemos isso?

  • Digamos que esta caixa seja uma imagem que queremos ampliar.

  • Ao usar dois dedos em um trackpad para dimensionar, era possível que um dos dois dedos falsos não atingisse o elemento.

  • O caminho de clique não se cruzaria com a imagem e começaria fora dos limites.

  • Então, em vez de escalonar, a imagem seria rolada.

  • Se houvesse outro elemento rolável fora da imagem, talvez ele também começasse a rolar junto.

  • Não era isso que o usuário queria.

  • Eles queriam escalonar.

  • Então, em vez de enviar esses dedos falsos, agora enviamos o gesto de dimensionamento apenas para o elemento abaixo da posição do cursor, o que leva ao comportamento correto.

  • Não importa onde o cursor esteja ou quão perto ele esteja da borda do elemento que você quer dimensionar, agora ele será dimensionado corretamente.

  • A boa notícia é que esses eventos de entrada reinterpretados são usados por padrão em componentes como roláveis e transformáveis para oferecer um comportamento de maior fidelidade para entradas de trackpad se você estiver usando esses elementos básicos.

  • Se você estiver usando algo criado com base neles, vai receber esse comportamento melhorado ao atualizar sua versão do Compose para a versão mais recente.

  • Se você estiver escrevendo modificadores de entrada de ponteiro personalizados, confira essas novas APIs e verifique se elas são compatíveis para oferecer aos usuários a melhor experiência possível em todos esses dispositivos de entrada diferentes.

  • E, claro, também adicionamos APIs de teste para que você possa verificar se o app está processando todos esses tipos de entrada corretamente.

  • Portanto, a performTrackpadInput é muito parecida com as APIs de teste atuais para mouse e toque.

    onNodeWithContentDescription("Photo")
        .performTrackpadInput {
            click(center)
    
            pan(Offset(0f, 100f))
    
            scale(0.9f)
        }
  • E, usando tudo isso, você pode validar se o app está funcionando corretamente, não importa qual dispositivo de entrada o usuário esteja usando no dispositivo.

  • Espero que isso tenha ajudado você a entender melhor como um evento de ponteiro vai do dedo no touchpad até o app.

  • Agora, vou passar a palavra para o Jeremy, que vai falar sobre uma maneira muito diferente de usar os trackpads.

Introdução à entrada indireta

  • Obrigado.

  • Certo.

  • Olá, meu nome é Jeremy Walker.

  • Trabalho muito com entrada e achei que seria interessante falar sobre algumas das novidades que estamos fazendo com a entrada do Compose e como isso afeta você.

  • E, especificamente, com esse tipo de entrada indireta, que surgiu devido a uma nova entrada baseada em um novo formato: óculos inteligentes.

  • Então, quando você recebe entrada de óculos, pode fazer isso de algumas maneiras: olhando para algo, usando a voz, usando gestos, mas vou falar especificamente sobre o touchpad.

  • Eles são um pouco diferentes dos que o Alex mencionou, porque ficam na lateral da cabeça, são verticais, muito largos e extremamente curtos.

  • Então, quando pensamos em como isso funcionaria no Compose, se você imaginar que está olhando para um botão pequeno à sua frente, se estiver movendo um cursor para tentar tocá-lo, essa não será uma boa experiência, certo?

  • Então, como vamos dar suporte a ele?

  • Esta é a interface do Glasses.

  • Ele foi projetado para sobrepor conteúdo de forma integrada ao seu campo de visão.

  • Mas é muito simples, tentamos manter as coisas simples.

  • Neste exemplo, você pode ver que é uma lista.

  • Ela está focada em um determinado elemento no centro.

  • O que realmente queremos fazer?

  • Bem, queremos selecioná-lo ou talvez passar para o anterior ou o próximo.

  • Não precisamos necessariamente de um local na tela.

  • Este é um pouco mais complicado, mas basicamente há três opções: desligar, ativar o som ou desativar o vídeo.

  • Mas, na verdade, você quer selecionar essa opção ou passar para outra e selecioná-la.

  • Por isso, achamos que não é necessário mapear diretamente um local na tela ou, neste caso, seus olhos.

  • Então, é assim que fica.

  • Por exemplo, aqui, estou apenas me movendo entre os elementos, e isso está na interface do Glasses, o que se encaixa no que queremos.

  • E você pode tocar nele.

  • Analisamos o Android para pensar no que existe que poderíamos usar para dar suporte a isso.

  • E se vocês já tiverem, se vocês receberem… Ah, desculpa.

  • Isso está pulando para frente aqui.

  • Deixa eu voltar.

  • Vamos lá.

  • Certo.

  • Se você já usou um desses dispositivos e o vinculou a um dispositivo Android, sabe que é fácil mover elementos da interface de um para o outro.

  • Por exemplo, com o controle remoto, você pode ver que pode Se você imaginar ou se tiver vinculado ao Google TV e tiver uma grade inteira de filmes, poderá pressionar os botões de seta para ir até o que deseja e pressionar o botão para selecioná-lo, o botão OK.

  • Se você tiver um menu de jogo e esse controle, poderá usar o botão direcional ou o joystick para mover para um elemento da interface, por exemplo, em um menu, e selecioná-lo.

  • O mesmo vale para o teclado.

  • Isso corresponde ao que queremos no Compose.

  • E aqui estão os itens, para você ter uma ideia de como é.

  • E, se voltarmos um pouco mais, havia esse tipo de touchpad que era pareado com uma TV e usava algo chamado toque de navegação.

  • A ideia é que as coordenadas x e y não sejam afetadas nem vinculadas à tela.

  • Eles são usados apenas para detecção de gestos.

  • Se você deslizar para a direita, é como se estivesse pressionando a seta para avançar.

  • Se deslizar para a esquerda, você vai para a esquerda, para cima e para baixo.

  • Isso nos dá quase tudo o que queremos.

  • Você só usa x e y para rastrear a velocidade e descobrir esses arrastos e deslizamentos, etc. Isso nos dá a maior parte do que precisamos para começar a usar o Compose.

  • Não é exato porque está mapeado para a TV, então precisamos garantir que não vamos seguir o caminho da TV, não vamos estragar as TVs mais tarde, então precisamos garantir que vamos dividir isso, mas vamos chegar lá em breve.

  • Mas ele nos dá um ponto de partida.

  • Para que possamos usar esse tipo de navegação por toque.

Design de API para entrada indireta e novos formatos

  • Então, tudo bem.

  • Como será o design da API?

  • No Compose, a entrada mais geral ou comum é o toque, certo?

  • Por isso, temos muito trabalho em relação a isso.

  • Então, tivemos que descobrir como isso se relaciona com o problema.

  • Lembre-se de que o PointerEvent é assim.

  • Aqui, você tem o tipo, que é o tipo de movimento para baixo e para cima, e tem esse PointerInputChange, que é apenas uma lista. Você pode pensar nela como dedos.

  • Se você tiver três dedos abaixados, terá três itens na lista.

  • Queríamos que ficasse parecido com isso.

  • Esse não é um módulo de interface, que é um módulo muito mais baixo do que a maioria dos desenvolvedores usa ao lidar com entradas.

  • Eles vão ficar uma camada acima, na camada de base.

  • É onde estão todos os nossos detectores de gestos.

  • Porque, como desenvolvedores, vocês não se importam com todas essas entradas de minutos.

  • Você só quer saber se houve um arrasto, um clique ou um deslize e, em seguida, realizar a ação com base nisso.

  • Por isso, temos um monte de código que faz isso para você, e ele é alimentado por esses recursos em segundo plano.

  • Então, começamos a pensar: "Ok, precisamos facilitar ao máximo para que o código existente na base possa oferecer suporte a esses novos tipos".

  • Então, criamos um novo tipo que é praticamente igual.

  • É chamada de indireta.

  • E o significado indireto é que não é um toque direto na tela.

  • Então, ele avisa que isso está chegando. Não pense nisso do ponto de vista do toque.

  • Pense que estamos apenas lidando com gestos e navegando entre elementos da interface.

  • O tipo é o mesmo.

  • Ele tem uma lista de dedos.

  • Nesse caso, é IndirectPointerInputChange.

  • Mas nós fizemos isso, e funcionou muito bem.

  • Mas encontramos mais um item chamado "Eixo de movimento direcional principal".

  • É um nome complicado, mas vai fazer sentido quando eu explicar, porque é interessante como ele surgiu.

  • Como isso aconteceu?

  • Bem, novamente, você tem esse touchpad na lateral da cabeça, que é largo e curto.

  • E, nesse ponto, tivemos a ideia de que, ok, temos a navegação por toque.

  • Podemos deslizar para a esquerda e para a direita.

  • E isso funciona bem.

  • Mas se você pensar em como os trackpads existem agora, certo?

  • Quando você vai para a esquerda ou para a direita, você vai para a esquerda ou para a direita no trackpad com o conteúdo da interface.

  • Se você deslizar para cima ou para baixo, vai rolar a lista para cima ou para baixo.

  • Se você tentar fazer isso com óculos, não é tão ruim, certo?

  • Mas se você fizer isso, não será uma boa experiência.

  • Descobrimos que o movimento mais natural é tocar, mas deslizar o dedo na lateral dos óculos também é uma opção.

  • Fizemos vários estudos com usuários, e eles esperam que, ao deslizar horizontalmente, o conteúdo não se mova horizontalmente, mas verticalmente.

  • Pense nisso como se estivesse deslizando o dedo em uma lista à sua frente. Você pode avançar ou voltar na lista.

  • Então, estamos pegando uma entrada bidimensional e tentando pensar nela de uma forma unidimensional.

  • E foi daí que surgiu a ideia do eixo de movimento principal: você pode ir na horizontal e ele vai na vertical.

  • Portanto, é possível especificar no próprio dispositivo.

  • Você pode dizer: "Ei, esse não é um trackpad normal".

  • Quero que a direção principal seja horizontal, e esse é o único eixo que quero seguir.

  • Foi daí que surgiu a ideia.

  • Ok.

  • Então, estamos bem confiantes.

  • O Compose está cuidando de tudo.

  • E você tem essa implementação vertical legal.

  • Pense nisso.

  • Então, como mapeamos isso? Agora, com a definição horizontal, temos apenas "avançar" e "voltar".

  • Não temos cima, baixo, esquerda, direita.

  • Só temos para frente e para trás.

  • E então, podemos mapear isso se você estiver familiarizado com o foco com o próximo e o anterior.

  • Agora, vamos passar para o próximo e o anterior.

  • E isso funciona muito bem para vídeos verticais.

  • E é ainda mais legal.

  • Ele funciona com o horizontal pronto para uso, certo?

  • Porque você só tem um monte de itens em foco.

  • Basta ir para a próxima ou a anterior, e funciona com as duas.

  • Esse é um caso em que definimos e descobrimos tudo isso no Compose, mas seria muito bom se o próprio dispositivo pudesse dizer, porque ele sabe se tem um formato estranho e se quer apenas esse eixo principal, mas não tem como definir isso.

  • Então, tivemos que levar esse conceito de volta para a plataforma e dizer: "Ok, precisamos definir isso na plataforma para que possamos padronizar e os próprios dispositivos possam especificar isso, para que o Compose possa oferecer suporte total até o fim".

  • Então, há um caso em que trabalhamos no Compose, avançamos no Compose e voltamos, e meio que o trouxemos conosco.

  • E foi exatamente isso que fizemos.

  • Criamos essa nova API para que você ou o dispositivo possam especificar isso.

    InputDevice.ViewBehavior
    .getPrimaryDirectionalMotionAxis()
  • E se você se lembra, quando falei sobre entrada por toque, ou navegação por toque, eu estava falando sobre como ela foi criada principalmente para TVs.

  • Não queríamos quebrar essas implementações.

  • Por isso, adicionamos outro campo para especificar esse novo tipo de entrada de vidro.

  • Ok.

  • Então, o que isso significa para você?

  • Essa é uma versão mais específica do que você recebe como um detector de gestos.

    // Glimmer Library
    Box(
        modifier =
            Modifier.fillMaxSize()
                .onIndirectPointerGesture(
                    enabled = true,
                    onSwipeForward = { /* onSwipeForward */ },
                    onSwipeBackward = { /* onSwipeBackward */ },
                    onClick = { /* onClick */ },
                )
                .focusTarget()
    ) {
        // App()
  • Se você estiver usando o Foundation, ele vai funcionar com os detectores de gestos.

  • Fizemos o trabalho para você, então não precisa fazer nada.

  • Mas se você quiser criar um app para os óculos, use nossa biblioteca Glimmer. Temos um detector de gestos especial que só oferece, sabe, para que você não precise combiná-los, use apenas este, receba o clique, o frame para frente e para trás.

Como testar a entrada de ponteiro indireto

  • Ok.

  • Falamos sobre o funcionamento da API na plataforma e no Compose.

  • E os testes?

  • É assim que o toque funciona quando você quer fazer um teste de toque.

    @Test
    clickTest {
    
    rule.onNodeWithTag(defaultTag)
    .performTouchInput
    { click(center) }
    
    }
  • Basta dizer performTouchInput e fazer um gesto.

  • Há muitos gestos diferentes, e nós criamos todos os pequenos eventos para que o gesto aconteça.

  • Como isso funciona de modo geral?

  • Bem, você tem o módulo de interface do Compose, o módulo de teste de interface e eles se comunicam, pelo menos em termos de entrada, usando MotionEvents.

  • Além disso, o módulo de teste de IU também usa um mecanismo de fila, que é todo baseado em MotionEvent.

  • Por que estou contando isso?

  • Porque não é possível criar de forma programática um MotionEvent que seja como um evento de entrada indireta, o que foi ótimo para mim, porque isso significava que eu tinha que reescrever toda a comunicação entre os módulos e o sistema de fila.

  • Mas eu fiz.

  • O que isso significa para você?

  • Se você já conhece o teste de entrada por toque, é isso que você recebe para entrada indireta.

  • Basta adicionar "indireto" ao nome.

    @Test
    clickTest {
    
    rule.onNodeWithTag(defaultTag)
    // .performTouchInput
    .performIndirectPointerInput (
    IndirectPointerEventPrimaryDirectionalMotionAxis.X,
    inputDeviceSize,
    )
    { click(center) }
    }
  • Agora, estou transmitindo alguns parâmetros extras.

  • Você provavelmente reconhece o primeiro, que é onde você especifica a horizontal.

  • E o tamanho da entrada seria, obviamente, o tamanho do touchpad.

  • Isso não muda muita coisa.

  • Se você olhar o código-fonte, vai ver que eu escrevi uma função de extensão que se parece com isto.

    @Test
    clickTest {
    
    rule.onNodeWithTag(defaultTag)
    // .performTouchInput
    .performIndirectPointerInput
    { click(center) }
    
    }
  • Ok.

  • Isso dá uma ideia de todo o trabalho que fizemos para oferecer suporte a trackpads em armações ou óculos inteligentes.

  • Mas estamos fazendo, estou fazendo esses exercícios para rastreamento de olhos e mãos também, então há muito mais por vir.

  • Então, de modo geral, o que eu quero que você lembre da minha parte?

Resumo e principais conclusões

  • Então, se você estiver usando um trackpad com um smartphone, óculos ou qualquer outra entrada, estamos pensando nisso e tentando fazer o trabalho com antecedência.

  • Portanto, se você estiver usando esses detectores de gestos do Foundation, eles vão funcionar sem problemas.

  • Essa é a primeira coisa.

  • Em segundo lugar, o Compose é o principal kit de ferramentas de interface, e vamos continuar explorando e oferecendo suporte a novos tipos de entrada, tanto na plataforma quanto no kit de ferramentas, como mostrei aqui.

  • E, claro, para testar APIs, quero manter uma superfície e um paradigma conhecidos para que seja o mais fácil possível migrar e testar esses novos tipos.

  • Agradecemos sua atenção. Esperamos que isso tenha ajudado.

Código

Snippets de código

Snippet do Kotlin em 15:47 em "Atualizações do trackpad e gestos com vários dedos"
onNodeWithContentDescription("Photo")
    .performTrackpadInput {
        click(center)

        pan(Offset(0f, 100f))

        scale(0.9f)
    }
Snippet do Kotlin em 25:08 em "Design de API para entrada indireta e novos formatos"
InputDevice.ViewBehavior
.getPrimaryDirectionalMotionAxis()
Snippet do Kotlin em 25:32 em "Design de API para entrada indireta e novos formatos"
// Glimmer Library
Box(
    modifier =
        Modifier.fillMaxSize()
            .onIndirectPointerGesture(
                enabled = true,
                onSwipeForward = { /* onSwipeForward */ },
                onSwipeBackward = { /* onSwipeBackward */ },
                onClick = { /* onClick */ },
            )
            .focusTarget()
) {
    // App()
Snippet do Kotlin aos 26:11 em "Como testar a entrada de ponteiro indireto"
@Test
clickTest {

rule.onNodeWithTag(defaultTag)
.performTouchInput
{ click(center) }

}
Snippet do Kotlin aos 27:08 em "Como testar a entrada de ponteiro indireto"
@Test
clickTest {

rule.onNodeWithTag(defaultTag)
// .performTouchInput
.performIndirectPointerInput (
IndirectPointerEventPrimaryDirectionalMotionAxis.X,
inputDeviceSize,
)
{ click(center) }
}
Snippet do Kotlin aos 27:23 em "Como testar a entrada de ponteiro indireto"
@Test
clickTest {

rule.onNodeWithTag(defaultTag)
// .performTouchInput
.performIndirectPointerInput
{ click(center) }

}