Tích hợp Compose với giao diện người dùng hiện có

Nếu sở hữu một ứng dụng có giao diện người dùng dựa trên Khung hiển thị, bạn có thể không muốn viết lại toàn bộ giao diện người dùng cho ứng dụng đó cùng một lúc. Trang này sẽ giúp bạn thêm các phần tử mới trong Compose vào giao diện người dùng hiện có.

Di chuyển giao diện người dùng được chia sẻ

Nếu đang chuyển dần sang ứng dụng Compose, bạn có thể cần sử dụng các phần tử dùng chung trên giao diện người dùng trong cả Compose và hệ thống Khung hiển thị. Ví dụ: nếu ứng dụng của bạn có thành phần CallToActionButton tuỳ chỉnh, thì bạn có thể cần sử dụng thành phần đó trong cả màn hình Compose và màn hình dựa trên Khung hiển thị.

Trong Compose, các phần tử dùng chung trên giao diện người dùng sẽ trở thành các thành phần kết hợp có thể dùng lại trên ứng dụng, bất kể phần tử đó được tạo kiểu bằng XML hay khung hiển thị tuỳ chỉnh. Ví dụ: bạn sẽ tạo một thành phần kết hợp CallToActionButton cho thành phần Button kêu gọi hành động tuỳ chỉnh.

Để sử dụng thành phần kết hợp này trong màn hình dựa trên Khung hiển thị, bạn cần tạo một trình bao bọc khung hiển thị tuỳ chỉnh mở rộng từ AbstractComposeView. Trong thành phần kết hợp Content bị ghi đè, hãy đặt thành phần kết hợp bạn đã tạo trong giao diện Compose như minh hoạ ở ví dụ dưới đây:

@Composable
fun CallToActionButton(
    text: String,
    onClick: () -> Unit,
    modifier: Modifier = Modifier,
) {
    Button(
        colors = ButtonDefaults.buttonColors(
            containerColor = MaterialTheme.colorScheme.secondary
        ),
        onClick = onClick,
        modifier = modifier,
    ) {
        Text(text)
    }
}

class CallToActionViewButton @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyle: Int = 0
) : AbstractComposeView(context, attrs, defStyle) {

    var text by mutableStateOf("")
    var onClick by mutableStateOf({})

    @Composable
    override fun Content() {
        YourAppTheme {
            CallToActionButton(text, onClick)
        }
    }
}

Lưu ý rằng các tham số của thành phần kết hợp đó sẽ trở thành biến có thể thay đổi bên trong khung hiển thị tuỳ chỉnh. Nhờ vậy, khung hiển thị CallToActionViewButton tuỳ chỉnh có thể tăng cường và sử dụng được (ví dụ: Liên kết khung hiển thị) như khung hiển thị truyền thống. Xem ví dụ bên dưới:

class ViewBindingActivity : ComponentActivity() {

    private lateinit var binding: ActivityExampleBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityExampleBinding.inflate(layoutInflater)
        setContentView(binding.root)

        binding.callToAction.apply {
            text = getString(R.string.greeting)
            onClick = { /* Do something */ }
        }
    }
}

Nếu thành phần tuỳ chỉnh chứa trạng thái có thể thay đổi, hãy xem phần Nguồn trạng thái đáng tin cậy.

Di chuyển giao diện của ứng dụng

Bạn nên sử dụng hệ thống thiết kế Material Design để thiết kế giao diện cho các ứng dụng Android.

Có 3 phiên bản Material dành cho các ứng dụng dựa trên Khung hiển thị:

  • Material Design 1 dùng thư viện AppCompat (cụ thể là Theme.AppCompat.*)
  • Material Design 2 dùng thư viện MDC-Android (cụ thể là Theme.MaterialComponents.*)
  • Material Design 3 dùng thư viện MDC-Android (cụ thể là Theme.Material3.*)

Có 2 phiên bản Material dành cho các ứng dụng Compose:

  • Material Design 2 dùng thư viện Compose Material (cụ thể là androidx.compose.material.MaterialTheme)
  • Material Design 3 dùng thư viện Compose Material 3 (cụ thể là androidx.compose.material3.MaterialTheme)

