Começar a usar Blocos


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.

Bloco &quot;Hello World&quot;.
Figura 1. Bloco "Hello World".

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:

  1. Iniciar um escopo do Material Design:chame a função materialScope() fornecendo o context e o deviceConfiguration necessários. É possível incluir parâmetros opcionais, como allowDynamicTheme e defaultColorScheme. O allowDynamicTheme é true por padrão, e o defaultColorScheme representa o ColorScheme 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 o allowDynamicTheme é false.

  2. 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() e textEdgeButton(), são funções de extensão em MaterialScope 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:

  1. titleSlot, normalmente para um título ou cabeçalho principal.
  2. mainSlot, para o conteúdo principal.
  3. O bottomSlot, usado com frequência para ações ou informações complementares. É aqui que um botão de borda aparece.
Layout de blocos mostrando titleSlot, mainSlot e bottomSlot
Figura 2.titleSlot, mainSlot e bottomSlot.

O conteúdo de cada slot é o seguinte:

  • titleSlot (opcional): normalmente, algumas palavras geradas por text().
  • 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

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.

Sistema de cores expressivo do Material 3
Figura 3. O sistema de cores expressivas do Material 3.

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:

  1. 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âncias ButtonColors preconfiguradas que mapeiam estilos comuns, como preenchido, tonal ou delineado, para as funções apropriadas do ColorScheme ativo no MaterialScope. 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étodo copy(), se você precisar mudar apenas um ou dois tokens:

    textEdgeButton(
        colors =
            filledButtonColors()
                .copy(
                    containerColor = colorScheme.tertiary,
                    labelColor = colorScheme.onTertiary
                )
        // ... other parameters
    )
    
  2. Forneça explicitamente papéis de cores substitutos. Crie seu próprio objeto ButtonColors e transmita-o ao componente. Para cards, use o objeto CardColors equivalente.

    textEdgeButton(
        colors =
            ButtonColors(
                // the materialScope makes colorScheme available
                containerColor = colorScheme.secondary,
                iconColor = colorScheme.secondaryDim,
                labelColor = colorScheme.onSecondary,
                secondaryLabelColor = colorScheme.onSecondary
            )
        // ... other parameters
    )
    
  3. 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():

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.