Sử dụng Khung hiển thị trong Compose

Bạn có thể đưa một hệ thống phân cấp thành phần hiển thị Android vào Giao diện người dùng của Compose. Phương pháp này đặc biệt hữu ích nếu bạn muốn sử dụng các thành phần trên giao diện người dùng chưa có trong Compose, chẳng hạn như AdView. Phương pháp này cũng cho phép bạn tái sử dụng các khung hiển thị tuỳ chỉnh mà bạn đã thiết kế.

Để thêm một phần tử hoặc hệ phân cấp khung hiển thị, hãy sử dụng AndroidView thành phần kết hợp. AndroidView sẽ được truyền một hàm lambda để trả về một lớp View. AndroidView cũng cung cấp một lệnh gọi lại lệnh update khi lượt xem tăng cao. AndroidView sẽ tái cấu trúc lại bất cứ khi nào một biểu thị State giữa các hàm callback thay đổi. Cũng như nhiều thành phần kết hợp được tích hợp sẵn khác, AndroidView sẽ sử dụng một tham số Modifier để đặt vị trí của nó trong thành phần kết hợp cha, chẳng hạn như vậy.

@Composable
fun CustomView() {
    var selectedItem by remember { mutableStateOf(0) }

    // Adds view to Compose
    AndroidView(
        modifier = Modifier.fillMaxSize(), // Occupy the max size in the Compose UI tree
        factory = { context ->
            // Creates view
            MyView(context).apply {
                // Sets up listeners for View -> Compose communication
                setOnClickListener {
                    selectedItem = 1
                }
            }
        },
        update = { view ->
            // View's been inflated or state read in this block has been updated
            // Add logic here if necessary

            // As selectedItem is read here, AndroidView will recompose
            // whenever the state changes
            // Example of Compose -> View communication
            view.selectedItem = selectedItem
        }
    )
}

@Composable
fun ContentExample() {
    Column(Modifier.fillMaxSize()) {
        Text("Look at this CustomView!")
        CustomView()
    }
}

AndroidView có tính năng liên kết thành phần hiển thị

Để nhúng bố cục XML bất kỳ, hãy sử dụng API AndroidViewBinding do thư viện androidx.compose.ui:ui-viewbinding cung cấp. Để làm được điều này, dự án của bạn cần bật tính năng liên kết khung hiển thị.

@Composable
fun AndroidViewBindingExample() {
    AndroidViewBinding(ExampleLayoutBinding::inflate) {
        exampleView.setBackgroundColor(Color.GRAY)
    }
}

AndroidView trong danh sách Lazy

Nếu bạn đang sử dụng AndroidView trong danh sách Lazy (LazyColumn, LazyRow, Pager, v.v.), hãy cân nhắc sử dụng AndroidView quá tải được giới thiệu trong phiên bản 1.4.0-rc01. Phương thức nạp chồng này cho phép Compose sử dụng lại thực thể View cơ bản khi thành phần chứa được sử dụng lại theo nguyên trạng trường hợp của danh sách Lazy.

Hàm quá tải AndroidView này sẽ thêm 2 tham số bổ sung:

  • onReset – Một lệnh gọi lại được gọi để báo hiệu rằng View sắp được gọi sử dụng lại. Giá trị này không được có giá trị rỗng để có thể sử dụng lại thành phần hiển thị.
  • onRelease (không bắt buộc) – Một lệnh gọi lại được gọi để báo hiệu rằng View đã đã thoát khỏi cấu trúc và sẽ không được sử dụng lại lần nữa.

@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun AndroidViewInLazyList() {
    LazyColumn {
        items(100) { index ->
            AndroidView(
                modifier = Modifier.fillMaxSize(), // Occupy the max size in the Compose UI tree
                factory = { context ->
                    MyView(context)
                },
                update = { view ->
                    view.selectedItem = index
                },
                onReset = { view ->
                    view.clear()
                }
            )
        }
    }
}

Các mảnh trong Compose

Sử dụng thành phần kết hợp AndroidViewBinding để thêm một Fragment vào Compose. AndroidViewBinding có quy trình xử lý dành riêng cho mảnh, chẳng hạn như xoá mảnh khi thành phần kết hợp rời khỏi cấu trúc.

Hãy làm việc này bằng cách tăng cường tệp XML chứa FragmentContainerView dưới dạng chủ sở hữu của Fragment.

Chẳng hạn nếu đã xác định được my_fragment_layout.xml, bạn có thể sử dụng mã như thế này trong khi thay thế thuộc tính XML android:name bằng tên lớp Fragment của bạn:

<androidx.fragment.app.FragmentContainerView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/fragment_container_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:name="com.example.compose.snippets.interop.MyFragment" />

Tăng cường mảnh này trong Compose như sau:

@Composable
fun FragmentInComposeExample() {
    AndroidViewBinding(MyFragmentLayoutBinding::inflate) {
        val myFragment = fragmentContainerView.getFragment<MyFragment>()
        // ...
    }
}

