Tutoriais

Apresentamos o Cahier: um novo exemplo do Android GitHub para produtividade e criatividade em telas grandes

Leitura de 11 minutos
Chris Assigbe
Engenheira de relações com desenvolvedores

A API Ink está na versão Beta e pronta para ser integrada ao seu app. Esse marco foi possível graças ao feedback valioso dos desenvolvedores, que levou a melhorias contínuas na performance, na estabilidade e na qualidade visual da API.

Os apps do Google, como Documentos Google, Pixel Studio, Google Fotos, Chrome PDF, Criador de efeitos do YouTube e recursos exclusivos no Android, como Circule para pesquisar, usam as APIs mais recentes. 

Para marcar esse marco, temos o prazer de anunciar o lançamento do Cahier, um exemplo de app abrangente para fazer anotações otimizado para dispositivos Android de todos os tamanhos, principalmente tablets e smartphones dobráveis.

O que é o Cahier?

Cahier ("caderno" em francês) é um app de exemplo criado para demonstrar como criar um aplicativo que permite aos usuários capturar e organizar ideias combinando texto, desenhos e imagens. 

A amostra pode servir como referência para aumentar a produtividade e a criatividade do usuário em telas grandes. Ele mostra as práticas recomendadas para criar essas experiências, acelerando a compreensão e a adoção de APIs e técnicas avançadas relacionadas pelos desenvolvedores. Esta postagem mostra os principais recursos do Cahier, as APIs importantes e as decisões arquitetônicas que tornam o exemplo uma ótima referência para seus próprios apps.

Os principais recursos demonstrados na amostra incluem:

  • Criação versátil de notas:mostra como implementar um sistema flexível de criação de conteúdo que oferece suporte a vários formatos em uma única nota, incluindo texto, desenhos livres e anexos de imagens.
  • Ferramentas de tinta criativas: implementa uma experiência de desenho de alto desempenho e baixa latência usando a API Ink. O exemplo mostra como integrar vários pincéis, um seletor de cores, a funcionalidade de desfazer/refazer e uma ferramenta de borracha.
  • Integração de conteúdo fluida com arrastar e soltar: demonstra como processar conteúdo de entrada e saída usando arrastar e soltar. Isso inclui aceitar imagens soltas de outros apps e permitir que os usuários arrastem conteúdo para fora do seu app para compartilhar sem problemas.
  • Organização de notas: marque notas como favoritas para acesso rápido. Filtre a visualização para manter tudo organizado.
  • Arquitetura que prioriza o modo off-line:criada com uma arquitetura que prioriza o modo off-line usando o Room, garantindo que todos os dados sejam salvos localmente e que o app permaneça totalmente funcional sem uma conexão de Internet.
  • Suporte avançado para várias janelas e várias instâncias: mostra como oferecer suporte a várias instâncias, permitindo que o app seja iniciado em várias janelas para que os usuários possam trabalhar em diferentes notas lado a lado, aumentando a produtividade e a criatividade em telas grandes.
  • Interface adaptável para todas as telas: a interface do usuário se adapta perfeitamente a diferentes tamanhos e orientações de tela usando ListDetailPaneScaffold e NavigationSuiteScaffold para oferecer uma experiência do usuário otimizada em smartphones, tablets e dispositivos dobráveis.
  • Integração profunda com o sistema: fornece um guia sobre como tornar seu app o app de anotações padrão no Android 14 e versões mais recentes respondendo a intents de anotações em todo o sistema, permitindo a captura rápida de conteúdo de vários pontos de entrada do sistema.

Criado para produtividade e criatividade em telas grandes

No lançamento inicial, vamos focar em alguns recursos principais que tornam o Cahier um recurso de aprendizado essencial para casos de uso de produtividade e criatividade.

Uma base de adaptabilidade

O Cahier foi criado para ser adaptável desde o início. A amostra usa a biblioteca material3-adaptive, especificamente ListDetailPaneScaffold e NavigationSuiteScaffold, para adaptar o layout do app a vários tamanhos e orientações de tela. Esse é um elemento crucial para um app Android moderno, e o Cahier oferece um exemplo claro de como implementá-lo de maneira eficaz.

