Pierwsze kroki z kafelkami

Aby zacząć udostępniać kafelki z aplikacji, umieść w jej pliku build.gradle te zależności.

Odlotowy

dependencies {
    // Use to implement support for wear tiles
    implementation "androidx.wear.tiles:tiles:1.4.0-alpha01"

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

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

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

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

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

Kotlin

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

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

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

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

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

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

Utwórz kafelek

Aby udostępnić kafelek z aplikacji, utwórz klasę, która rozszerza TileService, i zaimplementuj metody, jak pokazano w tym przykładowym kodzie:

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

Następnie dodaj usługę w tagu <application> w pliku 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>

Filtr uprawnień i intencji rejestruje tę usługę jako dostawcę kafelków.

Ikona, etykieta i opis są wyświetlane użytkownikowi podczas konfigurowania kart na telefonie lub zegarku.

Użyj tagu metadanych podglądu, aby wyświetlić podgląd kafelka podczas jego konfigurowania na telefonie.

Omówienie cyklu życia kafelka

Po utworzeniu i zadeklarowaniu TileService w manifeście aplikacji możesz reagować na zmiany stanu usługi kafelków.

TileService jest usługą wiążącą. Urządzenie TileService jest powiązane w wyniku żądania aplikacji lub jeśli system musi się z nim połączyć. Typowy cykl życia ograniczonej usługi obejmuje 4 metody wywołania zwrotnego: onCreate(), onBind(), onUnbind() i onDestroy(). System wywołuje te metody za każdym razem, gdy usługa wchodzi w nowy etap cyklu życia.

Oprócz wywołań zwrotnych, które kontrolują cykl życia usługi powiązanej z usługą, możesz zaimplementować inne metody specyficzne dla cyklu życia TileService. Wszystkie usługi kafelków muszą implementować onTileRequest() i onTileResourcesRequest(), aby odpowiadać na żądania aktualizacji systemu.

  • onTileAddEvent(): system wywołuje tę metodę tylko wtedy, gdy użytkownik doda Twój kafelek po raz pierwszy oraz gdy usunie go i doda ponownie. To najlepszy moment na przeprowadzenie jednorazowej inicjalizacji.

    Funkcja onTileAddEvent() jest wywoływana tylko po ponownej konfiguracji zestawu kafelków, a nie za każdym razem, gdy kafelek zostanie utworzony przez system. Na przykład: gdy urządzenie zostanie zrestartowane lub włączone, onTileAddEvent() nie zostanie wywołana dla już dodanych kafelków. Zamiast tego możesz użyć getActiveTilesAsync(), aby uzyskać migawkę aktywnych kafelków należących do Ciebie.

  • onTileRemoveEvent(): system wywołuje tę metodę tylko wtedy, gdy użytkownik usunie Twój kafelek.

  • onTileEnterEvent(): system wywołuje tę metodę, gdy na ekranie pojawia się kafelek udostępniony przez tego dostawcę.

  • onTileLeaveEvent(): system wywołuje tę metodę, gdy kafelek udostępniony przez tego dostawcę znika z widoku na ekranie.

  • onTileRequest(): system wywołuje tę metodę, gdy żąda od tego dostawcy nowej osi czasu.

  • onTileResourcesRequest(): system wywołuje tę metodę, gdy system żąda od tego dostawcy pakietu zasobów. Może się to zdarzyć przy pierwszym wczytywaniu kafelka lub przy każdej zmianie wersji zasobu.

Zapytanie, które kafelki są aktywne

Aktywne kafelki to karty, które zostały dodane do wyświetlania na zegarku. Użyj statycznej metody TileService (getActiveTilesAsync()), aby zapytać, które kafelki należące do Twojej aplikacji są aktywne.

Utwórz interfejs dla kafelków

Układ kafelka jest zapisywany za pomocą wzorca konstruktora. Układ kafelków tworzy się jak drzewo, które składa się z kontenerów układu i podstawowych elementów układu. Każdy element układu ma właściwości, które można ustawić za pomocą różnych metod ustawiających.

Podstawowe elementy układu

Oprócz komponentów Material obsługiwane są te elementy wizualne z biblioteki protolayout:

  • Text: renderuje ciąg znaków z opcjonalnie zawijaniem tekstu.
  • Image: renderuje obraz.
  • Spacer: zapewnia dopełnienie między elementami lub może pełnić funkcję separatora, gdy ustawisz kolor tła.

Komponenty

Oprócz podstawowych elementów biblioteka protolayout-material zawiera komponenty, które zapewniają układ kafelków zgodny z zaleceniami interfejsu Material Design.

  • Button: klikalny komponent okrągły, który zawiera ikonę.
  • Chip: klikalny komponent w kształcie stadionu, który może zawierać maksymalnie 2 wiersze tekstu i opcjonalną ikonę.

  • CompactChip: klikalny komponent w kształcie stadionu zaprojektowany tak, by zawierał wiersz tekstu.

  • TitleChip: klikalny komponent w kształcie stadionu podobny do Chip, ale o większej wysokości, aby zmieścić tekst tytułu.

  • CircularProgressIndicator: okrągły wskaźnik postępu, który można umieścić w elemencie EdgeContentLayout, aby pokazywać postęp na krawędziach ekranu.

Kontenery układu

Obsługiwane są też te kontenery wraz z układami materiału:

  • Row: układa elementy podrzędne w poziomie, jeden po drugim.
  • Column: elementy podrzędne są umieszczane jeden po drugim w pionie.
  • Box: nakłada się na siebie elementy podrzędne.
  • Arc: układa elementy podrzędne w okręgu.
  • Spannable: stosuje określony atrybut FontStyles do sekcji tekstu wraz z przeplatanym tekstem i obrazami. Więcej informacji znajdziesz w artykule o obiektach sprzężonych.

Każdy kontener może zawierać jedno lub więcej elementów podrzędnych, które też mogą być kontenerami. Na przykład element Column może zawierać wiele elementów Row w postaci elementów podrzędnych, co skutkuje układem przypominającym siatkę.

Na przykład kafelek z układem kontenera i dwoma elementami układu podrzędnego może wyglądać tak:

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

Układy materiałowe

Oprócz podstawowych układów biblioteka protolayout-material udostępnia kilka dostosowanych układów przeznaczonych do przytrzymywania elementów w określonych „boksach”.

  • PrimaryLayout: umieszcza pojedyncze działanie główne CompactChip na dole, a treści pośrodku.

  • MultiSlotLayout: umieszcza etykiety główne i dodatkowe między opcjonalnymi treściami i opcjonalnym parametrem CompactChip na dole.

  • MultiButtonLayout: umieszcza zestaw przycisków ułożonych zgodnie ze wskazówkami dotyczącymi materiału.

  • EdgeContentLayout: określa położenie treści na krawędzi ekranu, np. CircularProgressIndicator. W tym układzie treść, która się w nim znajduje, ma automatycznie stosowane marginesy i dopełnienie.

Łuki

Obsługiwane są te podrzędne kontenery Arc:

  • ArcLine: renderuje krzywą linię wokół łuku.
  • ArcText: renderuje w polu tekstu zakrzywiony tekst.
  • ArcAdapter: renderuje podstawowy element układu w łuku narysowany przy styku do łuku.

Więcej informacji o poszczególnych typach elementów znajdziesz w dokumentacji referencyjnej.

Modyfikatory

Do każdego dostępnego elementu układu można opcjonalnie zastosować modyfikatory. Te modyfikatory powinny służyć do tych celów:

  • zmienić wygląd układu, Możesz np. dodać tło, obramowanie lub dopełnienie do elementu układu.
  • Dodaj metadane dotyczące układu. Na przykład możesz dodać do elementu układu modyfikator semantyczny używany z czytnikami ekranu.
  • Dodaj funkcję. Na przykład dodaj klikalny modyfikator do elementu układu, aby kafelek był interaktywny. Więcej informacji znajdziesz w artykule Korzystanie z kafelków.

Możemy na przykład dostosować domyślny wygląd i metadane obiektu Image, jak pokazano w tym przykładowym kodzie:

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

Elementy spalne

Spannable to specjalny typ kontenera, który służy do umieszczania elementów podobnie jak tekst. Jest to przydatne, gdy chcesz zastosować inny styl tylko do jednego podłańcucha w większym bloku tekstu, co nie jest możliwe w przypadku elementu Text.

Kontener Spannable zawiera elementy podrzędne Span. Inne elementy podrzędne ani zagnieżdżone instancje Spannable są niedozwolone.

Istnieją 2 rodzaje kont podrzędnych Span:

  • SpanText: renderuje tekst z określonym stylem.
  • SpanImage: renderuje obraz umieszczony w tekście.

Możesz na przykład napisać kursywą „świat” w kafelku „Hello world” i wstawić między słowami obraz, jak w tym przykładowym kodzie:

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

Praca z zasobami

Karty nie mają dostępu do żadnych zasobów aplikacji. Oznacza to, że nie można przekazać identyfikatora obrazu Androida do elementu układu Image i oczekiwać, że zostanie on rozwiązany. Zamiast tego zastąp metodę onTileResourcesRequest() i podaj zasoby ręcznie.

Obrazy można udostępniać za pomocą metody onTileResourcesRequest() na 2 sposoby:

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