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.3.0"

    // Use to utilize standard components and layouts in your tiles
    implementation "androidx.wear.protolayout:protolayout:1.1.0"

    // Use to utilize components and layouts with Material Design in your tiles
    implementation "androidx.wear.protolayout:protolayout-material:1.1.0"

    // Use to include dynamic expressions in your tiles
    implementation "androidx.wear.protolayout:protolayout-expression:1.1.0"

    // Use to preview wear tiles in your own app
    debugImplementation "androidx.wear.tiles:tiles-renderer:1.3.0"

    // Use to fetch tiles from a tile provider in your tests
    testImplementation "androidx.wear.tiles:tiles-testing:1.3.0"
}

Kotlin

dependencies {
    // Use to implement support for wear tiles
    implementation("androidx.wear.tiles:tiles:1.3.0")

    // Use to utilize standard components and layouts in your tiles
    implementation("androidx.wear.protolayout:protolayout:1.1.0")

    // Use to utilize components and layouts with Material Design in your tiles
    implementation("androidx.wear.protolayout:protolayout-material:1.1.0")

    // Use to include dynamic expressions in your tiles
    implementation("androidx.wear.protolayout:protolayout-expression:1.1.0")

    // Use to preview wear tiles in your own app
    debugImplementation("androidx.wear.tiles:tiles-renderer:1.3.0")

    // Use to fetch tiles from a tile provider in your tests
    testImplementation("androidx.wear.tiles:tiles-testing:1.3.0")
}

Criar um bloco

Para fornecer um bloco pelo aplicativo, crie uma classe que estenda TileService e implemente os métodos, conforme mostrado neste exemplo de código:

Kotlin

// Uses the ProtoLayout namespace for tile timeline objects.
// If you haven't done so already, migrate to the ProtoLayout namespace.
import androidx.wear.protolayout.TimelineBuilders.Timeline
import androidx.wear.protolayout.material.Text
import androidx.wear.tiles.TileBuilders.Tile

private val RESOURCES_VERSION = "1"
class MyTileService : TileService() {
    override fun onTileRequest(requestParams: RequestBuilders.TileRequest) =
        Futures.immediateFuture(Tile.Builder()
            .setResourcesVersion(RESOURCES_VERSION)
            .setTileTimeline(
                Timeline.fromLayoutElement(
                    Text.Builder(this, "Hello world!")
                        .setTypography(Typography.TYPOGRAPHY_DISPLAY1)
                        .setColor(argb(0xFF000000.toInt()))
                        .build()))
            .build())

    override fun onTileResourcesRequest(requestParams: ResourcesRequest) =
        Futures.immediateFuture(Resources.Builder()
            .setVersion(RESOURCES_VERSION)
            .build()
        )
}

Java

// Uses the ProtoLayout namespace for tile timeline objects.
// If you haven't done so already, migrate to the ProtoLayout namespace.
import androidx.wear.protolayout.TimelineBuilders.Timeline;
import androidx.wear.protolayout.material.Text;
import androidx.wear.tiles.TileBuilders.Tile;

public class MyTileService extends TileService {
    private static final String RESOURCES_VERSION = "1";

    @NonNull
    @Override
    protected ListenableFuture<Tile> onTileRequest(
        @NonNull TileRequest requestParams
    ) {
        return Futures.immediateFuture(new Tile.Builder()
            .setResourcesVersion(RESOURCES_VERSION)
            .setTileTimeline(
                Timeline.fromLayoutElement(
                    new Text.Builder(this, "Hello world!")
                        .setTypography(Typography.TYPOGRAPHY_DISPLAY1)
                        .setColor(ColorBuilders.argb(0xFF000000))
                        .build()))
            .build()
        );
   }

   @NonNull
   @Override
   protected ListenableFuture<Resources> onTileResourcesRequest(
       @NonNull ResourcesRequest requestParams
   ) {
       return Futures.immediateFuture(new Resources.Builder()
               .setVersion(RESOURCES_VERSION)
               .build()
       );
   }
}

Em seguida, adicione um serviço dentro da tag <application> do arquivo AndroidManifest.xml.

<service
   android:name=".MyTileService"
   android:label="@string/tile_label"
   android:description="@string/tile_description"
   android:icon="@drawable/tile_icon_round"
   android:roundIcon="@drawable/tile_icon_round"
   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 e a descrição são mostrados ao usuário quando ele configura os blocos no smartphone ou smartwatch.

Use a tag de metadados de visualização para mostrar uma prévia do bloco durante a configuração dele no smartphone.

Visão geral do ciclo de vida do serviço de bloco

Depois de criar e declarar a TileService no manifesto do app, você pode responder às mudanças de estado do serviço de bloco.

TileService é um serviço vinculado. O TileService está vinculado como resultado da solicitação do app ou quando o sistema precisa se comunicar com ele. Um ciclo de vida de serviços vinculados típico contém os quatro métodos de callback a seguir: onCreate(), onBind(), onUnbind() e onDestroy(). O sistema invoca esses métodos sempre que o serviço entra em uma nova fase do ciclo de vida.