Interface adaptável do Cahier criada com a biblioteca adaptável do Material 3..gif

Interface adaptável do Cahier criada com a biblioteca adaptável do Material 3

Mostrando as principais APIs e integrações

O exemplo se concentra em mostrar APIs de produtividade eficientes que podem ser usadas nos seus próprios aplicativos, incluindo:

Um olhar mais atento às principais APIs

Vamos nos aprofundar em duas das APIs fundamentais que o Cahier integra para oferecer uma experiência de anotações de primeira classe.

Como criar experiências de tinta natural com a API Ink

A entrada com stylus transforma dispositivos de tela grande em notebooks e blocos de desenho digitais. Para ajudar você a criar experiências de tinta fluidas e naturais, transformamos a API Ink em um elemento fundamental da amostra. A API Ink facilita a criação, a renderização e a manipulação de traços de tinta incríveis com a melhor baixa latência da categoria.

A API Ink oferece uma arquitetura modular para que você possa adaptá-la à pilha e às necessidades específicas do seu app. Os módulos da API incluem:

  • Módulos de criação (Composeviews): processe a entrada de tinta em tempo real para criar traços suaves com a menor latência possível em um dispositivo.
    • Em DrawingSurface, o Cahier usa o elemento combinável InProgressStrokes, recém-introduzido, para processar entradas de toque ou stylus em tempo real. Esse módulo é responsável por capturar eventos de ponteiro e renderizar traços de tinta úmida com a menor latência possível.
  • Módulo Strokes:representa a entrada de tinta e a representação visual dela. Quando um usuário termina de desenhar uma linha, o callback onStrokesFinished fornece um objeto Stroke finalizado/seco ao app. Esse objeto imutável, que representa o traço de tinta concluído, é gerenciado em DrawingCanvasViewModel.
  • Módulo de renderização:mostra traços de tinta de maneira eficiente, permitindo que eles sejam combinados com o Compose do Jetpack ou com as visualizações do Android.
  • Módulos de pincel (Composeviews): oferecem uma maneira declarativa de definir o estilo visual dos traços. As atualizações recentes (desde a versão alpha03) incluem um novo pincel de linha tracejada, especialmente útil para recursos como a seleção de laço. O DrawingCanvasViewModel contém o estado do currentBrush. Uma caixa de ferramentas no DrawingCanvas permite que os usuários selecionem diferentes famílias de pincéis (como StockBrushes.pressurePen() ou StockBrushes.highlighter()) e mudem as cores. O ViewModel atualiza o objeto Brush, que é usado pelo elemento combinável InProgressStrokes para novos traços.
  • Módulos de geometria (Composeviews): oferecem suporte à manipulação e análise de traços para recursos como apagar e selecionar.
    • A ferramenta de borracha na caixa de ferramentas e a funcionalidade em DrawingCanvasViewModel dependem do módulo de geometria. Quando a borracha está ativa, ela cria um MutableParallelogram ao redor do caminho do gesto do usuário. Em seguida, a borracha verifica as interseções entre o formato e as caixas delimitadoras dos traços existentes para determinar quais traços apagar, tornando a borracha intuitiva e precisa.
  • Módulo Storage:oferece recursos eficientes de serialização e desserialização para dados de tinta, resultando em economia significativa de tamanho de disco e rede. Para salvar desenhos, o Cahier mantém os objetos Stroke no banco de dados Room. Em Converters, a amostra usa a função encode do módulo de armazenamento para serializar o StrokeInputBatch (os dados de pontos brutos) em um ByteArray. O array de bytes, junto com as propriedades do pincel, é salvo como uma string JSON. A função decode é usada para reconstruir os traços quando uma nota é carregada.
orion.png

Além desses módulos principais, as atualizações recentes ampliaram os recursos da API Ink:

  • As novas APIs experimentais para objetos BrushFamily personalizados permitem que os desenvolvedores criem tipos de pincel criativos e exclusivos, oferecendo possibilidades para ferramentas como os pincéis Lápis e Ponteiro laser.

O Cahier usa pincéis personalizados, incluindo o pincel de música exclusivo mostrado abaixo, para ilustrar possibilidades criativas avançadas.

