Xem trước giao diện người dùng trong Compose cho Wear OS

Bản xem trước Compose của Android Studio cho phép bạn kiểm tra và xác minh các thành phần kết hợp Wear OS trên nhiều kích thước màn hình đồng hồ, viền tròn và tỷ lệ phông chữ ngay trong IDE mà không cần triển khai ứng dụng vào đồng hồ thực hoặc trình mô phỏng.

Vì các thiết bị Wear OS có màn hình tròn, nơi các góc cắt nội dung và lớp phủ hệ thống như TimeText và ScrollIndicator uốn cong dọc theo cạnh màn hình, nên việc định cấu hình bản xem trước dành riêng cho Wear OS là điều cần thiết để phát hiện sớm các vấn đề về bố cục.


Thiết lập các phần phụ thuộc của bản xem trước

Để sử dụng chú thích xem trước và định nghĩa thiết bị của Wear OS Compose, hãy thêm các phần phụ thuộc sau vào tệp build.gradle.kts của mô-đun:

dependencies {
    // Provides @WearPreview* multipreview annotations
    // (such as @WearPreviewDevices and @WearPreviewFontScales)
    implementation("androidx.wear.compose:compose-ui-tooling:1.7.0")

    // Provides WearDevices constants
    // (such as WearDevices.SMALL_ROUND and WearDevices.LARGE_ROUND)
    implementation("androidx.wear:wear-tooling-preview:1.0.0")

    // Standard Compose preview support and interactive/animation inspection
    implementation("androidx.compose.ui:ui-tooling-preview")
    debugImplementation("androidx.compose.ui:ui-tooling")
}

Chọn nội dung bạn muốn xem trước: màn hình hay thành phần

Cách bạn định cấu hình bản xem trước phụ thuộc vào việc bạn đang xem trước toàn màn hình hay thành phần giao diện người dùng riêng biệt.

Xem trước toàn màn hình (AppScaffold + ScreenScaffold)

Khi xem trước toàn bộ màn hình, hãy luôn bao bọc thành phần kết hợp màn hình trong cả AppScaffold và ScreenScaffold bằng chú thích xem trước thiết bị Wear. Thao tác này sẽ kết xuất màn hình đồng hồ hình tròn và đảm bảo rằng:

  • TimeText hiển thị ở cạnh cong trên cùng của mặt đồng hồ.
  • ScrollIndicator sẽ xuất hiện dọc theo viền bên phải.
  • EdgeButton được đặt đúng vị trí và bị cắt ở đường cong dưới cùng.
  • Khoảng đệm nội dung và tính năng cắt màn hình tròn phản ánh chính xác phần cứng xem thực tế.
@WearPreviewDevices
@Composable
fun WorkoutScreenPreview() {
    MaterialTheme {
        // AppScaffold provides the top-level TimeText overlay
        AppScaffold {
            // WorkoutScreen contains its own ScreenScaffold and content
            WorkoutScreen(
                heartRate = 142,
                elapsedTime = "12:45"
            )
        }
    }
}
WorkoutScreenPreview được kết xuất trên WearDevices.SMALL_ROUND

Tròn nhỏ (192x192dp)

WorkoutScreenPreview được kết xuất trên WearDevices.LARGE_ROUND

Tròn lớn (227x227dp)

Xem trước các thành phần riêng biệt

Khi xem trước các thành phần riêng lẻ (chẳng hạn như Card, Button hoặc khối trạng thái tuỳ chỉnh), hãy bỏ qua tham số device và sử dụng @Preview tiêu chuẩn có nền tối. Điều này đảm bảo màu sắc và độ tương phản của Wear Material 3 xuất hiện chính xác mà không cần kết xuất màn hình đồng hồ tròn đầy đủ:

@Preview(
    showBackground = true,
    backgroundColor = 0xFF000000
)
@Composable
fun HeartRateCardPreview() {
    MaterialTheme {
        HeartRateCard(bpm = 142, zone = "Aerobic")
    }
}
Bản xem trước thành phần riêng biệt HeartRateCardPreview không có khung đồng hồ

Xem trước thành phần riêng biệt (không có khung thiết bị).


Chú thích nhiều bản xem trước được tích hợp sẵn

Gói androidx.wear.compose.ui.tooling.preview cung cấp các chú thích tích hợp tự động định cấu hình nền tối (backgroundColor = 0xFF000000, showBackground = true) và kích thước thiết bị đồng hồ hình tròn:

Annotation Nội dung được hiển thị Trường hợp sử dụng
@WearPreviewSmallRound 1 bản xem trước trên WearDevices.SMALL_ROUND (192x192dp). Lặp lại nhanh chóng trên kích thước hiển thị tròn bị hạn chế nhất.
@WearPreviewLargeRound 1 bản xem trước trên WearDevices.LARGE_ROUND (227x227dp). Kiểm tra mật độ bố cục và khoảng cách thừa trên các đồng hồ lớn hơn.
@WearPreviewDevices 2 bản xem trước: SMALL_ROUND và LARGE_ROUND. Quy trình kiểm tra tiêu chuẩn trên nhiều thiết bị cho mọi thành phần kết hợp màn hình.
@WearPreviewFontScales 6 bản xem trước trên SMALL_ROUND trên tất cả các tỷ lệ phông chữ Wear: Nhỏ (0.94f), Bình thường (1.0f), Trung bình (1.06f), Lớn (1.12f), Lớn hơn (1.18f) và Lớn nhất (1.24f). Kiểm tra tính năng xuống dòng tự động, rút gọn và mở rộng chiều cao của nút.

Bạn có thể xếp chồng @WearPreviewDevices và @WearPreviewFontScales trên cùng một hàm xem trước để tạo một ma trận thử nghiệm toàn diện:

@WearPreviewDevices
@WearPreviewFontScales
@Composable
fun MessageDetailScreenPreview() {
    MaterialTheme {
        AppScaffold {
            MessageDetailScreen(
                sender = "Alex",
                body = "Running 5 mins late!"
            )
        }
    }
}

Chú thích xem trước tuỳ chỉnh và thông số kỹ thuật phần cứng

Khi cần kiểm soát chi tiết hơn (chẳng hạn như kiểm thử các kích thước phần cứng cụ thể, chuỗi dài đã bản địa hoá hoặc các tổ hợp trong trường hợp xấu nhất), bạn có thể định cấu hình @Preview trực tiếp hoặc xác định chú thích nhiều bản xem trước tuỳ chỉnh của riêng mình.

Các hằng số WearDevices có sẵn và thông số kỹ thuật phần cứng tuỳ chỉnh

Đối tượng androidx.wear.tooling.preview.devices.WearDevices cung cấp mã nhận dạng thiết bị tiêu chuẩn:

  • WearDevices.SMALL_ROUND ("id:wearos_small_round", 192x192dp)
  • WearDevices.LARGE_ROUND ("id:wearos_large_round", 227x227dp)

Để xem trước trên màn hình tròn cực lớn (chẳng hạn như đồng hồ 44 mm – 45 mm hoặc các mẫu Ultra ở độ phân giải 240x240dp), hãy truyền một chuỗi spec: tuỳ chỉnh đến tham số device:

@Preview(
    name = "XL Round Watch (240dp)",
    device = "spec:width=240dp,height=240dp,dpi=320,isRound=true",
    showBackground = true,
    backgroundColor = 0xFF000000
)
@Composable
fun WorkoutScreenXlPreview() {
    MaterialTheme {
        AppScaffold {
            WorkoutScreen(heartRate = 142, elapsedTime = "12:45")
        }
    }
}

Tạo chú thích tuỳ chỉnh có nhiều bản xem trước

Để kiểm tra một trường hợp cực đoan, hãy tạo một chú thích nhiều bản xem trước tuỳ chỉnh, trong đó ghép màn hình tròn nhỏ nhất với thang phông chữ lớn nhất và một ngôn ngữ có nhiều từ (chẳng hạn như tiếng Đức) cùng với một màn hình tròn lớn tiêu chuẩn:

@Preview(
    name = "1. Standard Large Round",
    group = "Layout extremes",
    device = WearDevices.LARGE_ROUND,
    backgroundColor = 0xFF000000,
    showBackground = true
)
@Preview(
    name = "2. Extreme Small Round (Largest Font + German)",
    group = "Layout extremes",
    device = WearDevices.SMALL_ROUND,
    fontScale = 1.24f,
    locale = "de-rDE",
    backgroundColor = 0xFF000000,
    showBackground = true
)
annotation class WearPreviewExtremes
Bản xem trước vòng đấu lớn tiêu chuẩn

1. Tiêu chuẩn, tròn, lớn

Cực nhỏ, tròn với tỷ lệ phông chữ lớn nhất

2. Cực nhỏ, tròn (Phông chữ lớn nhất + tiếng Đức)


Xem trước các cột có thể cuộn (TransformingLazyColumn)

Theo mặc định, TransformingLazyColumn sẽ khởi chạy với mục đầu tiên (index = 0) được ghim ở đầu màn hình. Tuy nhiên, trên Wear OS, các mục sẽ thay đổi chiều cao và góc bo tròn (SurfaceTransformation) khi chúng tiến đến các cạnh cong trên cùng và dưới cùng của màn hình, đồng thời EdgeButton chỉ xuất hiện khi bạn cuộn xuống dưới cùng.

Cách xem trước danh sách khi di chuyển xuống một phần hoặc xuống cuối danh sách:

Bước 1: Nâng TransformingLazyColumnState trong thành phần kết hợp trên màn hình