Além dos callbacks que controlam o ciclo de vida do serviço vinculado, é possível implementar outros métodos específicos para o ciclo de vida da TileService. Todos os serviços de bloco precisam implementar onTileRequest() e onTileResourcesRequest() para responder às solicitações de atualizações do sistema.

  • onTileAddEvent(): o sistema chama esse método somente quando o usuário adiciona o bloco pela primeira vez e se ele remove e adiciona o bloco novamente. Esse é o melhor momento para fazer qualquer inicialização única.

    O onTileAddEvent() só é chamado quando o conjunto de blocos é reconfigurado, não sempre que um bloco é criado pelo sistema. Por exemplo, quando o dispositivo é reinicializado ou ligado, o onTileAddEvent() não é chamado para os blocos que já foram adicionados. Em vez disso, use getActiveTilesAsync() para ter um snapshot de quais blocos pertencentes a você estão ativos.

  • onTileRemoveEvent(): o sistema chamará esse método somente se o usuário remover o bloco.

  • onTileEnterEvent(): o sistema chama esse método quando um bloco fornecido por esse provedor aparece na tela.

  • onTileLeaveEvent(): o sistema chama esse método quando um bloco fornecido por esse provedor sai da visualização na tela.

  • onTileRequest(): o sistema chama esse método quando solicita uma nova linha do tempo a esse provedor.

  • onTileResourcesRequest(): o sistema chama esse método quando solicita um pacote de recursos do provedor. Isso pode acontecer na primeira vez que um Bloco estiver sendo carregado ou sempre que a versão do recurso mudar.

Consultar quais blocos estão ativos

Blocos ativos são blocos que foram adicionados para exibição no relógio. Use o método estático getActiveTilesAsync() da TileService para consultar quais blocos pertencentes ao seu app estão ativos.

Criar uma IU para blocos

O layout de um bloco é criado usando um padrão de construtor. Ele é construído como árvore que consiste em contêineres e elementos básicos de layout. Cada elemento de layout tem propriedades, que podem ser definidas com vários métodos setter.

Elementos básicos de layout

Os elementos visuais da biblioteca protolayout abaixo têm suporte nos componentes do Material Design:

  • Text: renderiza uma string de texto, opcionalmente a encapsulando.
  • Image: renderiza uma imagem.
  • Spacer: fornece padding entre elementos ou pode atuar como um divisor quando você define a cor do plano de fundo.

Componentes do Material Design

Além dos elementos básicos, a biblioteca protolayout-material fornece componentes que garantem um design de blocos alinhado às recomendações da interface do usuário do Material Design.

  • Button: componente circular clicável projetado para conter um ícone.
  • Chip: componente clicável em formato de estádio projetado para conter até duas linhas de texto e um ícone opcional.

  • CompactChip: componente clicável em formato de estádio projetado para conter uma linha de texto.

  • TitleChip: componente clicável em formato de estádio parecido com um Chip, mas com uma altura maior para acomodar o texto do título.

  • CircularProgressIndicator: indicador circular que pode ser colocado dentro de um EdgeContentLayout para mostrar o progresso ao redor das bordas da tela.

Contêineres de layout

Os contêineres abaixo têm suporte nos layouts do Material Design:

  • Row: mostra os elementos filhos horizontalmente, um após o outro.
  • Column: mostra os elementos filhos verticalmente, um após o outro.
  • Box: sobrepõe elementos filhos uns sobre os outros.
  • Arc: mostra os elementos filhos em um círculo.
  • Spannable: aplica FontStyles específicos a seções de texto, bem como texto e imagens intercalados. Para mais informações, consulte Spannables.

Cada contêiner pode conter um ou mais filhos, que também podem ser contêineres. Por exemplo, uma Column pode conter vários elementos Row como filhos, resultando em um layout parecido com uma grade.

Um bloco com um layout de contêiner e dois elementos de layout filhos pode ser parecido com este exemplo:

Kotlin

private fun myLayout(): LayoutElement =
    Row.Builder()
        .setWidth(wrap())
        .setHeight(expand())
        .setVerticalAlignment(VALIGN_BOTTOM)
        .addContent(Text.Builder()
            .setText("Hello world")
            .build()
        )
        .addContent(Image.Builder()
            .setResourceId("image_id")
            .setWidth(dp(24f))
            .setHeight(dp(24f))
            .build()
        ).build()

Java

private LayoutElement myLayout() {
    return new Row.Builder()
        .setWidth(wrap())
        .setHeight(expand())
        .setVerticalAlignment(VALIGN_BOTTOM)
        .addContent(new Text.Builder()
            .setText("Hello world")
            .build()
        )
        .addContent(new Image.Builder()
            .setResourceId("image_id")
            .setWidth(dp(24f))
            .setHeight(dp(24f))
            .build()
        ).build();
}

Layouts do Material Design

Além dos layouts básicos, a biblioteca protolayout-material fornece alguns layouts opinativos feitos para manter elementos em "slots" específicos.

  • PrimaryLayout: posiciona uma única ação principal CompactChip na parte de baixo com o conteúdo centralizado acima dela.

  • MultiSlotLayout: posiciona os rótulos primário e secundário com conteúdo opcional entre eles e um CompactChip opcional na parte de baixo da tela.

  • MultiButtonLayout: posiciona um conjunto de botões organizados de acordo com as diretrizes do Material Design.

  • EdgeContentLayout: posiciona o conteúdo ao redor da borda de uma tela, como um CircularProgressIndicator. Ao usar esse layout, o conteúdo dele tem as margens e o padding adequados aplicados automaticamente.

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 saber mais, 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:

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()
);
}