Laser arco-íris criado com pincéis personalizados da API Ink..gif

Laser arco-íris criado com pincéis personalizados da API Ink

notes.png

Pincel de música criado com os pincéis personalizados da API Ink

  • Os módulos de interoperabilidade nativa do Jetpack Compose simplificam a integração de funcionalidades de tinta diretamente nas UIs do Compose para uma experiência de desenvolvimento mais idiomática e eficiente.

A API Ink oferece várias vantagens que a tornam a escolha ideal para apps de produtividade e criatividade em vez de uma implementação personalizada:

  • Facilidade de uso:a API Ink abstrai as complexidades de gráficos e geometria, permitindo que você se concentre nos recursos principais do Cahier.
  • Performance:o suporte integrado de baixa latência e a renderização otimizada garantem uma experiência de tinta suave e responsiva.
  • Flexibilidade:o design modular permite escolher os componentes necessários, o que possibilita a integração perfeita da API Ink à arquitetura do Cahier.

A API Ink já foi adotada em muitos apps do Google, incluindo marcações no Documentos, Circule para pesquisar e apps parceiros, como o Orion Notes e o PDF Scanner.

"A API Ink foi nossa primeira escolha para o recurso Circule para pesquisar (CtS). Usando a extensa documentação, a integração da API Ink foi muito fácil, permitindo que alcançássemos nosso primeiro protótipo funcional em apenas uma semana. O suporte a animação e textura de pincel personalizada do Ink nos permitiu iterar rapidamente no design do traço." - Jordan Komoda, engenheiro de software do Google

Como se tornar o app de notas padrão com a função de anotações

A criação de anotações é um recurso importante que melhora a produtividade do usuário em dispositivos de tela grande. Com o recurso de função de notas, os usuários podem acessar seus apps compatíveis na tela de bloqueio ou enquanto outros apps estão em execução. Esse recurso identifica e define apps de anotações padrão em todo o sistema, concedendo a eles permissão para serem iniciados e capturar conteúdo. 

Implementação no Cahier

A implementação da função de anotações envolve algumas etapas importantes, todas demonstradas no exemplo:

  1. Declaração do manifesto: primeiro, o app precisa declarar a capacidade de processar intents de anotações. No AndroidManifest.xml, o Cahier inclui um <intent-filter> para a ação android.intent.action.CREATE_NOTE. Isso sinaliza ao sistema que o app é um possível candidato para a função de anotações.
  2. Verificação do status da função: o SettingsViewModel usa o RoleManager do Android para determinar o status atual. O SettingsViewModel verifica se a função de notas está disponível no dispositivo (isRoleAvailable) e se o Cahier tem essa função no momento (isRoleHeld). Esse estado é exposto à interface usando fluxos do Kotlin.
  3. Solicitar a função: no arquivo Settings.kt, um Button é mostrado ao usuário se a função estiver disponível, mas não atribuída. Quando clicado, o botão chama a função requestNotesRole no ViewModel. A função cria uma intent para abrir a tela de configurações padrão do app, em que o usuário pode selecionar o Cahier. O processo é gerenciado usando a API rememberLauncherForActivityResult, que processa o lançamento da intent e o recebimento do resultado.
  4. Atualizar a interface: depois que o usuário volta da tela de configurações, o callback ActivityResultLauncher aciona uma função no ViewModel para atualizar o status da função, garantindo que a interface reflita com precisão se o app agora é o padrão.

Saiba como integrar a função de anotações no seu app no guia de criação de um app de anotações.

helloworld.png

O Cahier foi lançado em uma janela flutuante como o app padrão para fazer anotações em um tablet Lenovo

Um grande avanço: a Lenovo ativa a função de notas

Temos o prazer de anunciar um grande avanço na produtividade do Android em telas grandes: a Lenovo ativou o suporte à função de notas em tablets com Android 15 e versões mais recentes. Com essa atualização, agora é possível atualizar os apps de anotações para permitir que usuários com dispositivos Lenovo compatíveis os definam como padrão, concedendo acesso direto na tela de bloqueio e desbloqueando recursos de captura de conteúdo no nível do sistema.

