Para começar a fornecer blocos, inclua as dependências abaixo no
arquivo build.gradle do app.
Groovy
dependencies { // Use to implement support for wear tiles implementation "androidx.wear.tiles:tiles:1.6.2" // Use to utilize standard components and layouts in your tiles implementation "androidx.wear.protolayout:protolayout:1.4.2" // Use to utilize components and layouts with Material Design in your tiles implementation "androidx.wear.protolayout:protolayout-material:1.4.2" // Use to include dynamic expressions in your tiles implementation "androidx.wear.protolayout:protolayout-expression:1.4.2" // Use to preview wear tiles in your own app debugImplementation "androidx.wear.tiles:tiles-renderer:1.6.2" // Use to fetch tiles from a tile provider in your tests testImplementation "androidx.wear.tiles:tiles-testing:1.6.2" }
Kotlin
dependencies { // Use to implement support for wear tiles implementation("androidx.wear.tiles:tiles:1.6.2") // Use to utilize standard components and layouts in your tiles implementation("androidx.wear.protolayout:protolayout:1.4.2") // Use to utilize components and layouts with Material Design in your tiles implementation("androidx.wear.protolayout:protolayout-material:1.4.2") // Use to include dynamic expressions in your tiles implementation("androidx.wear.protolayout:protolayout-expression:1.4.2") // Use to preview wear tiles in your own app debugImplementation("androidx.wear.tiles:tiles-renderer:1.6.2") // Use to fetch tiles from a tile provider in your tests testImplementation("androidx.wear.tiles:tiles-testing:1.6.2") }
Principais conceitos
Os blocos não são criados da mesma forma que os apps Android e usam conceitos diferentes:
- Modelos de layout: definem a disposição geral dos elementos visuais na
tela. Isso é feito pela função
primaryLayout(). - Elementos de layout: representam um elemento gráfico individual, como um botão ou card, ou vários desses elementos agrupados usando uma coluna, um buttonGroup ou algo semelhante. Eles são incorporados a um modelo de layout.
- Recursos: os objetos
ResourceBuilders.Resourcesconsistem em um mapa de pares de chave-valor dos recursos do Android (imagens) necessários para renderizar um layout e uma versão. - Linha do tempo: um objeto
TimelineBuilders.Timelineé uma lista de uma ou mais instâncias de um objeto de layout. É possível fornecer vários mecanismos e expressões para indicar quando o renderizador precisa alternar de um objeto de layout para outro, como para parar de mostrar um layout em um horário específico. - Estado: uma estrutura de dados do tipo
StateBuilders.Stateque é transmitida entre o bloco e o app para permitir que os dois componentes se comuniquem entre si. Por exemplo, se um botão for tocado no bloco, o estado vai conter o ID do botão. Também é possível trocar tipos de dados usando um mapa. - Bloco: um objeto
TileBuilders.Tileque representa um bloco, composto por uma linha do tempo, um ID da versão dos recursos, um intervalo de atualização e um estado. - Protolayout: esse termo aparece no nome de várias classes relacionadas a blocos e se refere à biblioteca Protolayout do Wear OS, uma biblioteca de gráficos usada em várias plataformas do Wear OS.
Criar um bloco
Usar Material3TileService (recomendado)
Ao criar blocos do Material 3 (androidx.wear.tiles:tiles:1.6.0 e versões mais recentes),
sempre estenda Material3TileService em vez da classe
TileService de nível mais baixo. O Material3TileService oferece uma arquitetura opinativa que prioriza o Kotlin e simplifica o desenvolvimento de blocos:
- Corrotinas integradas do Kotlin: implemente uma única função de suspensão
MaterialScope.tileResponse(requestParams: TileRequest): Tileem vez de usarListenableFuturee substituironTileRequest(). - Pré-configurado
MaterialScope:tileResponse()é executado comMaterialScopecomo receptor (configurado por parâmetros de construtor, comoallowDynamicThemeedefaultColorScheme). Portanto, não é necessário unir o layout em uma chamadamaterialScope()redundante. - Registro de recursos de chamada única: inclui
ProtoLayoutScopepara registrar automaticamente recursos de imagem inline e retornar o layout e os recursos do bloco em uma única chamada de binder do sistema. ComoMaterial3TileServiceprocessa recursos automaticamente (finalonTileResourcesRequest()), não substituaonTileResourcesRequest().
class MyTileService : Material3TileService() { override suspend fun MaterialScope.tileResponse(requestParams: TileRequest): Tile = tile( timeline = Timeline.fromLayoutElement( primaryLayout( mainSlot = { text("Hello, World!".layoutString, typography = BODY_LARGE) } ) ) ) }
Arquitetura subjacente do TileService
O Material3TileService é baseado na classe de base TileService,
que os outros exemplos deste guia usam para ilustrar conceitos de baixo nível.
Para fornecer um bloco pelo app, implemente um serviço do tipo TileService
e registre-o no manifesto. Com base nisso, o sistema solicita os blocos necessários durante as chamadas para onTileRequest() e os recursos durante as chamadas para onTileResourcesRequest().
class MyTileService : TileService() { override fun onTileRequest(requestParams: RequestBuilders.TileRequest) = Futures.immediateFuture( Tile.Builder() .setResourcesVersion(RESOURCES_VERSION) .setTileTimeline( Timeline.fromLayoutElement( materialScope(this, requestParams.deviceConfiguration) { primaryLayout( mainSlot = { text("Hello, World!".layoutString, typography = BODY_LARGE) } ) } ) ) .build() ) override fun onTileResourcesRequest(requestParams: ResourcesRequest) = Futures.immediateFuture(Resources.Builder().setVersion(RESOURCES_VERSION).build()) }
Em seguida, adicione um serviço dentro da tag <application> do arquivo
AndroidManifest.xml.
<service android:name=".snippets.m3.tile.MyTileService" android:label="@string/tile_label" android:description="@string/tile_description" android:icon="@mipmap/ic_launcher" android:exported="true" android:permission="com.google.android.wearable.permission.BIND_TILE_PROVIDER"> <intent-filter> <action android:name="androidx.wear.tiles.action.BIND_TILE_PROVIDER" /> </intent-filter> <meta-data android:name="androidx.wear.tiles.PREVIEW" android:resource="@drawable/tile_preview" /> </service>
O filtro de permissão e intent registra esse serviço como um provedor de blocos.
O ícone, o rótulo, a descrição e o recurso de visualização são mostrados ao usuário quando ele configura os blocos no smartphone ou smartwatch. O recurso de pré-lançamento é compatível com todos os qualificadores de recursos padrão do Android. Assim, é possível variar a prévia de acordo com fatores como tamanho da tela e idioma do dispositivo. Consulte a lista de verificação de pré-lançamento para mais recomendações.
Implante o app e adicione o Bloco ao carrossel de Blocos. Também há uma maneira mais fácil para desenvolvedores de visualizar um Bloco, mas, por enquanto, faça isso manualmente.
Para um exemplo completo, consulte o exemplo de código no GitHub ou o codelab.
Criar uma IU para blocos
Os elementos da interface do Material 3 Expressive são criados usando uma abordagem estruturada com tecnologia do padrão de builder com segurança de tipos do Kotlin.
Layout
Para orientações sobre os princípios de design por trás da criação de layouts de blocos eficazes e responsivos, consulte Layouts comuns para blocos.
Para criar o layout, faça o seguinte:
Inicie um escopo do Material Design: chame a função
materialScope(), fornecendo oscontextedeviceConfigurationnecessários. É possível incluir parâmetros opcionais, comoallowDynamicThemeedefaultColorScheme. OallowDynamicThemeétruepor padrão, e odefaultColorSchemerepresenta oColorSchemeusado quando as cores dinâmicas não estão disponíveis, como quando o usuário desativa o recurso, quando ele não é compatível com o dispositivo ou quandoallowDynamicThemeéfalse. Ao usar APIsImageResource, comobackgroundImage(), em umTileServicepersonalizado, chamematerialScopeWithResources().Crie sua interface dentro do escopo: todos os componentes de interface de um determinado layout de bloco precisam ser definidos dentro do lambda de uma única chamada materialScope() de nível superior. Essas funções de componente, como
primaryLayout()etextEdgeButton(), são funções de extensão emMaterialScopee só estão disponíveis quando chamadas nesse escopo do receptor.materialScope( context = context, deviceConfiguration = requestParams.deviceConfiguration, // requestParams is passed to onTileRequest defaultColorScheme = myFallbackColorScheme ) { // inside the MaterialScope, you can call functions like primaryLayout() primaryLayout( titleSlot = { text(text = "Title".layoutString) }, mainSlot = { text(text = "Main Content".layoutString) }, bottomSlot = { textEdgeButton( labelContent = { text("Action".layoutString) }, onClick = clickable() ) } ) }
Slots
No M3, o layout de blocos usa uma abordagem inspirada no Compose que utiliza três slots distintos. De cima para baixo, são eles:
- O
titleSlot, normalmente para um título ou cabeçalho principal. - O
mainSlot, para o conteúdo principal. - O
bottomSlot, geralmente usado para ações ou informações complementares. É também onde um botão de borda aparece.
O conteúdo de cada slot é o seguinte:
titleSlot(opcional): geralmente, algumas palavras geradas portext().mainSlot(obrigatório): componentes organizados em estruturas como linhas, colunas e grupos de botões. Esses componentes também podem ser incorporados de forma recursiva uns aos outros. Por exemplo, uma coluna pode conter linhas.bottomSlot(opcional): normalmente preenchido com um botão de borda ou um rótulo de texto.
Como não é possível rolar os blocos, não há componentes para paginação, rolagem ou processamento de listas longas de conteúdo. Verifique se o conteúdo permanece visível quando o tamanho da fonte aumenta ou o texto fica mais longo devido à tradução.
Componentes de interface
A biblioteca protolayout-material3 oferece um grande número de componentes
projetados de acordo com as especificações do Material 3 Expressive e as recomendações de interface
do usuário.
Botões
Os botões são principalmente orientados para ações. Eles servem para acionar ações específicas. O conteúdo de cada botão, como um ícone ou texto curto, identifica a ação.
- textButton(): botão com um único slot para conteúdo de texto (curto)
- iconButton(): botão com um único slot para representar um ícone.
- avatarButton(): botão de avatar em formato de pílula que oferece até três espaços para conteúdo que representa um rótulo empilhado verticalmente e um rótulo secundário, além de uma imagem (avatar) ao lado dele.
- imageButton(): botão de imagem clicável que não oferece outros
slots, apenas imagem (por exemplo,
backgroundImagecomo plano de fundo) - compactButton(): botão compacto que oferece até dois slots para receber conteúdo empilhado horizontalmente que representa um ícone e um texto ao lado dele.
- button(): botão em formato de pílula que oferece até três slots para conteúdo que representa um rótulo empilhado verticalmente e um rótulo secundário, além de um ícone ao lado.
Botões de borda
Um botão de borda é um botão especializado de largura total fixado na parte de baixo da tela redonda de um relógio, que representa a ação mais importante a ser realizada na tela do bloco atual.
- iconEdgeButton(): botão de borda que oferece um único slot para um ícone ou conteúdo pequeno e redondo semelhante.
- textEdgeButton(): botão de borda que oferece um único slot para um texto ou conteúdo longo e amplo semelhante.
Cartões
Os cards são principalmente orientados para informações. Eles mostram coleções de dados estruturados e relacionados. Embora seja possível criar cards interativos, eles geralmente mostram um resumo das informações, que o usuário pode tocar para ver mais detalhes ou realizar uma ação relacionada.
- titleCard(): card de título que oferece de um a três slots, geralmente baseados em texto.
- appCard(): card de app que oferece até cinco slots, geralmente baseados em texto.
- textDataCard(): card de dados que oferece até três slots empilhados verticalmente, geralmente baseados em texto ou números.
- iconDataCard(): card de dados que oferece até três slots empilhados verticalmente, geralmente baseados em texto ou números, com um ícone.
- graphicDataCard(): card de dados gráficos que oferece um slot para dados gráficos, como indicador de progresso, e até dois slots empilhados verticalmente, geralmente para descrições de texto.
Indicadores de progresso
- circularProgressIndicator(): indica o progresso em direção a uma meta usando um elemento radial.
- segmentedCircularProgressIndicator(): indica o progresso em direção a uma meta usando um elemento radial com etapas distintas.
Elementos de layout de grupo
- buttonGroup(): layout de componente que coloca os filhos em uma sequência horizontal.
- primaryLayout(): layout de tela cheia que representa um estilo de layout M3 sugerido, que é responsivo e cuida do posicionamento dos elementos, com as margens e o padding recomendados aplicados.
Aplicar um tema
No Material 3 Expressive, o sistema de cores é definido por 29 funções de cores padrão, organizadas em seis grupos: primária, secundária, terciária, de erro, de superfície e de contorno.
Um ColorScheme mapeia cada uma dessas 29 funções para uma cor correspondente.
Como ele faz parte do MaterialScope e os componentes precisam ser criados dentro dele,
eles assumem automaticamente as cores do esquema. Essa abordagem permite que todos os elementos da interface
sigam automaticamente os padrões do Material Design.
Para permitir que os usuários escolham entre um esquema de cores definido por você, como um que reflita
as cores da sua marca, e um fornecido pelo sistema, derivado do
mostrador do relógio atual do usuário ou escolhido por ele, inicialize o MaterialScope da
seguinte forma:
val myColorScheme = ColorScheme( primary = Color.rgb(0, 0, 255).argb, // Blue onPrimary = Color.rgb(255, 255, 255).argb, // White // 27 more ) materialScope( context = context, deviceConfiguration = requestParams.deviceConfiguration, defaultColorScheme = myColorScheme ) { // If the user selects "no theme" in settings, myColorScheme is used. // Otherwise, the system-provided theme is used. }
Se você escolher essa opção, forneça um ícone de bloco monocromático para que ele possa ser colorido corretamente e ter uma visibilidade ideal em qualquer tema.
Para forçar os blocos a aparecerem no esquema de cores fornecido, desative o suporte
a temas dinâmicos definindo allowDynamicTheme como false:
materialScope( context = context, deviceConfiguration = requestParams.deviceConfiguration, allowDynamicTheme = false, defaultColorScheme = myColorScheme ) { // myColorScheme is *always* used. }
Cor
Cada componente individual usa um subconjunto das 29 funções de cor definidas por um
ColorScheme. Por exemplo, os botões usam até quatro cores, que por padrão são
retiradas do grupo "primary" do ColorScheme ativo:
Token do componente ButtonColors |
ColorScheme role |
|---|---|
| containerColor | principal |
| iconColor | onPrimary |
| labelColor | onPrimary |
| secondaryLabelColor | onPrimary (opacidade 0,8) |
Para orientações detalhadas sobre como aplicar cores aos seus designs do Wear OS, consulte o guia de design de cores.
Talvez seja necessário desviar dos tokens de cor padrão para elementos específicos da interface. Por exemplo, talvez você queira que um textEdgeButton use
cores do grupo "secundário" ou "terciário", em vez de "primário", para
se destacar e oferecer um contraste melhor.
É possível personalizar as cores dos componentes de várias maneiras:
Use uma função auxiliar para cores predefinidas. Use funções auxiliares como
filledTonalButtonColors()para aplicar os estilos de botão padrão do Material 3 Expressive. Essas funções criam instâncias pré-configuradas deButtonColorsque mapeiam estilos comuns, como preenchido, tonal ou contornado, para as funções adequadas doColorSchemeativo noMaterialScope. Isso permite aplicar estilos consistentes sem definir manualmente cada cor para tipos de botão comuns.textEdgeButton( colors = filledButtonColors(), // default /* OR colors = filledTonalButtonColors() */ /* OR colors = filledVariantButtonColors() */ // ... other parameters )
Para cards, use a família de funções
filledCardColors()equivalente.Também é possível modificar o objeto
ButtonColorsretornado por funções auxiliares usando o métodocopy()se você só precisar mudar um ou dois tokens:textEdgeButton( colors = filledButtonColors() .copy( containerColor = colorScheme.tertiary, labelColor = colorScheme.onTertiary ), // ... other parameters )
Forneça explicitamente papéis de cor de substituição. Crie seu próprio objeto
ButtonColorse o transmita para o componente. Para cartões, use o objetoCardColorsequivalente.textEdgeButton( colors = ButtonColors( // the materialScope makes colorScheme available containerColor = colorScheme.secondary, iconColor = colorScheme.secondaryDim, labelColor = colorScheme.onSecondary, secondaryLabelColor = colorScheme.onSecondary ), // ... other parameters )
Especifique cores fixas (use com cuidado). Embora seja recomendável especificar as cores pela função semântica (por exemplo,
colorScheme.primary), também é possível fornecer valores de cor diretos. Essa abordagem precisa ser usada com moderação, porque pode levar a inconsistências com o tema geral, especialmente se ele mudar dinamicamente.textEdgeButton( colors = filledButtonColors().copy( containerColor = android.graphics.Color.RED.argb, // Using named colors labelColor = 0xFFFFFF00.toInt().argb // Using a hex code for yellow ), // ... other parameters )
Tipografia
Para mais informações sobre como usar a tipografia de maneira eficaz nos seus designs, consulte o guia de design de tipografia.
Para criar consistência visual na plataforma Wear OS e otimizar a performance, todo o texto nos blocos é renderizado usando uma fonte fornecida pelo sistema. Ou seja, os blocos não são compatíveis com famílias tipográficas personalizadas. No Wear OS 6 e versões mais recentes, essa é uma fonte específica do OEM. Na maioria dos casos, será uma fonte variável, que oferece uma experiência mais expressiva e um controle mais granular.
Para criar um estilo de texto, geralmente você usa o método text()
combinado com constantes tipográficas. Esse componente permite usar
funções de tipografia predefinidas no Material 3 Expressive, o que ajuda o bloco
a seguir as práticas recomendadas de tipografia estabelecidas para legibilidade e hierarquia.
A biblioteca oferece um conjunto de 18 constantes de tipografia semântica, como
BODY_MEDIUM. Essas constantes também afetam outros eixos de fonte além do tamanho.
text( text = "Hello, World!".layoutString, typography = BODY_MEDIUM, )
Para ter mais controle, forneça configurações adicionais. No Wear OS 6 e
versões mais recentes, é provável que uma fonte variável seja usada, que pode ser modificada nos
eixos itálico, peso, largura e arredondamento. É possível controlar esses eixos
usando o parâmetro settings:
text( text = "Hello, World".layoutString, italic = true, // Use elements defined in androidx.wear.protolayout.LayoutElementBuilders.FontSetting settings = listOf( weight(500), width(100F), roundness(100) ), )
Por fim, se você precisar controlar o tamanho ou o espaçamento entre letras (não recomendado),
use basicText() em vez de text() e crie um valor para a
propriedade fontStyle usando fontStyle().
Formato e margens
É possível mudar o raio do canto de quase todos os componentes usando a propriedade
shape. Os valores vêm da propriedade MaterialScope shapes:
textButton( height = expand(), width = expand(), shape = shapes.medium, // OR another value like shapes.full colors = filledVariantButtonColors(), labelContent = { text("Hello, World!".layoutString) }, )
Depois de alterar o formato de um componente, se você achar que ele deixa muito
ou pouco espaço ao redor da borda da tela, ajuste as margens usando o
parâmetro margin de primaryLayout():
primaryLayout( mainSlot = { textButton( shape = shapes.small, /* ... */ ) }, // margin constants defined in androidx.wear.protolayout.material3.PrimaryLayoutMargins margins = MAX_PRIMARY_LAYOUT_MARGIN, )
Arcos
Há suporte para os filhos de contêiner Arc abaixo:
ArcLine: renderiza uma linha curva ao redor do arco.ArcText: renderiza texto curvado no arco.ArcAdapter: renderiza um elemento básico de layout no arco, mostrado em uma tangente em relação ao arco.
Para mais informações, consulte a documentação de referência de cada um dos tipos de elemento.
Modificadores
Como alternativa, todos os elementos de layout disponíveis podem ter modificadores. Use esses modificadores para as finalidades abaixo:
- Mudar a aparência do layout. Por exemplo, adicionar um plano de fundo, uma borda ou um padding ao elemento de layout.
- Adicionar metadados sobre o layout. Por exemplo, adicionar um modificador de semântica ao elemento de layout que vai ser usado por leitores de tela.
- Adicionar funcionalidade. Por exemplo, adicione um modificador clicável ao elemento de layout para tornar o bloco interativo. Para mais informações, consulte Interagir com blocos.
Por exemplo, podemos personalizar a aparência e os metadados padrão de uma Image,
conforme mostrado neste exemplo de código:
private fun myImage(): LayoutElement = Image.Builder() .setWidth(dp(24f)) .setHeight(dp(24f)) .setResourceId("image_id") .setModifiers( Modifiers.Builder() .setBackground(Background.Builder().setColor(argb(0xFFFF0000.toInt())).build()) .setPadding(ModifiersBuilders.Padding.Builder().setStart(dp(12f)).build()) .setSemantics(Semantics.Builder().setContentDescription("Image description").build()) .build() ) .build()
Spannables
Um Spannable é um tipo especial de contêiner que apresenta elementos de maneira parecida com um
texto. Isso é útil quando você quer aplicar um estilo diferente a apenas uma
substring em um bloco de texto maior, algo que não é possível com o
elemento Text.
Um contêiner Spannable é preenchido com filhos Span. Outros filhos ou instâncias Spannable aninhadas não são permitidos.
Há dois tipos de filhos Span:
SpanText: renderiza o texto com um estilo específico.SpanImage: renderiza uma imagem inline com texto.
Por exemplo, você pode aplicar itálico à palavra "world" em um bloco "Hello world" e inserir uma imagem entre as palavras, conforme mostrado neste exemplo de código:
private fun mySpannable(): LayoutElement = LayoutElementBuilders.Spannable.Builder() .addSpan(SpanText.Builder().setText("Hello ").build()) .addSpan(SpanImage.Builder().setWidth(dp(24f)).setHeight(dp(24f)).setResourceId("image_id").build()) .addSpan( SpanText.Builder() .setText("world") .setFontStyle(FontStyle.Builder().setItalic(true).build()) .build() ) .build()
Trabalhar com recursos
Os Blocos não têm acesso a nenhum dos recursos do seu app. Isso significa que você
não pode transmitir um ID de imagem do Android a um elemento de layout Image e esperar que ele seja
resolvido. Em vez disso, substitua o método onTileResourcesRequest() e
forneça os recursos manualmente.
Há duas maneiras de fornecer imagens no método
onTileResourcesRequest():
- Forneça um recurso drawable usando
setAndroidResourceByResId(). - Forneça uma imagem dinâmica como uma
ByteArrayusandosetInlineResource().
override fun onTileResourcesRequest( requestParams: ResourcesRequest ) = Futures.immediateFuture( Resources.Builder() .setVersion("1") .addIdToImageMapping( "image_from_resource", ResourceBuilders.ImageResource.Builder() .setAndroidResourceByResId( ResourceBuilders.AndroidImageResourceByResId.Builder() .setResourceId(R.drawable.ic_walk) .build() ).build() ) .addIdToImageMapping( "image_inline", ResourceBuilders.ImageResource.Builder() .setInlineResource( ResourceBuilders.InlineImageResource.Builder() .setData(imageAsByteArray) .setWidthPx(48) .setHeightPx(48) .setFormat(ResourceBuilders.IMAGE_FORMAT_RGB_565) .build() ).build() ).build() )
Lista de verificação da imagem de prévia do bloco
O sistema mostra a imagem de visualização do bloco, referenciada no manifesto do app Android, no editor de carrossel de blocos para adicionar um novo bloco. Esse editor aparece tanto em dispositivos Wear OS quanto no app complementar do relógio em smartphones.
Para ajudar os usuários a aproveitar ao máximo essa imagem de prévia, verifique os seguintes detalhes sobre o bloco:
- Reflete o design mais recente. A visualização precisa representar com precisão o design mais recente do bloco.
- Usa as dimensões recomendadas. Para oferecer a melhor qualidade de exibição possível e uma boa experiência do usuário, a imagem de visualização precisa ter dimensões de 400 x 400 px.
- Usa um tema de cores estático. Use o tema de cores estático do bloco, não um dinâmico.
- Inclui o ícone do app. Confirme se o ícone do app aparece na parte de cima da imagem de prévia.
- Mostra o estado carregado/conectado. A prévia precisa mostrar um estado totalmente funcional "carregado" ou "conectado", evitando qualquer conteúdo vazio ou de marcador.
- Aproveite as regras de resolução de recursos para personalização (opcional). Considere usar as regras de resolução de recursos do Android para fornecer prévias que correspondam às configurações de tamanho de tela, idioma ou localidade do dispositivo. Isso é particularmente útil se a aparência do bloco variar entre os dispositivos.