在 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 { mutableIntStateOf(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)
    }
}

AndroidView 中的 Lazy 清單

如果您在 Lazy 清單 (LazyColumn、LazyRow、Pager 等) 中使用 AndroidView,請考慮使用 1.4.0-rc01 版中導入的 AndroidView 多載。當包含的組合項重複使用時 (Lazy 清單就是這種情況),Compose 就能重複使用基礎 View 例項。

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

  • onReset - 呼叫的回呼,用來發出 View 即將重複使用的信號。這必須為非空值,才能啟用檢視區塊重複使用功能。
  • onRelease (選用) - 系統會叫用這個回呼,表示 View 已退出組合,不會再次重複使用。

@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 中的片段 (過渡步驟)

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

如要加入片段,請使用 AndroidFragment 可組合函式。您將 Fragment 類別傳遞至 AndroidFragment,然後會直接將該類別的例項新增 至組合中。AndroidFragment 也提供 fragmentState 物件,可使用指定狀態建立 AndroidFragment、傳遞至新片段的 arguments,以及提供組合片段的 onUpdate 回呼。如同其他內建可組合函式,AndroidFragment 會接受 Modifier 參數,您可以使用該參數設定其在上層可組合函式中的位置。

在 Compose 中呼叫 AndroidFragment,如下所示:

@Composable
fun FragmentInComposeExample() {
    AndroidFragment<MyFragment>()
}

上限片段生命週期狀態

在 Fragment 1.9.0 以上版本中,您可以使用 maxLifecycle 參數,限制內嵌 Fragment 的生命週期狀態。

舉例來說,在 HorizontalPager 中,您只能在相應頁面處於有效狀態時,動態將 maxLifecycle 設為 RESUMED,讓螢幕外頁面維持在 STARTED。當使用者前往其他頁面時,前一頁的片段上限會變成 STARTED。這會暫停或取消範圍限定為 RESUMED 的工作負載 (例如在 repeatOnLifecycle(RESUMED) 中執行的協同程式),防止螢幕外網頁執行僅限前景的工作,同時將 UI 初始化作業保留在 STARTED:

HorizontalPager(state = pagerState) { page ->
    // Dynamically cap the lifecycle state based on whether the page is selected
    val maxLifecycle = if (pagerState.settledPage == page) {
        Lifecycle.State.RESUMED
    } else {
        Lifecycle.State.STARTED
    }

    when (page) {
        0 -> AndroidFragment<HomeFragment>(maxLifecycle = maxLifecycle)
        1 -> AndroidFragment<LibraryFragment>(maxLifecycle = maxLifecycle)
        /* Other pages and corresponding fragments */
    }
}

從 Compose 呼叫 Android 架構

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

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

Composition Locals

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

CompositionLocal 的用途是在 Compose 中傳遞 Context、Configuration 或 View 等 Android 架構類型的值,其中的 Compose 程式碼是由對應的 LocalContext,LocalConfiguration,或 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")
    }
}

廣播接收器

如要展示 CompositionLocal 和副作用 (例如 BroadcastReceiver),您必須從可組合函式註冊該元件,並使用 LocalContext 來使用目前結構定義,以及 rememberUpdatedState 和 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 */
}

其他互動

如果沒有根據所需互動定義的公用程式,最佳做法就是按照一般 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)
    }
}