Phân cấp kiến trúc của Jetpack Compose

Trang này cung cấp thông tin tổng quan cấp cao về các phân cấp kiến trúc tạo nên Jetpack Compose cũng như các nguyên tắc cốt lõi ảnh hưởng đến cách sắp xếp này.

Jetpack Compose không phải một khối dự án đơn lẻ; thư viện này được tạo nên từ nhiều mô-đun lắp ráp với nhau nhằm tạo thành một ngăn xếp hoàn chỉnh. Hiểu rõ được các mô-đun cấu thành nên Jetpack Compose sẽ giúp bạn:

  • Sử dụng mức độ trừu tượng phù hợp để tạo nên ứng dụng hoặc thư viện
  • Hiểu được khi nào bạn cần "hạ cấp" để có thêm quyền kiểm soát hoặc tuỳ chỉnh
  • Giảm thiểu các phần phụ thuộc

Các phân cấp

Các cấp chính của Jetpack Compose bao gồm:

Hình 1. Các lớp chính của Jetpack Compose.

Mỗi cấp được xây dựng dựa trên các phân cấp thấp hơn, phối hợp cùng các tính năng để tạo nên các đối tượng trên cấp cao hơn. Mỗi cấp được xây dựng dựa trên các API công khai của các cấp thấp hơn để xác định các giới hạn mô-đun và cho phép bạn thay thế bất kỳ phân cấp nào nếu cần thiết. Hãy cùng tìm hiểu các phân cấp này từ dưới lên.

Thời gian chạy
Mô-đun này cung cấp những thành phần cơ bản về thời gian chạy của Compose, chẳng hạn như remember, mutableStateOf, chú giải @ComposableSideEffect. Bạn có thể xem xét xây dựng trực tiếp trên phân cấp này nếu chỉ sử dụng khả năng quản lý sơ đồ cây của Compose mà không cần đến giao diện người dùng (UI).
Giao diện người dùng (UI)
Phân cấp giao diện người dùng (UI) gồm nhiều mô-đun (ui-text, ui-graphics, ui-tooling, v.v.). Các mô-đun này triển khai các thành phần cơ bản của bộ công cụ giao diện người dùng (UI), chẳng hạn như LayoutNode, Modifier, trình xử lý nhập liệu (input handler), bố cục tuỳ chỉnh (custom layout) và bản vẽ (drawing). Bạn có thể xem xét xây dựng trên phân cấp này khi chỉ cần đến các thuộc tính cơ bản của bộ công cụ giao diện người dùng (UI).
Nền tảng
Mô-đun này cung cấp các thành phần độc lập của hệ thống thiết kế cho giao diện người dùng của Compose, chẳng hạn như Row, ColumnLazyColumn, ghi nhận các cử chỉ cụ thể, v.v. Bạn có thể cân nhắc việc dựng trên lớp nền tảng để tạo hệ thống thiết kế cho riêng mình.
Material
Mô-đun này cung cấp quá trình triển khai của hệ thống Material Design cho giao diện người dùng (UI) Compose, cung cấp một hệ thống chủ đề, tạo kiểu cho các thành phần, các chỉ báo gợn sóng và biểu tượng. Hãy xây dựng trên phân cấp này khi bạn sử dụng Material Design trong ứng dụng.

Nguyên tắc thiết kế

Nguyên tắc chung của Jetpack Compose là cung cấp các nhóm chức năng tập trung nhỏ, có thể được lắp ráp (hoặc gộp lại) với nhau, thay vì nhiều khối thành phần. Cách tiếp cận này có một số ưu điểm.

Kiểm soát

Các đối tượng trên phân cấp cao hơn có xu hướng thực hiện nhiều tác vụ hơn cho bạn, nhưng chúng sẽ hạn chế các quyền kiểm soát trực tiếp của bạn. Nếu cần kiểm soát nhiều hơn, bạn có thể "thả xuống" để sử dụng thành phần cấp thấp hơn.

Ví dụ: nếu muốn tạo hiệu ứng chuyển màu của một thành phần nào đó, bạn có thể sử dụng API animateColorAsState:

val color = animateColorAsState(if (condition) Color.Green else Color.Red)

Tuy nhiên, nếu bạn muốn thành phần này luôn bắt đầu với màu xám, thì bạn không thể làm điều đó bằng API này. Thay vào đó, bạn có thể thả xuống để sử dụng cấp độ thấp hơn Animatable API:

val color = remember { Animatable(Color.Gray) }
LaunchedEffect(condition) {
    color.animateTo(if (condition) Color.Green else Color.Red)
}

API animateColorAsState trên phân cấp cao hơn được xây dựng dựa trên API Animatable ở phân cấp thấp hơn. Việc sử dụng API cấp thấp tuy phức tạp hơn nhưng mang lại quyền kiểm soát cao hơn. Hãy chọn mức độ trừu tượng phù hợp nhất với nhu cầu của bạn.

Tuỳ chỉnh

Việc tập hợp các thành phần cấp cao hơn từ các thành phần cấp thấp hơn làm cho việc tuỳ chỉnh các thành phần trở nên dễ dàng hơn nhiều. Ví dụ: hãy xem xét cách triển khai cho đối tượng Button do phân cấp Material cung cấp:

