在 Compose 中使用 View

您可以在 Compose UI 中加入 Android 檢視區塊階層。如要使用 Compose 尚未提供的 UI 元素 (例如 AdView),這種做法就特別實用。您也可以透過這種做法重複使用自己設計的自訂檢視畫面。

如要加入檢視區塊元素或階層,請使用 AndroidView 可組合項。AndroidView 會傳遞一個傳回 View 的 lambda。AndroidView 也提供了 update回呼,當 view 加載時呼叫的回呼。AndroidView 會在每次回呼變更時,讀取 State 時重組。舉例來說,如同其他內建可組合項,AndroidView 會透過可用的 Modifier 參數設定其本身在上層可組合項中的位置。

@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 搭配檢視區塊繫結

如要嵌入 XML 版面配置,請使用 androidx.compose.ui:ui-viewbinding 程式庫提供的 AndroidViewBinding API。如要這麼做,您的專案必須啟用檢視區塊繫結

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

Lazy 清單中的 AndroidView

如果您在 Lazy 清單 (LazyColumnLazyRowPager 等) 中使用 AndroidView,建議您使用 1.4.0-rc01 版本中引入的 AndroidView 超載。當含有組合項目的 Lazy 清單重複使用時,這個超載可讓 Compose 重複使用基礎 View 例項。

這個 AndroidView 的超載會新增 2 個額外參數:

  • onReset:這個回呼會叫用,用來表示 View 即將重複使用。不得為空值,才能重複使用 View。
  • onRelease (選用) - 系統會叫用回呼,以表示 View 已退出組合,且不會再重複使用。

@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()
                }
            )
        }
    }
}

Compose 中的片段

使用 AndroidViewBinding 可組合項在 Compose 中新增 FragmentAndroidViewBinding 採用片段特定處理方式,例如,在可組合項離開組合時移除片段。

方法是:將包含 FragmentContainerView 的 XML 展開做為 Fragment 的持有者。

舉例來說,如果您定義了 my_fragment_layout.xml,則可以使用這樣的程式碼,並將 android:name XML 屬性替換為 Fragment 的類別名稱:

<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" />

在 Compose 中以下列方式展開這個片段:

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

如果您需要在同一個版面配置中使用多個片段,請務必為每個 FragmentContainerView 定義專屬 ID。

從 Compose 呼叫 Android 架構

Compose 會在 Android 架構類別內運作。舉例來說,它託管於 Android View 類別 (例如 ActivityFragment),並可能使用 Context 之類的 Android 架構類別、系統資源、ServiceBroadcastReceiver

如要進一步瞭解系統資源,請參閱「Compose 中的資源」。

Composition Locals

CompositionLocal 類別可讓使用者透過可編輯的函式以默示方式傳送資料。通常在 UI 樹狀結構的特定節點中提供值。該值可用在可撰寫的子系中,但不必將 CompositionLocal 宣告為可組合項中的參數。

CompositionLocal 的用途是在 Compose 中傳遞 ContextConfigurationView 等 Android 架構類型的值,其中的 Compose 程式碼是由對應的 LocalContextLocalConfiguration,或 LocalView 所代管。請注意,CompositionLocal 類別前面會加上 Local,以在 IDE 中使用自動完成功能來進一步發現。

使用 current 屬性存取 CompositionLocal 目前的值。舉例來說,以下程式碼會在 Toast.makeToast 方法中提供 LocalContext.current,以顯示浮動式訊息。

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

如需更完整的範例,請參閱本文結尾處的個案研究:BroadcastReceivers 部分。

其他互動

如果沒有根據所需互動定義的公用程式,最佳做法就是按照一般 Compose 指南,讓資料向下流動並讓活動向上流動。如要進一步瞭解如何運用 Compose,請參閱這篇文章。舉例來說,這個合成事件會啟動其他活動:

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)
    }
}

個案研究:廣播接收器

如需實際範例,建議您在 Compose 中遷移或實作。如要展示 CompositionLocal副作用 (例如 BroadcastReceiver),您必須從可組合函式註冊該元件。

這項解決方案是使用 LocalContext,來使用目前結構定義,以及 rememberUpdatedStateDisposableEffect 副作用。

@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 */
}

後續步驟

現在,您已瞭解在 View 中使用 Compose 時的互通性 API,反之亦然,請參閱「其他注意事項」頁面以瞭解詳情。