Làm quen với thẻ thông tin

Để bắt đầu cung cấp thẻ thông tin từ ứng dụng, hãy đưa các phần phụ thuộc sau đây vào tệp build.gradle của ứng dụng.

Groovy

dependencies {
    // Use to implement support for wear tiles
    implementation "androidx.wear.tiles:tiles:1.1.0"

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

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

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

Kotlin

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

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

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

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

Tạo thẻ thông tin

Để cung cấp thẻ thông tin từ ứng dụng, hãy tạo một class (lớp) giúp mở rộng TileService và triển khai các phương thức, như trong mã mẫu sau:

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

Tiếp theo, hãy thêm một dịch vụ bên trong thẻ <application> của tệp 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>

Bộ lọc quyền và ý định sẽ đăng ký dịch vụ này làm trình cung cấp thẻ thông tin.

Người dùng sẽ thấy biểu tượng, nhãn và nội dung mô tả khi định cấu hình thẻ thông tin trên điện thoại hoặc đồng hồ.

Hãy dùng thẻ siêu dữ liệu xem trước để hiện bản xem trước của thẻ thông tin khi bạn định cấu hình thẻ thông tin trên điện thoại.

Tổng quan về vòng đời của dịch vụ thẻ thông tin

Sau khi tạo và khai báo TileService trong tệp kê khai ứng dụng, bạn có thể phản hồi các thay đổi về trạng thái của dịch vụ thẻ thông tin.

TileService là một dịch vụ ràng buộc. TileService của bạn bị ràng buộc do yêu cầu của ứng dụng hoặc nếu hệ thống cần giao tiếp với ứng dụng đó. Vòng đời dịch vụ ràng buộc điển hình có bốn phương thức gọi lại sau: onCreate(), onBind(), onUnbind()onDestroy(). Hệ thống sẽ gọi các phương thức này mỗi khi dịch vụ bước vào một giai đoạn mới trong vòng đời.

Ngoài các lệnh gọi lại kiểm soát vòng đời dịch vụ ràng buộc, bạn có thể triển khai các phương thức khác dành riêng cho vòng đời TileService. Tất cả dịch vụ thẻ thông tin phải triển khai onTileRequest()onTileResourcesRequest() để phản hồi các yêu cầu cập nhật của hệ thống.

  • onTileAddEvent(): Hệ thống chỉ gọi phương thức này khi người dùng thêm thẻ thông tin của bạn lần đầu tiên và nếu người dùng xoá rồi thêm lại thẻ thông tin của bạn. Đây là thời điểm tốt nhất để thực hiện mọi thao tác khởi chạy một lần.

    onTileAddEvent() chỉ được gọi khi tập hợp thẻ thông tin được định cấu hình lại, chứ không phải khi hệ thống tạo một thẻ thông tin. Ví dụ: khi thiết bị khởi động lại hoặc bật nguồn, onTileAddEvent() sẽ không được gọi cho các thẻ thông tin đã được thêm. Bạn có thể sử dụng getActiveTilesAsync() để xem thông tin tổng quan nhanh về những thẻ thông tin thuộc về bạn đang hoạt động.

  • onTileRemoveEvent(): Hệ thống chỉ gọi phương thức này nếu người dùng xoá thẻ thông tin của bạn.

  • onTileEnterEvent(): Hệ thống gọi phương thức này khi một thẻ thông tin do nhà cung cấp này cung cấp xuất hiện trên màn hình.

  • onTileLeaveEvent(): Hệ thống gọi phương thức này khi một thẻ thông tin do nhà cung cấp này cung cấp nằm ngoài khung hiển thị trên màn hình.

  • onTileRequest(): Hệ thống gọi phương thức này khi hệ thống yêu cầu một dòng thời gian mới từ trình cung cấp này.

  • onTileResourcesRequest(): Hệ thống gọi phương thức này khi hệ thống yêu cầu gói tài nguyên từ trình cung cấp này. Điều này có thể xảy ra vào lần đầu tiên Thẻ thông tin được tải hoặc bất cứ khi nào phiên bản tài nguyên thay đổi.

Truy vấn ô nào đang hoạt động

Thẻ thông tin đang hoạt động là các thẻ thông tin được thêm vào để hiển thị trên đồng hồ. Sử dụng phương thức tĩnh của TileService getActiveTilesAsync() để truy vấn thẻ thông tin nào thuộc ứng dụng của bạn đang hoạt động.

Tạo giao diện người dùng cho thẻ thông tin

Bố cục của thẻ thông tin được viết bằng mẫu trình tạo. Bố cục của thẻ thông tin được thiết kế giống một cây bao gồm các vùng chứa bố cục và các phần tử của bố cục cơ bản. Mỗi phần tử của bố cục có các thuộc tính mà bạn có thể thiết lập thông qua nhiều phương thức setter.

Phần tử của bố cục cơ bản

Dưới đây là các phần tử hình ảnh từ thư viện protolayout được hỗ trợ, cùng với các thành phần Material:

  • Text: hiển thị một chuỗi văn bản (không bắt buộc phải gói văn bản đó).
  • Image: hiển thị một hình ảnh.
  • Spacer: cung cấp khoảng đệm giữa các phần tử hoặc có thể hoạt động như một đường phân chia khi bạn đặt màu nền.

Thành phần Material

Ngoài các phần tử cơ bản, thư viện protolayout-material còn cung cấp các thành phần đảm bảo thiết kế thẻ thông tin phù hợp với các đề xuất về giao diện người dùng của Material Design.

  • Button: thành phần hình tròn có thể nhấp vào, được thiết kế để chứa một biểu tượng.
  • Chip: thành phần có hình dạng sân vận động có thể nhấp vào, được thiết kế để chứa tối đa 2 dòng văn bản và một biểu tượng không bắt buộc.

  • CompactChip: thành phần có hình dạng sân vận động có thể nhấp vào, được thiết kế để chứa một dòng văn bản.

  • TitleChip: thành phần có hình dạng sân vận động có thể nhấp vào, tương tự như Chip nhưng có chiều cao lớn hơn để chứa đủ văn bản tiêu đề.

  • CircularProgressIndicator: chỉ báo tiến trình hình tròn có thể đặt vào bên trong EdgeContentLayout để hiển thị tiến trình xung quanh các cạnh của màn hình.

Vùng chứa bố cục

Các vùng chứa sau đây được hỗ trợ, cùng với Bố cục Material:

  • Row: sắp xếp lần lượt từng phần tử con theo chiều ngang.
  • Column: sắp xếp lần lượt từng phần tử con theo chiều dọc.
  • Box: xếp chồng các phần tử con lên nhau.
  • Arc: sắp xếp các phần tử con trong một hình tròn.
  • Spannable: áp dụng FontStyles cụ thể cho các phần văn bản cùng với văn bản và hình ảnh xen kẽ. Để biết thêm thông tin, hãy xem phần Spannable.

Mỗi vùng chứa có thể chứa một hoặc nhiều phần tử con, mà chính các phần tử đó cũng có thể là vùng chứa. Ví dụ: Column có thể chứa nhiều phần tử Row dưới dạng phần tử con, từ đó tạo ra một bố cục giống như lưới.

Ví dụ: thẻ thông tin có 1 bố cục vùng chứa và 2 phần tử bố cục con có thể có dạng như sau:

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

Bố cục Material

Ngoài bố cục cơ bản, thư viện protolayout-material còn cung cấp một số bố cục ổn định để giữ những phần tử trong các "khung" cụ thể.

  • PrimaryLayout: đặt một hành động chính CompactChip ở dưới cùng với nội dung nằm ở giữa phía trên.

  • MultiSlotLayout: đặt nhãn chính và nhãn phụ có nội dung tuỳ ý nằm ở giữa và một CompactChip không bắt buộc ở dưới cùng.

  • MultiButtonLayout: đặt một nhóm các nút được sắp xếp theo nguyên tắc Material.

  • EdgeContentLayout: đặt nội dung xung quanh cạnh màn hình, chẳng hạn như CircularProgressIndicator. Khi dùng bố cục này, nội dung trong bố cục sẽ tự động có lề và khoảng đệm thích hợp.

Vòng cung

Dưới đây là các vùng chứa con Arc được hỗ trợ:

  • ArcLine: hiển thị một đường cong xung quanh Vòng cung.
  • ArcText: hiển thị văn bản cong trong Vòng cung.
  • ArcAdapter: hiển thị một phần tử bố cục cơ bản trong vòng cung, được vẽ tại tiếp tuyến của vòng cung.

Để biết thêm thông tin, hãy xem tài liệu tham khảo cho từng loại phần tử.

Đối tượng sửa đổi

Bạn có thể tuỳ ý áp dụng các công cụ sửa đổi cho mỗi phần tử bố cục có sẵn. Hãy sử dụng các công cụ sửa đổi này cho những mục đích sau:

  • Thay đổi giao diện hình ảnh của bố cục. Ví dụ: thêm nền, đường viền hoặc khoảng đệm vào phần tử bố cục.
  • Thêm siêu dữ liệu về bố cục. Ví dụ: thêm một đối tượng sửa đổi ngữ nghĩa vào phần tử bố cục để sử dụng với trình đọc màn hình.
  • Thêm chức năng. Ví dụ: thêm vào phần tử bố cục một đối tượng sửa đổi có thể nhấp để thẻ thông tin có khả năng tương tác. Để biết thêm thông tin, hãy xem bài viết Tương tác với thẻ thông tin.

Ví dụ: chúng ta có thể tuỳ chỉnh giao diện và siêu dữ liệu mặc định của Image, như trong mã mẫu sau:

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

Spannable

Spannable là loại vùng chứa đặc biệt có nhiệm vụ sắp xếp các phần tử tương tự như văn bản. Vùng chứa này hữu ích khi bạn muốn áp dụng kiểu khác cho duy nhất một chuỗi con trong khối văn bản lớn hơn – đây là điều không thể áp dụng với phần tử Text.

Vùng chứa Spannable chứa phần tử con Span. Bạn không được phép dùng các phần tử con khác hoặc các thực thể Spannable lồng nhau.

Có hai loại phần tử con Span:

  • SpanText: hiển thị văn bản có một kiểu cụ thể.
  • SpanImage: hiển thị hình ảnh cùng dòng với văn bản.

Ví dụ: bạn có thể in nghiêng từ "world" (mọi người) trong thẻ thông tin "Hello world" (Chào mọi người) và chèn một hình ảnh giữa các từ, như trong mã mẫu sau đây:

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

Làm việc với các tài nguyên

Ô không có quyền truy cập vào bất kỳ tài nguyên nào trong ứng dụng của bạn. Điều này có nghĩa là bạn không thể chuyển mã nhận dạng hình ảnh Android vào phần tử bố cục Image và kỳ vọng nó sẽ thực hiện phân giải. Thay vào đó, hãy ghi đè phương thức onTileResourcesRequest() và cung cấp mọi tài nguyên theo cách thủ công.

Có 2 cách để cung cấp hình ảnh trong phương thức 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()
);
}