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.4.1" // Use to utilize standard components and layouts in your tiles implementation "androidx.wear.protolayout:protolayout:1.2.1" // Use to utilize components and layouts with Material Design in your tiles implementation "androidx.wear.protolayout:protolayout-material:1.2.1" // Use to include dynamic expressions in your tiles implementation "androidx.wear.protolayout:protolayout-expression:1.2.1" // Use to preview wear tiles in your own app debugImplementation "androidx.wear.tiles:tiles-renderer:1.4.1" // Use to fetch tiles from a tile provider in your tests testImplementation "androidx.wear.tiles:tiles-testing:1.4.1" }
Kotlin
dependencies { // Use to implement support for wear tiles implementation("androidx.wear.tiles:tiles:1.4.1") // Use to utilize standard components and layouts in your tiles implementation("androidx.wear.protolayout:protolayout:1.2.1") // Use to utilize components and layouts with Material Design in your tiles implementation("androidx.wear.protolayout:protolayout-material:1.2.1") // Use to include dynamic expressions in your tiles implementation("androidx.wear.protolayout:protolayout-expression:1.2.1") // Use to preview wear tiles in your own app debugImplementation("androidx.wear.tiles:tiles-renderer:1.4.1") // Use to fetch tiles from a tile provider in your tests testImplementation("androidx.wear.tiles:tiles-testing:1.4.1") }
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 um card, ou vários elementos agrupados usando uma coluna, buttonGroup ou algo semelhante. Eles são incorporados em um modelo de layout.
- Recursos:os objetos
ResourceBuilders.Resources
consistem em um mapa de pares de chave-valor dos recursos do Android (imagens) necessários para renderizar um layout e uma versão. - Cronologia: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 parar de mostrar um layout em um momento específico. - Estado:uma estrutura de dados do tipo
StateBuilders.State
que é 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 terá o ID do botão. Também é possível trocar tipos de dados usando um mapa. - Tile:um objeto
TileBuilders.Tile
que representa um bloco, consistindo de uma linha do tempo, um ID da versão de 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 gráfica usada em várias plataformas do Wear OS.
Criar um bloco
Para fornecer um Bloco do app, implemente um serviço do tipo TileService
e registre-o no manifesto. Com isso, o sistema solicita os
tiles necessários durante as chamadas para onTileRequest()
e 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 visualização oferece suporte a todos os qualificadores de recursos padrão do Android. Portanto, é possível variar a visualização de acordo com fatores como tamanho da tela e idioma do dispositivo. Consulte a lista de verificação de pré-visualização para mais recomendações.
Implante o app e adicione o Bloco ao carrossel de Blocos. Há uma maneira mais fácil para desenvolvedores visualizarem um Bloco, mas, por enquanto, faça manualmente.

Para conferir um exemplo completo, consulte o exemplo de código no GitHub ou o codelab.
Criar uma IU para blocos
Os elementos de interface Expressiva do Material 3 são criados usando uma abordagem estruturada com o padrão de builder seguro para tipos do Kotlin.
Layout
Para criar o layout, faça o seguinte:
Iniciar um escopo do Material Design:chame a função
materialScope()
fornecendo ocontext
e odeviceConfiguration
necessários. É possível incluir parâmetros opcionais, comoallowDynamicTheme
edefaultColorScheme
. OallowDynamicTheme
étrue
por padrão, e odefaultColorScheme
representa oColorScheme
usado quando as cores dinâmicas não estão disponíveis, por exemplo, quando o usuário desativou o recurso ou quando ele não tem suporte ao dispositivo ou oallowDynamicTheme
éfalse
.Crie a interface no escopo:todos os componentes da interface de um determinado layout de bloco precisam ser definidos no 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 emMaterialScope
e só estão disponíveis quando chamadas neste 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(text = "Action".layoutString) } ) }
Caça-níqueis
No M3, o layout de blocos usa uma abordagem inspirada no Compose que usa três slots distintos. De cima para baixo, elas são:
titleSlot
, normalmente para um título ou cabeçalho principal.mainSlot
, para o conteúdo principal.- O
bottomSlot
, usado com frequência para ações ou informações complementares. É aqui que um botão de borda aparece.

