Hỗ trợ nội dung có thể mở rộng theo người dùng

Triển khai cử chỉ thu phóng bằng hai ngón tay để hỗ trợ nội dung có thể mở rộng trong ứng dụng. Đây là phương thức chuẩn, nhất quán trên nền tảng để cải thiện khả năng hỗ trợ tiếp cận, cho phép người dùng điều chỉnh kích thước văn bản và các phần tử trên giao diện người dùng một cách trực quan cho phù hợp với nhu cầu của họ. Ứng dụng của bạn có thể xác định hành vi điều chỉnh tỷ lệ tuỳ chỉnh bằng chế độ kiểm soát chi tiết và hành vi theo bối cảnh, mang lại trải nghiệm mà người dùng thường khám phá nhanh hơn so với tính năng ở cấp hệ thống như phóng to màn hình.

Chọn chiến lược điều chỉnh quy mô

Các chiến lược được đề cập trong hướng dẫn này khiến giao diện người dùng tự động dàn lại và sắp xếp lại để vừa với chiều rộng của màn hình. Điều này mang lại lợi ích đáng kể về khả năng hỗ trợ tiếp cận bằng cách loại bỏ nhu cầu kéo ngang và chuyển động "zig-zag" gây khó chịu mà nếu không thì sẽ cần thiết để đọc các dòng văn bản dài.

Tài liệu đọc thêm: Nghiên cứu xác nhận rằng đối với người dùng có thị lực kém, nội dung được dàn lại sẽ dễ đọc và dễ thao tác hơn đáng kể so với giao diện yêu cầu kéo và di chuyển hai chiều. Để biết thêm thông tin, hãy xem bài viết So sánh nội dung tự động điều chỉnh và nội dung kéo và quét trên thiết bị di động.

Điều chỉnh tỷ lệ cho tất cả phần tử hoặc chỉ phần tử văn bản

Bảng sau đây minh hoạ hiệu ứng hình ảnh của từng chiến lược điều chỉnh tỷ lệ.

Chiến lược Điều chỉnh tỷ lệ mật độ Tỷ lệ phông chữ

Hành vi

Điều chỉnh tỷ lệ mọi thứ theo tỷ lệ. Nội dung được dàn lại cho vừa với vùng chứa, vì vậy, người dùng không cần phải kéo sang ngang để xem tất cả nội dung.

Chỉ ảnh hưởng đến các phần tử văn bản. Bố cục tổng thể và các thành phần không phải văn bản vẫn giữ nguyên kích thước.

What Scales

Tất cả các thành phần hình ảnh: Văn bản, thành phần (nút, biểu tượng), hình ảnh và khoảng cách bố cục (khoảng đệm, lề)

Chỉ có văn bản

Minh hoạ

Đề xuất

Bây giờ, bạn đã thấy sự khác biệt về hình ảnh, bảng sau đây sẽ giúp bạn cân nhắc những điểm đánh đổi và chọn chiến lược phù hợp nhất cho nội dung của mình.

Loại giao diện người dùng

Chiến lược được đề xuất

Suy luận

Bố cục tập trung vào việc đọc

Ví dụ: Tin bài, ứng dụng nhắn tin

Mật độ hoặc tỷ lệ phông chữ

Bạn nên sử dụng tính năng điều chỉnh mật độ để điều chỉnh tỷ lệ cho toàn bộ vùng nội dung, bao gồm cả hình ảnh cùng dòng.

Điều chỉnh tỷ lệ phông chữ là một giải pháp thay thế đơn giản nếu chỉ cần điều chỉnh tỷ lệ văn bản.

Bố cục có cấu trúc trực quan

Ví dụ: Cửa hàng ứng dụng, nguồn cấp dữ liệu mạng xã hội

Điều chỉnh mật độ

Giữ nguyên mối quan hệ trực quan giữa hình ảnh và văn bản trong băng chuyền hoặc lưới. Tính chất dàn lại giúp tránh việc kéo ngang, điều này sẽ xung đột với các phần tử cuộn lồng nhau.

Phát hiện cử chỉ điều chỉnh tỷ lệ trong Jetpack Compose

Để hỗ trợ nội dung có thể mở rộng theo người dùng, trước tiên, bạn phải phát hiện cử chỉ nhiều điểm chạm. Trong Jetpack Compose, bạn có thể thực hiện việc này bằng cách sử dụng Modifier.transformable.

Đối tượng sửa đổi transformable là một API cấp cao cung cấp zoomChange delta kể từ sự kiện cử chỉ gần đây nhất. Điều này giúp đơn giản hoá logic cập nhật trạng thái thành tích luỹ trực tiếp (ví dụ: scale *= zoomChange), khiến logic này trở nên lý tưởng cho các chiến lược điều chỉnh tỷ lệ thích ứng được đề cập trong hướng dẫn này.

Ví dụ về cách triển khai

Các ví dụ sau đây cho thấy cách triển khai chiến lược điều chỉnh tỷ lệ mật độ và điều chỉnh tỷ lệ phông chữ.

Điều chỉnh tỷ lệ mật độ

Phương pháp này điều chỉnh tỷ lệ density cơ sở của một vùng giao diện người dùng. Do đó, tất cả các phép đo dựa trên bố cục (bao gồm khoảng đệm, khoảng cách và kích thước thành phần) đều được điều chỉnh theo tỷ lệ, như thể kích thước hoặc độ phân giải màn hình đã thay đổi. Vì kích thước văn bản cũng dựa trên mật độ, nên kích thước này cũng điều chỉnh theo tỷ lệ. Chiến lược này có hiệu quả khi bạn muốn phóng to đồng đều tất cả các phần tử trong một khu vực cụ thể, duy trì nhịp điệu và tỷ lệ hình ảnh tổng thể của giao diện người dùng.