Esse compromisso de um OEM líder demonstra a crescente importância das notas para oferecer uma experiência do usuário verdadeiramente integrada e produtiva no Android. 

Várias instâncias, várias janelas e modo Janela para computador

A produtividade em uma tela grande depende do gerenciamento eficiente de informações e fluxos de trabalho. Por isso, o Cahier foi criado para aproveitar ao máximo os recursos avançados de janelas do Android, oferecendo um espaço de trabalho flexível que se adapta às necessidades do usuário. O app é compatível com:

  • Várias janelas: a capacidade fundamental de executar ao lado de outro app no modo de tela dividida ou formato livre. Isso é essencial para tarefas como consultar uma página da Web enquanto faz anotações no Cahier.
  • Várias instâncias: é aqui que o verdadeiro multitarefas se destaca. O Cahier permite que os usuários abram várias janelas independentes do app ao mesmo tempo. Imagine comparar duas notas diferentes lado a lado ou consultar uma nota de texto em uma janela enquanto trabalha em um desenho em outra. O Cahier demonstra como gerenciar essas instâncias separadas, cada uma com seu próprio estado, transformando seu app em uma ferramenta poderosa e multifacetada.
  • Modo Janela para Computador: quando conectado a uma tela externa, o modo área de trabalho do Android transforma um tablet ou dispositivo dobrável em uma estação de trabalho. Como o Cahier foi criado com uma interface adaptável e oferece suporte a várias instâncias, o app funciona muito bem nesse ambiente. Os usuários podem abrir, redimensionar e posicionar várias janelas do Cahier como em um computador tradicional, permitindo fluxos de trabalho complexos que antes eram inacessíveis em dispositivos móveis.
cahier-desktop-windowing.webp

Cahier em execução no modo de janela da área de trabalho no Pixel Tablet

Veja como implementamos esses recursos no Cahier:

Para ativar várias instâncias, primeiro precisamos sinalizar ao sistema que o app pode ser iniciado várias vezes. Para isso, adicionamos a propriedade PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI à declaração de MainActivity em AndroidManifest:

  <activity

    android:name="com.example.cahier.MainActivity"

    android:exported="true"

    android:label="@string/app_name"

    android:theme="@style/Theme.MyApplication"

    android:showWhenLocked="true"

    android:turnScreenOn="true"

    android:resizeableActivity="true"

    android:launchMode="singleInstancePerTask">


    <property

        android:name="android.window.PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI"

        android:value="true"/>

    ...

</activity>

Em seguida, implementamos a lógica para iniciar uma nova instância do app. Em CahierHomeScreen.kt, quando um usuário opta por abrir uma nota em uma nova janela, criamos uma nova intent com flags específicas que instruem o sistema sobre como lidar com o início da nova atividade. A combinação de FLAG_ACTIVITY_NEW_TASK, FLAG_ACTIVITY_MULTIPLE_TASK e FLAG_ACTIVITY_LAUNCH_ADJACENT garante que a observação seja aberta em uma nova janela separada ao lado da atual.

  fun openNewWindow(activity: Activity?, note: Note) {

    val intent = Intent(activity, MainActivity::class.java)

    intent.putExtra(AppArgs.NOTE_TYPE_KEY, note.type)

    intent.putExtra(AppArgs.NOTE_ID_KEY, note.id)

    intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_MULTIPLE_TASK or

        Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT


    activity?.startActivity(intent)

}

Para oferecer suporte ao modo de várias janelas, precisamos sinalizar ao sistema que o app é redimensionável definindo o elemento <activity> ou <application> do manifesto.

  <activity

    android:name="com.example.cahier.MainActivity"

    android:resizeableActivity="true"

    ...>

</activity>

A interface criada com a biblioteca adaptativa do Material 3 permite que ela se adapte perfeitamente em cenários de várias janelas, como o modo de tela dividida do Android. 

Para melhorar a experiência do usuário, adicionamos suporte para arrastar e soltar. Confira abaixo como implementamos isso no Cahier.

Arrastar e soltar