O conteúdo de cada slot é o seguinte:
titleSlot
(opcional): normalmente, 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. Por exemplo, uma coluna pode conter linhas.bottomSlot
(opcional): normalmente preenchido com um botão de contorno ou um rótulo de texto.
Como os blocos não podem ser rolados, não há componentes para paginação, rolagem ou processamento de listas longas de conteúdo. O conteúdo precisa permanecer 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
- 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 forma de pílula que oferece até três slots para receber conteúdo que represente rótulo e rótulo secundário empilhados verticalmente e uma imagem (avatar) ao lado
- imageButton(): botão de imagem clicável que não oferece slots adicionais, apenas imagem (por exemplo, backgroundImage como 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 forma de pílula que oferece até três slots para receber conteúdo que representa rótulo e rótulo secundário empilhados verticalmente e um ícone ao lado dele.
Botões de borda
- iconEdgeButton(): botão de borda que oferece um único slot para usar um ícone ou conteúdo pequeno e arredondado.
- textEdgeButton(): botão de borda que oferece um único slot para receber um texto ou conteúdo longo e largo.
Cartões
- 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 com base em texto
- textDataCard(): cartão de dados que oferece até três slots empilhados verticalmente, geralmente com base em texto ou número.
- iconDataCard(): cartão de dados que oferece até três slots empilhados verticalmente, geralmente com base em texto ou numeral, com um ícone
- graphicDataCard(): cartão de dados gráfico 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 relação a uma meta usando um elemento radial com estágios distintos.
Como agrupar elementos de layout
- buttonGroup(): layout de componente que coloca os filhos em uma sequência horizontal.
- primaryLayout(): layout em tela cheia que representa um estilo de layout M3 sugerido que é responsivo e cuida da colocação dos elementos, junto com as margens e o padding recomendados aplicados
Temas
No Material 3 Expressivo, o sistema de cores é definido por 29 funções de cor 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 e, como faz parte do MaterialScope
e os componentes precisam ser criados nele,
elas usam automaticamente as cores do esquema. Essa abordagem permite que todos os elementos
da interface adiram automaticamente aos 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 pelo usuário, inicialize o MaterialScope
da seguinte
maneira:
val myColorScheme =
ColorScheme(
primary = ...
onPrimary = ...
// 27 more
)
materialScope(
defaultColorScheme = myColorScheme
) {
// If the user selects "no theme" in settings, myColorScheme is used.
// Otherwise, the system-provided theme is used.
}
Para forçar os blocos a aparecer no esquema de cores fornecido, desative o suporte
a temas dinâmicos definindo allowDynamicTheme
como false
:
materialScope(
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
tiradas do grupo "principal" do ColorScheme
ativo:
Token de componente ButtonColors |
Papel ColorScheme |
---|---|
containerColor | principal |
iconColor | onPrimary |
labelColor | onPrimary |
secondaryLabelColor | onPrimary (opacidade 0,8) |
Talvez seja necessário desviar dos tokens de cor padrão para elementos específicos
da interface. Por exemplo, você pode querer que um textEdgeButton
use
cores do grupo "secundário" ou "terciário", em vez de "primário", para
se destacar e proporcionar 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 Expressivo. Essas funções criam instânciasButtonColors
preconfiguradas que mapeiam estilos comuns, como preenchido, tonal ou delineado, para as funções apropriadas doColorScheme
ativo 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
ButtonColors
retornado por funções auxiliares usando o métodocopy()
, se você precisar mudar apenas um ou dois tokens:textEdgeButton( colors = filledButtonColors() .copy( containerColor = colorScheme.tertiary, labelColor = colorScheme.onTertiary ) // ... other parameters )
Forneça explicitamente papéis de cores substitutos. Crie seu próprio objeto
ButtonColors
e transmita-o ao componente. Para cards, use o objetoCardColors
equivalente.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 cautela). Em geral, é recomendável especificar cores pelo papel semântico delas (por exemplo,
colorScheme.primary
), você também pode fornecer valores de cor diretos. Essa abordagem deve 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.argb // Using a hex code for yellow ) // ... other parameters )
Tipografia
Para criar consistência visual em toda a plataforma Wear OS e otimizar o desempenho, todo o texto nos blocos é renderizado usando uma fonte fornecida pelo sistema. Ou seja, os blocos não oferecem suporte a 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, oferecendo 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 que você use
funções de tipografia predefinidas no Material 3 Expressive, o que ajuda seu bloco
a aderir às 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 os eixos de fonte, exceto o tamanho.
text(
text = "Hello, World!".layoutString,
typography = BODY_MEDIUM,
)
Para ter mais controle, você pode definir outras configurações. No Wear OS 6 e
versões mais recentes, é provável que uma fonte variável seja usada, que pode ser modificada ao longo dos
eixos itálico, espessura, 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 a espaça entre letras (não recomendado),
use basicText() em vez de text() e crie um valor para a
propriedade fontStyle
usando fontStyle().
Forma 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 a forma 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 de layout básico 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 no exemplo de código abaixo:
Kotlin
private fun myImage(): LayoutElement = Image.Builder() .setWidth(dp(24f)) .setHeight(dp(24f)) .setResourceId("image_id") .setModifiers(Modifiers.Builder() .setBackground(Background.Builder().setColor(argb(0xFFFF0000)).build()) .setPadding(Padding.Builder().setStart(dp(12f)).build()) .setSemantics(Semantics.builder() .setContentDescription("Image description") .build() ).build() ).build()
Java
private LayoutElement myImage() { return new Image.Builder() .setWidth(dp(24f)) .setHeight(dp(24f)) .setResourceId("image_id") .setModifiers(new Modifiers.Builder() .setBackground(new Background.Builder().setColor(argb(0xFFFF0000)).build()) .setPadding(new Padding.Builder().setStart(dp(12f)).build()) .setSemantics(new 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:
Kotlin
private fun mySpannable(): LayoutElement = 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()
Java
private LayoutElement mySpannable() { return new Spannable.Builder() .addSpan(new SpanText.Builder() .setText("Hello ") .build() ) .addSpan(new SpanImage.Builder() .setWidth(dp(24f)) .setHeight(dp(24f)) .setResourceId("image_id") .build() ) .addSpan(new SpanText.Builder() .setText("world") .setFontStyle(newFontStyle.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
ByteArray
usandosetInlineResource()
.
Kotlin
override fun onTileResourcesRequest( requestParams: ResourcesRequest ) = Futures.immediateFuture( Resources.Builder() .setVersion("1") .addIdToImageMapping("image_from_resource", ImageResource.Builder() .setAndroidResourceByResId(AndroidImageResourceByResId.Builder() .setResourceId(R.drawable.image_id) .build() ).build() ) .addIdToImageMapping("image_inline", ImageResource.Builder() .setInlineResource(InlineImageResource.Builder() .setData(imageAsByteArray) .setWidthPx(48) .setHeightPx(48) .setFormat(ResourceBuilders.IMAGE_FORMAT_RGB_565) .build() ).build() ).build() )
Java
@Override protected ListenableFuture<Resources> onTileResourcesRequest( @NonNull ResourcesRequest requestParams ) { return Futures.immediateFuture( new Resources.Builder() .setVersion("1") .addIdToImageMapping("image_from_resource", new ImageResource.Builder() .setAndroidResourceByResId(new AndroidImageResourceByResId.Builder() .setResourceId(R.drawable.image_id) .build() ).build() ) .addIdToImageMapping("image_inline", new ImageResource.Builder() .setInlineResource(new InlineImageResource.Builder() .setData(imageAsByteArray) .setWidthPx(48) .setHeightPx(48) .setFormat(ResourceBuilders.IMAGE_FORMAT_RGB_565) .build() ).build() ).build() ); }
Lista de verificação da imagem de visualização do bloco
O sistema mostra a imagem de visualização do bloco, referenciada no manifesto do app Android, no editor de carrossel de blocos. Esse editor aparece em dispositivos Wear OS e no app complementar do relógio em smartphones.
Para ajudar os usuários a aproveitar ao máximo essa imagem de visualização, 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.
- Usar um tema de cores estático. Use o tema de cor 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 visualização.
- Mostra o estado de carregamento/login. A visualização precisa mostrar um estado totalmente funcional "carregado" ou "com login", evitando qualquer conteúdo vazio ou de marcador de posição.
- Use as regras de resolução de recursos para personalização (opcional). Considere usar as regras de resolução de recursos do Android para fornecer visualizações que correspondam às configurações de idioma, localidade ou tamanho da tela do dispositivo. Isso é especialmente útil se a aparência do bloco variar entre os dispositivos.