Nếu có thể, bạn nên sử dụng phiên bản mới nhất là Material 3 trong trường hợp hệ thống thiết kế của ứng dụng cho phép. Bạn có thể tham khảo hướng dẫn di chuyển cho cả Khung hiển thị và Compose theo đường liên kết dưới đây:

Khi tạo màn hình mới trong ứng dụng Compose, bất kể bạn đang sử dụng phiên bản Material Design nào, hãy đảm bảo rằng bạn áp dụng MaterialTheme trước mọi thành phần kết hợp tạo ra giao diện người dùng từ thư viện Compose Material. Các thành phần Material (Button, Text, v.v.) phụ thuộc vào việc có sẵn MaterialTheme hay không. Nếu không có, thì hành vi của các thành phần đó sẽ không được xác định.

Tất cả các mẫu Jetpack Compose đều sử dụng giao diện Compose tuỳ chỉnh dựa trên MaterialTheme.

Hãy xem bài viết Hệ thống thiết kế trong ComposeDi chuyển giao diện XML sang Compose để tìm hiểu thêm.

WindowInsets và Ảnh động IME

Từ tính năng Soạn thư phiên bản 1.2.0 trở đi, bạn có thể xử lý WindowInsets bằng cách sử dụng công cụ sửa đổi để xử lý các bố cục đó. Ảnh động IME cũng được hỗ trợ.

class ExampleActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        WindowCompat.setDecorFitsSystemWindows(window, false)

        setContent {
            MaterialTheme {
              MyScreen()
            }
        }
    }
}

@Composable
fun MyScreen() {
    Box {
        LazyColumn(
            modifier = Modifier
                .fillMaxSize() // fill the entire window
                .imePadding() // padding for the bottom for the IME
                .imeNestedScroll(), // scroll IME at the bottom
            content = { }
        )
        FloatingActionButton(
            modifier = Modifier
                .align(Alignment.BottomEnd)
                .padding(16.dp) // normal 16dp of padding for FABs
                .navigationBarsPadding() // padding for navigation bar
                .imePadding(), // padding for when IME appears
            onClick = { }
        ) {
            Icon( /* ... */)
        }
    }
}

Ảnh động hiển thị phần tử giao diện người dùng cuộn lên và xuống để nhường chỗ cho bàn phím

Hình 2. Ảnh động IME

Ưu tiên phân chia trạng thái khỏi bản trình bày

Theo truyền thống, View là một trạng thái. View quản lý các trường mô tả nội dung cần hiển thị, ngoài cách thức hiển thị nội dung đó. Khi bạn chuyển đổi View sang Compose, hãy tìm cách tách riêng dữ liệu đang hiển thị sang luồng dữ liệu một chiều, như đã giải thích thêm ở phần chuyển trạng thái lên trên (state hoisting).

Ví dụ: View có thuộc tính visibility mô tả liệu thuộc tính này đang hiện, ẩn hay đã biến mất. Đây là một thuộc tính vốn có của View. Mặc dù các đoạn mã khác có thể làm thay đổi chế độ hiển thị của View, nhưng bản thân View chỉ thực sự biết chế độ hiển thị hiện tại của mã đó. Logic để đảm bảo rằng View hiển thị có thể dễ gặp lỗi và thường liên quan đến chính View.

Ngược lại, Compose giúp bạn dễ dàng hiển thị các thành phần kết hợp hoàn toàn khác nhau bằng cách sử dụng logic có điều kiện trong Kotlin:

if (showCautionIcon) {
    CautionIcon(/* ... */)
}

Theo thiết kế, CautionIcon không cần phải biết hoặc quan tâm đến lý do tại sao nội dung đó hiển thị và không có khái niệm về visibility: nội dung này nằm trong Thành phần kết hợp hoặc không.

Bằng cách tách biệt hoạt động quản lý trạng thái và logic trình bày, bạn có thể tự do thay đổi cách hiển thị nội dung dưới dạng lượt chuyển đổi trạng thái thành giao diện người dùng. Nhờ khả năng chuyển trạng thái lên trên khi cần, bạn cũng có thể sử dụng lại các Thành phần kết hợp nhiều lần hơn vì quyền sở hữu trạng thái linh hoạt hơn.

Tăng cấp các thành phần đã đóng gói và có thể sử dụng lại