Cho phép thành phần kết hợp màn hình chấp nhận một tham số TransformingLazyColumnState có rememberTransformingLazyColumnState() làm giá trị mặc định:

@Composable
fun InboxScreen(
    messages: List<Message>,
    columnState: TransformingLazyColumnState = rememberTransformingLazyColumnState(),
) {
    val transformationSpec = rememberTransformationSpec()

    ScreenScaffold(
        scrollState = columnState,
        edgeButton = {
            EdgeButton(onClick = { /* Compose new */ }) {
                Text("New message")
            }
        }
    ) { contentPadding ->
        TransformingLazyColumn(
            state = columnState,
            contentPadding = contentPadding,
        ) {
            items(messages.size) { index ->
                Card(
                    onClick = {},
                    modifier = Modifier
                        .fillMaxWidth()
                        .transformedHeight(this, transformationSpec)
                        .minimumVerticalContentPadding(
                            CardDefaults.minimumVerticalListContentPadding
                        ),
                    transformation = SurfaceTransformation(transformationSpec),
                ) {
                    Text(messages[index].subject)
                }
            }
        }
    }
}

Bước 2: Truyền initialAnchorItemIndex trong @Preview

rememberTransformingLazyColumnState chấp nhận 2 tham số cuộn ban đầu không bắt buộc:

  • initialAnchorItemIndex: Int: Khi được đặt thành một chỉ mục không âm (ví dụ: 3), danh sách sẽ khởi tạo bằng mục đó được căn giữa trong khung hiển thị của đồng hồ.
  • initialAnchorItemScrollOffset: Int: Khoảng cách bù pixel không bắt buộc được áp dụng so với mục neo ở giữa.

Bạn có thể tạo bản xem trước song song cho thấy trạng thái Trên cùng, Giữa (đã cuộn) và Dưới cùng (EdgeButton có thể nhìn thấy) của cùng một màn hình:

@WearPreviewLargeRound
@Composable
fun InboxScreenTopPreview() {
    MaterialTheme {
        AppScaffold {
            // Default (-1): Pinned to top of list (index 0)
            InboxScreen(messages = sampleMessages)
        }
    }
}

@WearPreviewLargeRound
@Composable
fun InboxScreenScrolledMiddlePreview() {
    MaterialTheme {
        AppScaffold {
            // Centers item index 3 in the viewport, showing top/bottom item morphing
            InboxScreen(
                messages = sampleMessages,
                columnState = rememberTransformingLazyColumnState(
                    initialAnchorItemIndex = 3
                )
            )
        }
    }
}

@WearPreviewLargeRound
@Composable
fun InboxScreenBottomEdgeButtonPreview() {
    MaterialTheme {
        AppScaffold {
            // Anchors on the last item so the EdgeButton is visible at the bottom
            InboxScreen(
                messages = sampleMessages,
                columnState = rememberTransformingLazyColumnState(
                    initialAnchorItemIndex = sampleMessages.lastIndex
                )
            )
        }
    }
}
InboxScreen được ghim lên đầu danh sách

Đầu trang (Mặc định -1)

InboxScreen scrolled to middle index 3

Trung bình (initialAnchorItemIndex = 3)

InboxScreen được cuộn xuống dưới cùng với EdgeButton được mở rộng

Dưới cùng (EdgeButton mở rộng)

Lưu ý: Bạn cũng có thể nhấp vào Start Interactive Mode (Bắt đầu chế độ tương tác) trên mọi @Preview trong Android Studio để di chuyển TransformingLazyColumn trực tiếp bằng chuột hoặc bàn di chuột và kiểm tra SurfaceTransformation biến đổi, EdgeButton hoạt ảnh xuất hiện và ScrollIndicator chuyển động theo thời gian thực.

Bảo vệ ScrollIndicator trong quá trình chụp ảnh màn hình (LocalScrollCaptureInProgress)

Khi các công cụ Chụp ảnh màn hình cuộn (ảnh chụp màn hình dài) hoặc kiểm thử ảnh chụp màn hình nhiều khung hình của hệ thống chụp một TransformingLazyColumn đang cuộn, Compose sẽ đặt LocalScrollCaptureInProgress.current thành true trong khi chụp và ghép nhiều ô khung hiển thị theo chiều dọc.

Vì ScreenScaffold không tự động ẩn scrollIndicator trong quá trình chụp ảnh màn hình khi cuộn, nên lớp phủ thanh cuộn nổi sẽ xuất hiện lặp lại trên mọi ô được ghép của ảnh chụp màn hình dài, trừ phi bạn bảo vệ rõ ràng bằng !LocalScrollCaptureInProgress.current:

ScreenScaffold(
    scrollState = columnState,
    scrollIndicator = {
        if (!LocalScrollCaptureInProgress.current) {
            ScrollIndicator(state = columnState)
        }
    }
) { contentPadding ->
    // TransformingLazyColumn content...
    // ...
}