@Composable
fun Button(
    // …
    content: @Composable RowScope.() -> Unit
) {
    Surface(/* … */) {
        CompositionLocalProvider(/* … */) { // set LocalContentAlpha
            ProvideTextStyle(MaterialTheme.typography.button) {
                Row(
                    // …
                    content = content
                )
            }
        }
    }
}

Đối tượng Button được tạo nên từ 4 thành phần:

  1. Thành phần Surface về chất liệu cung cấp nền, hình dạng, xử lý lượt nhấp, v.v.

  2. Phương thức CompositionLocalProvider thay đổi độ đậm nhạt của nội dung khi đối tượng nút được kích hoạt hoặc vô hiệu

  3. Phương thức ProvideTextStyle thiết lập kiểu văn bản mặc định

  4. Phương thức Row cung cấp nguyên tắc bố cục mặc định cho nội dung của đối tượng nút đó

Chúng tôi đã xoá một số tham số và chú giải để khiến cấu trúc rõ ràng hơn, dù vậy toàn bộ thành phần chỉ có khoảng 40 dòng lệnh bởi nó đơn giản chỉ lắp ráp lại 4 thành phần này để triển khai đối tượng nút. Các thành phần như đối tượng Button sẽ hiển thị các tham số cố định, cân bằng các tuỳ chỉnh được cấp phép phổ biến để tránh các trường hợp dùng quá nhiều tham số, khiến thành phần đó trở nên khó sử dụng hơn. Ví dụ: các thành phần Material sẽ cung cấp các tuỳ chỉnh cụ thể trong hệ thống Material Design, giúp bạn dễ dàng tuân theo các nguyên tắc thiết kế Material.

Tuy nhiên, nếu bạn muốn tuỳ chỉnh ngoài các thông số của thành phần, thì bạn có thể "thả xuống" một cấp và phát triển một thành phần. Ví dụ: Material Design quy định các đối tượng nút phải có màu đồng nhất. Nếu bạn muốn đối tượng nút có màu biến đổi tuyến tính (gradient), thuộc tính này sẽ không hỗ trợ cho tham số của đối tượng Button này. Trong trường hợp đó, bạn có thể sử dụng phương thức triển khai đối tượng Button của phân cấp Material để tham chiếu cho đối tượng của riêng mình:

@Composable
fun GradientButton(
    // …
    background: List<Color>,
    modifier: Modifier = Modifier,
    content: @Composable RowScope.() -> Unit
) {
    Row(
        // …
        modifier = modifier
            .clickable(onClick = {})
            .background(
                Brush.horizontalGradient(background)
            )
    ) {
        CompositionLocalProvider(/* … */) { // set material LocalContentAlpha
            ProvideTextStyle(MaterialTheme.typography.button) {
                content()
            }
        }
    }
}

Cách triển khai ở trên tiếp tục sử dụng các thành phần từ lớp Material, chẳng hạn như các khái niệm của Material về giai đoạn alpha của nội dung hiện tại và kiểu văn bản hiện tại. Tuy nhiên, phương thức này sẽ thay thế Material Surface bằng một Row và tạo kiểu để có được giao diện mong muốn.

Nếu bạn hoàn toàn không muốn sử dụng khái niệm Material, chẳng hạn như khi tạo hệ thống thiết kế riêng, sau đó bạn có thể thả xuống hoàn toàn sử dụng nền tảng thành phần lớp:

@Composable
fun BespokeButton(
    // …
    backgroundColor: Color,
    modifier: Modifier = Modifier,
    content: @Composable RowScope.() -> Unit
) {
    Row(
        // …
        modifier = modifier
            .clickable(onClick = {})
            .background(backgroundColor)
    ) {
        // No Material components used
        content()
    }
}

Jetpack Compose sử dụng cách đặt tên đơn giản nhất cho các thành phần trên phân cấp cao nhất. Ví dụ: androidx.compose.material.Text được tạo dựa trên androidx.compose.foundation.text.BasicText. Điều này giúp bạn có thể tiến hành quá trình triển khai những cái tên dễ nhận biết nhất, trong trường hợp bạn có nhu cầu thay đổi các phân cấp cao hơn.

Chọn mức độ trừu tượng phù hợp

Theo quan niệm của Compose khi xây dựng các phân cấp chồng lên nhau, các thành phần có thể tái sử dụng đồng nghĩa với việc bạn không phải lúc nào cũng cần tới các thành phần ở phân cấp thấp hơn. Các thành phần trên cấp cao hơn không chỉ cung cấp thêm nhiều tính năng mà còn luôn triển khai các phương thức xử lý tốt nhất như hỗ trợ khả năng tiếp cận.

Ví dụ: nếu bạn muốn thêm tính năng hỗ trợ bằng cử chỉ vào thành phần tuỳ chỉnh, bạn có thể tạo tính năng này từ đầu bằng cách sử dụng thành phần Modifier.pointerInput, nhưng vẫn có các thành phần khác ở cấp cao hơn được dựng trên thành phần này có thể có điểm xuất phát tốt hơn, ví dụ: Modifier.draggable, Modifier.scrollable hoặc Modifier.swipeable.

Theo nguyên tắc, hãy ưu tiên xây dựng các thành phần trên phân cấp cao nhất bởi chúng cung cấp các tính năng giúp bạn hưởng lợi từ những phương thức xử lý tốt nhất mà phân cấp đó có.

Tìm hiểu thêm

Xem Mẫu Jetsnack ví dụ về cách xây dựng một hệ thống thiết kế tuỳ chỉnh.