Các phần tử View thường biết vị trí tồn tại của chính mình: bên trong Activity, Dialog, Fragment hoặc vị trí nào đó bên trong một hệ phân cấp View khác. Do những thành phần này thường được tăng cường từ các tệp bố cục tĩnh, nên cấu trúc tổng thể của View có xu hướng rất cứng nhắc. Điều này dẫn đến việc ghép nối chặt chẽ hơn và khiến View khó thay đổi hoặc sử dụng lại hơn.

Ví dụ: View tuỳ chỉnh có thể giả định rằng khung hiển thị này có khung hiển thị con thuộc một loại nhất định với một mã nhận dạng nhất định, đồng thời trực tiếp thay đổi các thuộc tính của loại đó để phản hồi một số hành động. Cách này giúp ghép nối chặt chẽ các phần tử View đó lại với nhau: Thành phần View tuỳ chỉnh có thể gặp sự cố hoặc bị lỗi nếu không tìm thấy thành phần con và có thể không sử dụng lại được nếu không có thành phần mẹ View tuỳ chỉnh.

Cách này ít gặp vấn đề hơn trong Compose nhờ các thành phần kết hợp có thể sử dụng lại. Thành phần mẹ có thể dễ dàng chỉ định trạng thái và lệnh gọi lại, nhờ đó, bạn có thể viết các Thành phần kết hợp có thể sử dụng lại mà không cần phải biết chính xác vị trí sẽ sử dụng những thành phần kết hợp này.

var isEnabled by rememberSaveable { mutableStateOf(false) }

Column {
    ImageWithEnabledOverlay(isEnabled)
    ControlPanelWithToggle(
        isEnabled = isEnabled,
        onEnabledChanged = { isEnabled = it }
    )
}

Trong ví dụ trên, cả ba phần đều được đóng gói nhiều hơn và ghép nối ít hơn:

  • ImageWithEnabledOverlay chỉ cần biết trạng thái hiện tại của isEnabled chứ không cần biết ControlPanelWithToggle có tồn tại hay không hoặc thậm chí là làm thế nào để kiểm soát.

  • ControlPanelWithToggle không biết rằng ImageWithEnabledOverlay tồn tại. Có thể không có, một hoặc nhiều cách để isEnabled hiển thị và ControlPanelWithToggle sẽ không phải thay đổi.

  • Đối với thành phần mẹ, bạn không cần quan tâm ImageWithEnabledOverlay hoặc ControlPanelWithToggle lồng nhau ở mức độ nào. Các thành phần con đó có thể đang tạo ảnh động cho các thay đổi, hoán đổi nội dung hoặc chuyển nội dung cho các thành phần con khác.

Mẫu này còn được gọi là đảo ngược quyền kiểm soát mà bạn có thể đọc thêm trong tài liệu về CompositionLocal.

Xử lý các thay đổi về kích thước màn hình

Một trong những cách chính để tạo bố cục View thích ứng là sử dụng nhiều tài nguyên cho các kích thước cửa sổ khác nhau. Mặc dù bạn vẫn có thể lựa chọn các tài nguyên đủ điều kiện khi đưa ra các quyết định về bố cục ở cấp màn hình, nhưng Compose sẽ giúp bạn thay đổi toàn bộ bố cục dễ dàng hơn chỉ bằng mã với logic có điều kiện thông thường. Hãy xem phần hỗ trợ nhiều kích thước màn hình để tìm hiểu thêm.

Ngoài ra, hãy tham khảo bài viết tạo bố cục thích ứng nếu bạn muốn tìm hiểu về các kỹ thuật mà Compose cung cấp để tạo giao diện người dùng thích ứng.

Thao tác cuộn dạng lồng với Khung hiển thị

Để biết thêm thông tin về cách hỗ trợ khả năng tương tác cuộn dạng lồng giữa các phần tử Khung hiển thị có thể cuộn và thành phần kết hợp có thể cuộn, được lồng theo cả hai hướng, hãy đọc qua phần Khả năng tương tác cuộn dạng lồng.

Compose trong RecyclerView

Các thành phần kết hợp trong RecyclerView hoạt động hiệu quả kể từ phiên bản RecyclerView 1.3.0-alpha02. Hãy đảm bảo bạn đang sử dụng ít nhất là phiên bản 1.3.0-alpha02 của RecyclerView để xem các lợi ích đó.