Nếu cần sử dụng nhiều mảnh trong cùng một bố cục, hãy đảm bảo là bạn đã xác định một mã nhận dạng duy nhất cho mỗi FragmentContainerView.

Gọi khung Android qua Compose

Compose hoạt động trong các lớp khung Android. Ví dụ: tệp được lưu trữ trên các lớp Khung hiển thị Android (như Activity hoặc Fragment) và có thể sử dụng các lớp khung Android (như Context), tài nguyên hệ thống, Service hoặc BroadcastReceiver.

Để tìm hiểu thêm về tài nguyên hệ thống, hãy xem trang Tài nguyên trong Compose.

Composition Locals (Thành phần kết hợp cục bộ)

Lớp CompositionLocal cho phép truyền dữ liệu trực tiếp thông qua các hàm tổng hợp. Những lớp này thường được cấp một giá trị nào đó ở một nút nhất định trong cây Giao diện người dùng. Giá trị con có thể kết hợp của hàm đó có thể sử dụng giá trị đó mà không cần khai báo lớp CompositionLocal dưới dạng tham số trong hàm có khả năng kết hợp.

Lớp CompositionLocal được dùng để truyền giá trị cho các loại khung Android trong Compose như Context, Configuration hoặc View mà trong đó mã Compose lưu trữ bởi các biến LocalContext, LocalConfiguration, hoặc LocalView tương ứng. Lưu ý rằng các lớp CompositionLocal có tiền tố Local để chức năng tự động điền trong IDE dễ nhận diện hơn.

Truy cập giá trị hiện tại của CompositionLocal bằng cách sử dụng thuộc tính current. Ví dụ: mã dưới đây sẽ hiển thị một thông báo ngắn bằng cách đưa LocalContext.current vào phương thức Toast.makeToast.

@Composable
fun ToastGreetingButton(greeting: String) {
    val context = LocalContext.current
    Button(onClick = {
        Toast.makeText(context, greeting, Toast.LENGTH_SHORT).show()
    }) {
        Text("Greet")
    }
}

Để có ví dụ hoàn chỉnh hơn, hãy xem thêm mục Nghiên cứu điển hình: BroadcastReceivers ở cuối tài liệu này.

Các hoạt động tương tác khác

Nếu không có tiện ích nào được xác định cho các hoạt động tương tác bạn cần thì cách tốt nhất là làm theo hướng dẫn Compose chung, dữ liệu chạy xuống, sự kiện chạy lên (được thảo luận kỹ hơn trong phần Tư duy trong Compose). Ví dụ: thành phần kết hợp này sẽ chạy một hoạt động khác:

class OtherInteractionsActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // get data from savedInstanceState
        setContent {
            MaterialTheme {
                ExampleComposable(data, onButtonClick = {
                    startActivity(Intent(this, MyActivity::class.java))
                })
            }
        }
    }
}

@Composable
fun ExampleComposable(data: DataExample, onButtonClick: () -> Unit) {
    Button(onClick = onButtonClick) {
        Text(data.title)
    }
}

Nghiên cứu điển hình: Broadcast receiver

Để có một ví dụ thực tế hơn về các tính năng mà bạn muốn di chuyển hoặc triển khai trong Compose và để hiển thị CompositionLocal cùng các hiệu ứng phụ, hãy giả sử BroadcastReceiver cần được đăng ký qua một hàm có khả năng kết hợp.

Giải pháp này tận dụng LocalContext để sử dụng ngữ cảnh hiện tại, cũng như rememberUpdatedState và các hiệu ứng phụ DisposableEffect.

@Composable
fun SystemBroadcastReceiver(
    systemAction: String,
    onSystemEvent: (intent: Intent?) -> Unit
) {
    // Grab the current context in this part of the UI tree
    val context = LocalContext.current

    // Safely use the latest onSystemEvent lambda passed to the function
    val currentOnSystemEvent by rememberUpdatedState(onSystemEvent)

    // If either context or systemAction changes, unregister and register again
    DisposableEffect(context, systemAction) {
        val intentFilter = IntentFilter(systemAction)
        val broadcast = object : BroadcastReceiver() {
            override fun onReceive(context: Context?, intent: Intent?) {
                currentOnSystemEvent(intent)
            }
        }

        context.registerReceiver(broadcast, intentFilter)

        // When the effect leaves the Composition, remove the callback
        onDispose {
            context.unregisterReceiver(broadcast)
        }
    }
}

@Composable
fun HomeScreen() {

    SystemBroadcastReceiver(Intent.ACTION_BATTERY_CHANGED) { batteryStatus ->
        val isCharging = /* Get from batteryStatus ... */ true
        /* Do something if the device is charging */
    }

    /* Rest of the HomeScreen */
}

Bước tiếp theo

Giờ đây, bạn đã biết các API có khả năng tương tác khi sử dụng Compose trong Khung hiển thị và ngược lại, hãy khám phá trang Những điểm cần cân nhắc khác để tìm hiểu thêm.