Um app realmente produtivo ou criativo não funciona isoladamente. Ele interage perfeitamente com o restante do ecossistema do dispositivo. O recurso arrastar e soltar é fundamental para essa interação, especialmente em telas grandes, em que os usuários costumam trabalhar em várias janelas de apps. O Cahier adota totalmente essa ideia ao implementar uma funcionalidade intuitiva de arrastar e soltar para adicionar e compartilhar conteúdo.

  • Importação fácil: os usuários podem arrastar imagens de outros aplicativos, como um navegador da Web, uma galeria de fotos ou um gerenciador de arquivos, e soltá-las diretamente em uma tela de anotações. Para isso, o Cahier usa o modificador dragAndDropTarget para definir uma zona de soltar, verificar conteúdo compatível (como image/*) e processar o URI recebido.
  • Compartilhamento simples: o conteúdo do Cahier é tão fácil de compartilhar quanto o de outros apps. Os usuários podem tocar e pressionar uma imagem em uma observação de texto ou tocar e pressionar toda a tela de uma observação de desenho e composição de imagem e arrastar para outro aplicativo.

Aprofundamento técnico: arrastar da tela de desenho

Implementar o gesto de arrastar na tela de desenho apresenta um desafio único. Na nossa DrawingSurface, os elementos combináveis que processam a entrada de desenho em tempo real (o InProgressStrokes da API Ink) e o Box que detecta o gesto de pressionar e manter pressionado para iniciar uma ação de arrastar são elementos combináveis irmãos.

Por padrão, o sistema de entrada de ponteiro do Jetpack Compose é projetado para que apenas um elemento combinável irmão (o primeiro na ordem de declaração que se sobrepõe ao local do toque) receba o evento. No caso do Cahier, queremos que a lógica de processamento de entrada de arrastar e soltar tenha a chance de ser executada e possivelmente consumir entradas antes que o elemento combinável InProgressStrokes use todas as entradas não consumidas para desenho e depois as consuma. Se não organizarmos as coisas na ordem certa, a caixa não vai detectar o gesto de pressionar e manter pressionado para iniciar uma ação de arrastar, ou o InProgressStrokes não vai receber a entrada para desenhar.

Para resolver isso, criamos um modificador pointerInputWithSiblingFallthrough personalizado e colocamos nosso Box usando esse modificador antes de InProgressStrokes no código combinável. Essa utilidade é um wrapper simples ao redor do sistema padrão pointerInput, mas com uma mudança essencial: ela substitui a função sharePointerInputWithSiblings() para retornar true. Isso informa ao framework Compose para permitir que eventos de ponteiro passem para elementos combináveis irmãos, mesmo depois de serem consumidos.

  internal fun Modifier.pointerInputWithSiblingFallthrough(

    pointerInputEventHandler: PointerInputEventHandler

) = this then PointerInputSiblingFallthroughElement(pointerInputEventHandler)


private class PointerInputSiblingFallthroughModifierNode(

    pointerInputEventHandler: PointerInputEventHandler

) : PointerInputModifierNode, DelegatingNode() {


    var pointerInputEventHandler: PointerInputEventHandler

        get() = delegateNode.pointerInputEventHandler

        set(value) {

            delegateNode.pointerInputEventHandler = value

        }


    val delegateNode = delegate(

        SuspendingPointerInputModifierNode(pointerInputEventHandler)

    )


    override fun onPointerEvent(

        pointerEvent: PointerEvent,

        pass: PointerEventPass,

        bounds: IntSize

    ) {

        delegateNode.onPointerEvent(pointerEvent, pass, bounds)

    }


    override fun onCancelPointerInput() {

        delegateNode.onCancelPointerInput()

    }


    override fun sharePointerInputWithSiblings() = true

}


private data class PointerInputSiblingFallthroughElement(

    val pointerInputEventHandler: PointerInputEventHandler

) : ModifierNodeElement<PointerInputSiblingFallthroughModifierNode>() {


    override fun create() = PointerInputSiblingFallthroughModifierNode(pointerInputEventHandler)


    override fun update(node: PointerInputSiblingFallthroughModifierNode) {

        node.pointerInputEventHandler = pointerInputEventHandler

    }


    override fun InspectorInfo.inspectableProperties() {

        name = "pointerInputWithSiblingFallthrough"

        properties["pointerInputEventHandler"] = pointerInputEventHandler

    }

}

Veja como ele é usado no DrawingSurface:

  Box(

    modifier = Modifier

        .fillMaxSize()

        // Our custom modifier enables this gesture to coexist with the drawing input.

        .pointerInputWithSiblingFallthrough {

            detectDragGesturesAfterLongPress(

                onDragStart = { onStartDrag() },

                onDrag = { _, _ -> /* consume drag events */ },

                onDragEnd = { /* No action needed */ }

            )

        }

) 