private class DensityScalingState(
    // Note: For accessibility, typical min/max values are ~0.75x and ~3.5x.
    private val minScale: Float = 0.75f,
    private val maxScale: Float = 3.5f,
    private val currentDensity: Density
) {
    val transformableState = TransformableState { zoomChange, _, _ ->
        scaleFactor.floatValue =
            (scaleFactor.floatValue * zoomChange).coerceIn(minScale, maxScale)
    }
    val scaleFactor = mutableFloatStateOf(1f)
    fun scaledDensity(): Density {
        return Density(
            currentDensity.density * scaleFactor.floatValue,
            currentDensity.fontScale
        )
    }
}

@Composable
fun DensityScalingSample() {
    val currentDensity = LocalDensity.current
    val scaleState =
        remember(currentDensity) { DensityScalingState(currentDensity = currentDensity) }

    Box(
        modifier = Modifier
            .fillMaxSize()
            .transformable(state = scaleState.transformableState),
        contentAlignment = Alignment.TopCenter
    ) {
        CompositionLocalProvider(
            LocalDensity provides scaleState.scaledDensity()
        ) {
            DemoCard()
        }
    }
}

Tỷ lệ phông chữ

Chiến lược này có mục tiêu cụ thể hơn, chỉ sửa đổi hệ số fontScale. Kết quả là chỉ có các phần tử văn bản tăng hoặc giảm, trong khi tất cả các thành phần bố cục khác (chẳng hạn như vùng chứa, khoảng đệm và biểu tượng) vẫn giữ nguyên kích thước cố định. Chiến lược này rất phù hợp để cải thiện mức độ dễ đọc của văn bản trong các ứng dụng có nhiều nội dung đọc.

class FontScaleState(
    // Note: For accessibility, typical min/max values are ~0.75x and ~3.5x.
    private val minScale: Float = 0.75f,
    private val maxScale: Float = 3.5f,
    private val currentDensity: Density
) {
    val transformableState = TransformableState { zoomChange, _, _ ->
        scaleFactor.floatValue =
            (scaleFactor.floatValue * zoomChange).coerceIn(minScale, maxScale)
    }
    val scaleFactor = mutableFloatStateOf(1f)
    fun scaledFont(): Density {
        return Density(
            currentDensity.density,
            currentDensity.fontScale * scaleFactor.floatValue
        )
    }
}

@Composable
fun FontScalingSample() {
    val currentDensity = LocalDensity.current
    val scaleState = remember { FontScaleState(currentDensity = currentDensity) }

    Box(
        modifier = Modifier
            .fillMaxSize()
            .transformable(state = scaleState.transformableState),
        contentAlignment = Alignment.TopCenter
    ) {
        CompositionLocalProvider(
            LocalDensity provides scaleState.scaledFont()
        ) {
            DemoCard()
        }
    }
}

Giao diện người dùng minh hoạ dùng chung

Đây là composable DemoCard dùng chung được cả hai ví dụ trước sử dụng để làm nổi bật các hành vi điều chỉnh tỷ lệ khác nhau.

@Composable
private fun DemoCard() {
    Card(
        modifier = Modifier
            .width(360.dp)
            .padding(16.dp),
        shape = RoundedCornerShape(12.dp)
    ) {
        Column(
            modifier = Modifier.padding(16.dp),
            verticalArrangement = Arrangement.spacedBy(16.dp)
        ) {
            Text("Demo Card", style = MaterialTheme.typography.headlineMedium)
            var isChecked by remember { mutableStateOf(true) }
            Row(verticalAlignment = Alignment.CenterVertically) {
                Text("Demo Switch", Modifier.weight(1f), style = MaterialTheme.typography.bodyLarge)
                Switch(checked = isChecked, onCheckedChange = { isChecked = it })
            }
            Row(verticalAlignment = Alignment.CenterVertically) {
                Icon(Icons.Filled.Person, "Icon", Modifier.size(32.dp))
                Spacer(Modifier.width(8.dp))
                Text("Demo Icon", style = MaterialTheme.typography.bodyLarge)
            }
            Row(
                Modifier.fillMaxWidth(),
                horizontalArrangement = Arrangement.SpaceBetween
            ) {
                Box(
                    Modifier
                        .width(100.dp)
                        .weight(1f)
                        .height(80.dp)
                        .background(Color.Blue)
                )
                Box(
                    Modifier
                        .width(100.dp)
                        .weight(1f)
                        .height(80.dp)
                        .background(Color.Red)
                )
            }
            Text(
                "Demo Text: Lorem ipsum dolor sit amet, consectetur adipiscing elit," +
                    " sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
                style = MaterialTheme.typography.bodyMedium,
                textAlign = TextAlign.Justify
            )
        }
    }
}

Mẹo và những điều cần lưu ý

Để tạo ra trải nghiệm chỉn chu và dễ tiếp cận hơn, hãy cân nhắc các đề xuất sau:

  • Cân nhắc việc cung cấp các chế độ điều khiển tỷ lệ không dựa trên cử chỉ: Một số người dùng có thể gặp khó khăn khi sử dụng cử chỉ. Để hỗ trợ những người dùng này, hãy cân nhắc việc cung cấp một cách khác để điều chỉnh hoặc đặt lại tỷ lệ mà không cần dựa vào cử chỉ.
  • Xây dựng cho mọi tỷ lệ: Thử nghiệm giao diện người dùng của bạn với cả chế độ cài đặt phông chữ hoặc màn hình trên toàn hệ thống và tỷ lệ trong ứng dụng. Kiểm tra để đảm bảo bố cục của ứng dụng thích ứng chính xác mà không làm hỏng, chồng chéo hoặc ẩn nội dung. Tìm hiểu thêm về cách xây dựng bố cục thích ứng.