Esta é a terceira postagem da nossa série sobre navegação por gestos. Se você quiser acessar outra publicação, verifique a lista abaixo:
Na postagem anterior, finalizamos a conversa sobre como arrastar para as bordas. Nesta terceira publicação, falaremos sobre como processar todos os conflitos de gestos entre seu aplicativo e os novos gestos do sistema no Android 10.
O que são os conflitos de gestos? Vejamos um exemplo. Aqui temos um aplicativo de reprodução de música que permite ao usuário arrastar a SeekBar para passar por diferentes pontos da faixa que está sendo tocada.
Infelizmente, a SeekBar está muito perto da área de gestos da tela inicial. Isso aciona o gesto de troca rápida do sistema, o que confunde o usuário.
O mesmo pode ocorrer em qualquer borda da tela com áreas de gestos. Há diversos exemplos comuns do que pode causar conflitos, como por exemplo: Gavetas de navegação (DrawerLayout), carrosséis (ViewPager), controles deslizantes (SeekBar) e ações ao deslizar nas listas.
Isso nos leva à pergunta: como podemos corrigir esse problema? Para ajudar com essa questão, criamos um fluxograma que orientará uma das soluções.
Esperamos que as perguntas sejam autoexplicativas, mas caso você tenha dúvidas, responderemos a cada uma delas:
A primeira pergunta serve para saber se o caso de uso principal do seu aplicativo exige ocultação das barras de status e/ou navegação. Ocultá-las significa tornar essas barras do sistema completamente invisíveis. Isso não significa que seu aplicativo ocupa o espaço de ponta a ponta ou algo semelhante.
Estes são alguns dos possíveis motivos para responder essa pergunta de forma afirmativa:
Alguns exemplos comuns de aplicativos que respondem afirmativamente a essa pergunta são jogos, players de vídeo, visualizadores de fotos e aplicativos de desenho.
Ao responder essa pergunta, é possível saber se a IU contém elementos nas/próximo às zonas de gestos (voltar ou página inicial) que exigem que o usuário deslize os dedos sobre elas.
No caso de jogos, normalmente a resposta será “Sim”, já que:
Além dos jogos, estes são alguns exemplos comuns de IU que responderão de forma afirmativa a essa pergunta:
Esperamos que essa pergunta seja simples. Isso também inclui visualizações que cobrem a área de gestos e se estendem além da tela, como um DrawerLayout ou ViewPager grande.
Aqui, saímos um pouco do assunto sobre tato e começamos a abordar as visualizações individuais. No caso das visualizações em que você respondeu “Sim” para a pergunta 2, há alguma que exija que o usuário use os comandos de deslizar/arrastar sobre ela?
Há diversos exemplos em que a resposta para essa pergunta seria “Sim”: SeekBars, páginas na parte inferior ou até mesmo um PopupMenu (é possível arrastá-los para abrir).
Após respondermos à pergunta 4, agora queremos saber se a visualização completa ou a maior parte dela está disposta em uma área de gestos.
Caso sua visualização seja um contêiner rolável, como um RecyclerView, aborde essa questão de forma diferente: a visualização completa ou a maior parte dela está disposta em áreas de gestos em todas as posições de rolagem? Se o usuário puder rolar a visualização para fora da área de gestos, você não precisa fazer nada.
No gráfico acima, ao ver o exemplo em que carrosséis completos (ViewPager) responderam negativamente a essa pergunta, é possível que você queira saber por que não há processamento nesse caso. Isso acontece porque as zonas de gestos esquerda/direita têm larguras comparativamente pequenas (o padrão é 20 dp cada) se comparadas à visualização. Normalmente, a largura da tela do smartphone na orientação retrato é de aproximadamente 360 dp, deixando por volta de 320 dp de largura para a tela visível em que o usuário pode deslizar os dedos livremente (ou seja, praticamente 90%). Mesmo com o preenchimento e as margens internas, o usuário ainda poderá deslizar normalmente o carrossel.
A última pergunta indaga se a visualização está disposta sobre alguma zona de gestos obrigatória. Na nossa postagem anterior do blog, dissemos que as zonas obrigatórias são as áreas da tela em que os gestos do sistema sempre recebem prioridade.
O Android 10 tem apenas uma zona de gestos obrigatória, localizada na parte inferior da tela. Ela permite que o usuário acesse a página inicial ou exiba os aplicativos recentes. Isso poderá mudar nas versões futuras da plataforma, mas por enquanto só precisamos pensar nas visualizações da parte inferior da tela.
Estes são alguns exemplos comuns:
Agora que abordamos as perguntas, esperamos que você tenha chegado a uma das soluções. Portanto, veremos os detalhes de cada uma delas.
Começaremos com a “solução” mais fácil: simplesmente... nadaa fazer! 🙌
Talvez ainda haja otimizações que você pode fazer (veja a seção abaixo). No entanto, é esperado que não ocorram grandes problemas no uso do seu aplicativo com o modo de navegação por gestos ativado.
Se o gráfico apontou esse resultado, mas você ainda acha que há um problema, entre em contato conosco. Podemos ter deixado algo passar.
Como vimos na postagem anterior do blog, os encartes são enviados para informar ao seu aplicativo onde as zonas de gestos do sistema estão localizadas na tela. Um método que pode ser usado para resolver conflitos de gestos é remover todas as visualizações conflitantes das zonas de gestos. Isso é especialmente importante no caso das visualizações próximas à parte inferior da tela, uma vez que essa área á uma zona de gestos obrigatória, e os aplicativos não podem usar as APIs de exclusão nela.
Vejamos um exemplo. Esta é a IU do player de música que vimos anteriormente. Ela contém uma SeekBar posicionada na parte inferior da tela, o que permite ao usuário passar por diferentes pontos da faixa.
No entanto, quando o usuário tenta passar para outro ponto da música, ocorre a seguinte situação:
Isso ocorre porque a zona de gestos inferior está sobreposta à SeekBar. Assim, o gesto da página inicial recebe a prioridade. Confira as zonas de gestos visualmente:
A solução mais simples para esse caso é adicionar outras margens ou preenchimento para que a SeekBar seja deslocada para cima, fora da zona de gestos. Algo assim:
Se arrastarmos a SeekBar conforme esse exemplo, você perceberá que o gesto da página inicial não será mais ativado:
Para implementar essa solução, precisamos usar os novos encartes de gestos do sistema, disponíveis na API 29 e a Biblioteca principal Jetpack v1.2.0 (atualmente em Alfa). No exemplo, aumentamos o preenchimento inferior da SeekBar para corresponder ao valor do encarte de gesto inferior:
Pode ser interessante a você ler outra postagem do blog em que exploramos algumas maneiras de facilitar o uso dos WindowInsets:
WindowInsets: listeners para layouts
Neste momento, é capaz que você pense: “tarefa concluída”. E realmente essa pode ser a solução final para alguns layouts. Mas no nosso exemplo, a IU regrediu com muito desperdício de espaço abaixo da SeekBar. Portanto, em vez de preencher a visualização para cima, podemos retrabalhar o layout para evitar o espaço desperdiçado:
Aqui, movemos a SeekBar para a parte superior da barra de reprodução, completamente fora da zona de gestos. Isso significa que não é mais necessário preencher ou aumentar a altura da barra para acomodar a SeekBar.
No entanto, precisamos fazer esse preenchimento/aumento pela altura da barra do sistema para que o texto não fique encoberto. Esse assunto foi abordado na nossa segunda postagem do blog sobre “Processamento de sobreposições visuais”.
Na nossa postagem anterior do blog, mencionamos que “os aplicativos têm a capacidade de excluir os gestos do sistema para determinadas partes da tela”. Os aplicativos fazem isso por meio das APIs de exclusão de gestos, uma novidade do Android 10.
O sistema fornece duas funções diferentes para excluir áreas: View.setSystemGestureExclusionRects() e Window.setSystemGestureExclusionRects(). A opção usada dependerá do seu aplicativo: se você usar a Visualização do Android, o sistema dará preferência à API de visualização. Caso contrário, use a API Window.
A principal diferença entre as duas APIs é que a API Window espera que todos os retângulos estejam no espaço de coordenadas da janela. Se você estiver usando visualizações, normalmente estará trabalhando no espaço de coordenada da visualização. A API de visualização cuida da conversão entre espaços de coordenadas, o que significa que você só precisa pensar em termos do conteúdo da visualização.
Vejamos um exemplo. Usaremos novamente o exemplo do player de música, com a SeekBar disposta em toda a largura da tela. Na seção anterior, corrigimos o fato da SeekBar acionar o gesto da página inicial. No entanto, ainda temos as zonas de gestos direita e esquerda para tratarmos.
Vejamos o que acontece quando o usuário tenta passar para outro ponto da música enquanto o ícone da SeekBar (o círculo para arrastar) está posicionado próximo a uma das bordas:
Como o ícone está sob a área de gestos à direita, o sistema entende que o usuário está realizando o gesto para voltar e mostra a seta para trás. Isso confunde o usuário, que provavelmente não queria voltar. Para corrigir isso, podemos usar as APIs de exclusão de gestos citadas acima e excluir os limites do ícone.
Há dois lugares de onde essas APIs geralmente são chamadas: onLayout() quando sua visualização está disposta e onDraw() quando ela está desenhada. Sua visualização passa em uma List<Rect> que contém todos os retângulos a serem excluídos. Como mencionado anteriormente, esses retângulos precisam estar no sistema de coordenadas da própria visualização.
Normalmente, você criaria uma função semelhante a esta, que seria chamada do onLayout() e/ou do onDraw():
Após essa adição, ao pular partes da música próximo às bordas funciona conforme o esperado:
Veja o exemplo acima. A SeekBar faz isso automaticamente para você no Android 10, então não é necessário fazer isso por si só. Esse é apenas um exemplo para mostrar o padrão geral.
Embora as APIs de exclusão de gestos possam parecer a solução perfeita para corrigir todos os conflitos desse tipo, elas não são. Ao usar essas APIs, você está declarando que o gesto do seu aplicativo é mais importante do que a ação do sistema de voltar. Essa é uma afirmação forte, e é por isso que essa API tem a finalidade de ser uma opção a ser usada em último caso.
Ao usar essas APIs, você está declarando que o gesto do seu aplicativo é mais importante do que a ação do sistema de voltar.
Como o comportamento ativado pela API causa interrupção ao usuário, o sistema limita a forma como ela é usada: os aplicativos podem excluir apenas até 200 dp por borda.
Estas são algumas perguntas comuns dos desenvolvedores ao receberem essa informação:
Por que ter um limite? Esperamos que a explicação acima possa servir como dica para a resposta. Acreditamos que poder deslizar da borda para voltar de maneira consistente seja muito importante aos usuários. De forma consistente em todo o dispositivo, não em um único aplicativo. O limite pode parecer restritivo, mas basta um aplicativo excluir uma borda inteira da tela para confundir o usuário, levando à desinstalação do aplicativo ou a algo mais drástico.
Ou seja, a navegação do sistema precisa ser sempre consistente e utilizável.
Por que 200 dp? O pensamento por trás dos 200 dp é muito simples. Como mencionamos anteriormente, as APIs de exclusão de gestos são como uma última opção, portanto o limite foi calculado como um múltiplo de alvos importantes de toque. O tamanho mínimo recomendado para um alvo de toque é de 48 dp. 4 alvos de toque × 48 dp = 192 dp. Adicione um pouco de preenchimento e chegaremos ao nosso valor de 200 dp.
E se eu solicitar a exclusão de mais de 200 dp em uma borda? O sistema atenderá somente os 200 dp inferiores solicitados por você.
Minha visualização está fora da tela, ela entra na contagem do limite? Não, o sistema conta somente retângulos excluídos que estejam nos limites da tela. Da mesma forma, se a visualização estiver parcialmente na tela, apenas a parte visível do retângulo solicitado será contada.
Você pode ter chegado até aqui e está se perguntando por que não falamos sobre o lado direito do fluxograma 🤔. Essas soluções são específicas para aplicativos que precisam ser exibidos em toda a tela. Elas serão abordadas na próxima postagem do blog, que já está disponível 👇
Navegação por gestos: Modos imersivos (IV)
A tecnologia do Google Cloud ajuda tanto nossos clientes quanto nossas equipes internas. Recentemente, a equipe de arquitetos de soluções decidiu mover um processo interno para usar o BigQuery com o objetivo de otimizar e concentrar melhor os esforços em toda a equipe.
Eles publicaram os guias de referência para que os clientes possam usá-los durante a criação de aplicativos no Google Cloud. Nosso processo de publicação tem várias etapas, incluindo aprovação de esboço, rascunho, revisão dos colegas, edição técnica, revisão jurídica, aprovação de RP e, por fim, a publicação no nosso site. Esse processo envolve a colaboração entres as equipes de edição técnica, jurídico e RP.
Com tantas etapas e pessoas envolvidas, é importante que colaboremos efetivamente. Nossa equipe usa uma ferramenta de colaboração executada no Google Cloud Platform (GCP) como um repositório central e fluxo de trabalho para nossos guias de referência.
À medida que nossa equipe de arquitetos de soluções crescia e nossas necessidades de relatórios se tornavam mais sofisticadas, percebemos que não podíamos fornecer efetivamente os insights de que precisávamos diretamente na nossa ferramenta de colaboração existente. Por exemplo, precisávamos criar e compartilhar painéis de status dos nossos guias de referência, criar um roteiro para os próximos trabalhos e analisar quanto tempo nossas soluções demoram para serem publicadas, desde a aprovação do esboço até a publicação. Também era necessário compartilhar essas informações fora da equipe, mas sem compartilhar informações desnecessárias, concedendo amplo acesso a toda a nossa instância de colaboração.
Como nossa ferramenta de colaboração oferece uma REST API robusta e flexível, decidimos escrever um script de exportação que armazenasse os resultados no BigQuery. O BigQuery foi escolhido porque sabíamos que seria possível escrever consultas avançadas nos dados e, em seguida, usar o Data Studio para criar os painéis. O uso do BigQuery para análise forneceu uma solução escalonável bem integrada a outras ferramentas de GCP e compatível com inserções em lote e em tempo real por meio da API de streaming.
Usamos um script Python simples para ler os problemas da API e inserir as entradas no BigQuery por meio do método da API de streaming. Escolhemos a API de streaming, em vez do Cloud Pub/Sub ou do Cloud Dataflow, porque queríamos preencher novamente o conteúdo do BigQuery com os dados mais recentes várias vezes ao dia. A biblioteca do cliente API Python do Google foi uma escolha óbvia, porque ela oferece uma maneira idiomática de interagir com as APIs do Google, incluindo a API de streaming do BigQuery.
Como esses dados seriam usados somente para fins de relatório, optamos por manter apenas a versão mais recente dos dados extraída. Tivemos duas razões para essa decisão:
Seguimos as práticas recomendadas comuns de extrair, transformar e carregar (ETL), com uma tabela de preparação e uma tabela de produção separada para que pudéssemos carregar dados na tabela de preparação sem afetar os usuários dos dados. O design criado com base nas práticas recomendadas de ETL exigia a exclusão de todos os registros da tabela de preparação. Além disso, era necessário carregá-la e substituir a tabela de produção pelo conteúdo.
Ao usar a API de streaming, o buffer de streaming do BigQuery permanece ativo por cerca de 30 a 60 minutos ou mais após o uso. Isso significa que não é possível excluir nem alterar os dados durante esse período. Como usamos a API de streaming, programamos o carregamento a cada três horas para equilibrar a inserção rápida de dados no BigQuery e poder excluir os dados subsequentemente da tabela de preparação durante o processo de carregamento.
Após os dados estarem no BigQuery, seria possível escrever consultas SQL diretamente neles ou usar qualquer uma das diversas ferramentas integradas disponíveis para analisá-los. Para visualização, escolhemos o Data Studio, porque ele é bem integrado ao BigQuery, oferece recursos personalizáveis do painel e a capacidade de colaborar, além de ser gratuito, é claro.
Como os conjuntos de dados do BigQuery podem ser compartilhados com os usuários, a usabilidade dos dados foi aberta para quem recebeu acesso e autorização apropriada. Com isso, também poderíamos combinar esses dados no BigQuery com outros conjuntos de dados. Por exemplo, rastreamos as métricas de engajamento on-line dos nossos guias de referência e as carregamos no BigQuery. Com os dois conjuntos de dados no BigQuery, foi fácil levar em consideração os números de engajamento on-line para criar os painéis.
Um dos principais motivos para querermos criar relatórios no nosso processo de publicação é acompanhar esse processo ao longo do tempo. O Data Studio facilitou a criação de painéis com gráficos, semelhantes aos dois que podem ser vistos abaixo. A criação do painel no Data Studio nos permitiu analisar facilmente nossas métricas de publicação ao longo do tempo e depois compartilhar os painéis específicos com outras equipes além da nossa.
O monitoramento é uma parte importante de qualquer canal ETL. O Stackdriver Monitoring oferece monitoramento, alertas e painéis para ambientes do GCP. Optamos por usar o módulo Google Cloud Logging no script de carregamento do Python porque isso geraria registros de erros no Stackdriver Logging e eles poderiam ser usados para alertar sobre erros no Stackdriver Monitoring. Configuramos um espaço de trabalho do Stackdriver Monitoring especificamente para o projeto com o processo de carregamento. Em seguida, criamos um painel de gerenciamento para rastrear qualquer erro de aplicativo. Configuramos alertas para enviar uma notificação por SMS sempre que erros aparecessem nos arquivos de registro do processo de carregamento. Veja os painéis no espaço de trabalho do Stackdriver:
Estes são os detalhes dos alertas que configuramos:
O BigQuery oferece a flexibilidade para você atender às suas necessidades comerciais ou analíticas, sejam elas de tamanho de petabyte ou não. A API de streaming do BigQuery significa que você pode transmitir dados diretamente no BigQuery e fornecer aos usuários finais acesso rápido aos dados. O Data Studio oferece uma integração fácil de usar com o BigQuery, que simplifica o desenvolvimento de painéis avançados. A abordagem de custo por consulta significa que você pagará pelo que armazenar e analisar, embora o BigQuery também ofereça preços fixos, caso você tenha um número alto de consultas grandes. O uso do BigQuery permitiu à nossa equipe ter novos insights consideráveis sobre o processo de publicação. Isso nos ajudou a refinar esse processo e concentrar maiores esforços nos tópicos técnicos mais conhecidos.
Se ainda não tiver visto, confira o que o BigQuery pode fazer com os conjuntos de dados públicos do BigQuery e consulte o que mais você pode fazer com o GCP nos nossos guias de referência.