// The Ink API's composable for live drawing sits here as a sibling.

InProgressStrokes(...)

Com isso, o sistema detecta corretamente os traços de desenho e o gesto de arrastar com toque longo simultaneamente. Depois que a ação de arrastar é iniciada, criamos um URI content:// compartilhável com FileProvider e transmitimos o URI para a estrutura de arrastar e soltar do sistema usando view.startDragAndDrop(). Essa solução garante uma experiência do usuário robusta e intuitiva, mostrando como superar conflitos complexos de gestos em interfaces em camadas.

Criado com arquitetura moderna

Além de APIs específicas, o Cahier demonstra padrões arquitetônicos cruciais para criar aplicativos adaptáveis e de alta qualidade.

A camada de apresentação: Jetpack Compose e adaptabilidade

A camada de apresentação é totalmente criada com o Jetpack Compose. Como mencionado, o Cahier adota a biblioteca material3-adaptive para adaptabilidade da interface. O gerenciamento de estado segue um padrão estrito de fluxo de dados unidirecional (UDF), com instâncias de ViewModel usadas como contêineres de dados que armazenam informações de notas e estado da interface.

A camada de dados: repositórios e Room

Para a camada de dados, o Cahier usa uma interface NoteRepository para abstrair todas as operações de dados. Essa escolha de design permite que o app troque entre uma fonte de dados local (Room) e um possível back-end remoto futuro. O fluxo de dados para uma ação como editar uma nota é simples:

  1. A interface do Jetpack Compose aciona um método no ViewModel.
  2. O ViewModel busca a observação do NoteRepository, processa a lógica e transmite a observação atualizada de volta ao repositório.
  3. O NoteRepository salva a atualização em um banco de dados do Room.

Suporte abrangente para digitação

Para ser um verdadeiro centro de produtividade, um app precisa lidar com vários métodos de entrada sem falhas. O Cahier foi criado para obedecer às diretrizes de entrada em telas grandes e oferece suporte a:

  • Stylus:integração com a API Ink, rejeição de toque da palma da mão, registro para a função de anotações, entrada de stylus em campos de texto e modo imersivo.
  • Teclado:suporte aos atalhos e combinações de teclado mais comuns (como ctrl+clique, meta+clique) e indicação clara do foco do teclado.
  • Mouse e trackpad:suporte para clique com o botão direito e estados de passar o cursor.

O suporte a interações avançadas de teclado, mouse e trackpad é um foco importante para melhorias futuras. 

Comece a usar hoje

Esperamos que o Cahier sirva como uma plataforma de lançamento para seu próximo app incrível. Ele foi criado para ser um recurso abrangente de código aberto que demonstra como combinar uma interface adaptável, APIs poderosas, como a Ink e a função de notas, e uma arquitetura moderna e adaptável.

Tudo pronto para mergulhar de cabeça?

  • Explore o código: acesse nosso repositório do GitHub para conhecer a base de código do Cahier e ver os princípios de design em ação.
  • Crie sob medida: use o Cahier como base para seu próprio aplicativo de anotações, marcação de documentos ou criativo.
  • Contribuir: suas contribuições são bem-vindas. Ajude a tornar o Cahier um recurso ainda melhor para a comunidade de desenvolvedores Android.

Confira os guias oficiais para desenvolvedores e comece a criar seu app de produtividade e criatividade de próxima geração hoje mesmo. Mal podemos esperar para saber o que você vai criar!

Escrito por:

Continuar lendo