在您的App中導入 Compose,把 Compose和view-based UI 結合一起。以下清單中提供一些 API、建議與秘訣,可幫助您輕鬆轉換到 Compose。
在檢視畫面中的 Compose
你可以將Compose-based UI 加到採用view-based設計的既有App。
如要建立以 Compose 為基礎的新畫面,請讓活動呼叫 setContent()
方法,然後視需要傳遞任何可組合函式。
class ExampleActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent { // In here, we can call composables!
MaterialTheme {
Greeting(name = "compose")
}
}
}
}
@Composable
fun Greeting(name: String) {
Text(text = "Hello $name!")
}
這段程式碼看起來就像 Compose-only App中會出現的寫法。
ComposeView 的 ViewCompositionStrategy
當檢視畫面從視窗卸離時,Compose 在預設情況下會處置Composition。Compose UI View
類型 (例如 ComposeView
和 AbstractComposeView
) 是使用ViewCompositionStrategy
來定義此行為。
Compose 在預設情況下是使用 DisposeOnDetachedFromWindow
策略。但在某些使用 Compose UI View
類型的情況下,這個預設值是不適合的:
Fragment。Composition 必須遵循 Fragment 的檢視生命週期,使 Compose UI
View
類型能夠儲存狀態。轉換。只要在轉換中使用 Compose UI
View
,當轉換開始 (而非轉換結束),系統就會從視窗卸離,使您的可編輯卸離其仍在螢幕上的狀態。RecyclerView
查看 holders,或由您自行管理的生命週期自訂View
。
在某些情況下,除非您手動呼叫 AbstractComposeView.disposeComposition
,否則應用程式將緩慢地無法從 Composition 執行個體釋放出記憶體。
請設定其他策略、或透過呼叫 setViewCompositionStrategy
方法,建立自己的策略,自動地處置不再需要的 Composition。例如,lifecycle
遭刪除時,DisposeOnLifecycleDestroyed
策略會處置Composition。此策略適用於與已知 LifecycleOwner
共用 1 對 1 關係的Compose UI View
類型。如果不知道 LifecycleOwner
,可以使用 DisposeOnViewTreeLifecycleDestroyed
。
如需查看這個 API 的實際應用情形,請參閱 Fragment 區節中的 ComposeView。
Fragment 中的 ComposeView
如要在片段或現有的檢視畫面版面配置中加入 Compose UI,請使用 ComposeView
並呼叫其 setContent()
方法。ComposeView
是 Android View
。
您可將 ComposeView
和其他的 View
一樣地放在 XML 版面配置中:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/hello_world"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Hello Android!" />
<androidx.compose.ui.platform.ComposeView
android:id="@+id/compose_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
請在 Kotlin 原始碼中,從 XML 定義的版面配置資源加載版面配置。然後使用 XML ID 取得 ComposeView
,並設定最適合代管 View
的 Composition 策略,並呼叫 setContent()
以使用 Compose。
class ExampleFragment : Fragment() {
private var _binding: FragmentExampleBinding? = null
// This property is only valid between onCreateView and onDestroyView.
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentExampleBinding.inflate(inflater, container, false)
val view = binding.root
binding.composeView.apply {
// Dispose of the Composition when the view's LifecycleOwner
// is destroyed
setViewCompositionStrategy(DisposeOnViewTreeLifecycleDestroyed)
setContent {
// In Compose world
MaterialTheme {
Text("Hello Compose!")
}
}
}
return view
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
圖 1. 這個程式碼會顯示在 View UI 階層中新增 Compose 元素的程式碼輸出內容。「Hello Android!」文字會以 TextView
小工具顯示。「您好 Compose!」文字會顯示Compose 文字元素。
假如您使用 Compose 建立全螢幕畫面,可以直接在片段中加入 ComposeView
,避免完全使用 XML 版面配置檔案。
class ExampleFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
return ComposeView(requireContext()).apply {
// Dispose of the Composition when the view's LifecycleOwner
// is destroyed
setViewCompositionStrategy(DisposeOnViewTreeLifecycleDestroyed)
setContent {
MaterialTheme {
// In Compose world
Text("Hello Compose!")
}
}
}
}
}
如果在同一個版面配置中有多個 ComposeView
元素,則每個元素都必須有專屬 ID 供 savedInstanceState
運作。
class ExampleFragment : Fragment() {
override fun onCreateView(...): View = LinearLayout(...).apply {
addView(ComposeView(...).apply {
id = R.id.compose_view_x
...
})
addView(TextView(...))
addView(ComposeView(...).apply {
id = R.id.compose_view_y
...
})
}
}
}
res/values/ids.xml
檔案已定義 ComposeView
ID:
<resources>
<item name="compose_view_x" type="id" />
<item name="compose_view_y" type="id" />
</resources>
在 Compose 中的檢視畫面
您可以在 Compose UI 中加入 Android 檢視區塊階層。如要使用 Compose 尚未提供的 UI 元素 (例如 AdView
),這種做法就特別實用。您也可以透過這種做法重複使用自己設計的自訂檢視畫面。
如要加入檢視表元素或階層,請使用 AndroidView
可組合式。AndroidView
會傳遞一個傳回 View
的 lambda。AndroidView
也提供了 update
回呼,當 view 加載時呼叫的回呼。AndroidView
會在每次回呼變更時,讀取 State
時重組。舉例來說,如同其他內建可組合函式,AndroidView
會透過可用的 Modifier
參數設定其本身在上層可組合函式中的位置。
@Composable
fun CustomView() {
val selectedItem = remember { mutableStateOf(0) }
// Adds view to Compose
AndroidView(
modifier = Modifier.fillMaxSize(), // Occupy the max size in the Compose UI tree
factory = { context ->
// Creates custom view
CustomView(context).apply {
// Sets up listeners for View -> Compose communication
myView.setOnClickListener {
selectedItem.value = 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.coordinator.selectedItem = selectedItem.value
}
)
}
@Composable
fun ContentExample() {
Column(Modifier.fillMaxSize()) {
Text("Look at this CustomView!")
CustomView()
}
}
如要嵌入 XML 版面配置,請使用 androidx.compose.ui:ui-viewbinding
程式庫提供的 AndroidViewBinding
API。如要這麼做,您的專案必須啟用 檢視畫面繫結。
@Composable
fun AndroidViewBindingExample() {
AndroidViewBinding(ExampleLayoutBinding::inflate) {
exampleView.setBackgroundColor(Color.GRAY)
}
}
Compose 中的片段
使用 AndroidViewBinding
可組合函式在 Compose 中新增 Fragment
。方法是:將包含 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_height="match_parent"
android:layout_width="match_parent"
android:name="com.example.MyFragment" />
在 Compose 中以下列方式展開這個片段:
@Composable
fun FragmentInComposeExample() {
AndroidViewBinding(MyFragmentLayoutBinding::inflate) {
val myFragment = fragmentContainerView.getFragment<MyFragment>()
// ...
}
}
如果您在同一個版面配置需要使用多個片段,請確定您已為每個 FragmentContainerView
定義專屬 ID。
從 Compose 呼叫 Android 架構
Compose 會在 Android 架構類別內運作。舉例來說,託管於 Android View 類別 (例如 Activity
或 Fragment
),而且可能需要使用 Context
的 Android 架構類別、系統資源、Service
或 BroadcastReceiver
。
如要進一步瞭解系統資源,請參閱 Compose 中的資源說明文件。
Composition Locals
CompositionLocal
類別可讓使用者透過可編輯的函式以默示方式傳送資料。通常在 UI 樹狀結構的特定節點中提供值。該值可用在可撰寫的子系中,但不必將 CompositionLocal
宣告為可組合函式中的參數。
CompositionLocal
用於在 Compose 中傳播 Android 架構類型的值,例如 Context
、Configuration
或 View
其中,Compose 程式碼是由對應的 LocalContext
,LocalConfiguration
,或 LocalView
。請注意,CompositionLocal
類別前面會加上 Local
,以在 IDE 中使用自動完成功能來進一步發現。
使用 current
屬性存取 CompositionLocal
目前的值。舉例來說,以下程式碼就是使用Context
在撰寫使用者介面樹狀結構的呼叫中,呼叫 LocalContext.current
的 Google Ads 新帳戶重新申請驗證。
@Composable
fun rememberCustomView(): CustomView {
val context = LocalContext.current
return remember { CustomView(context).apply { /*...*/ } }
}
如需更完整的範例,請參閱本文結尾處的個案研究:BroadcastReceivers 部分。
其他互動
如果沒有根據所需互動定義的公用程式,最佳做法就是按照一般 Compose 指南,讓資料向下流動並讓活動向上流動。如要進一步瞭解如何運用 Compose,請參閱這篇文章。舉例來說,這個合成事件會啟動其他活動:
class ExampleActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// get data from savedInstanceState
setContent {
MaterialTheme {
ExampleComposable(data, onButtonClick = {
startActivity(/*...*/)
})
}
}
}
}
@Composable
fun ExampleComposable(data: DataExample, onButtonClick: () -> Unit) {
Button(onClick = onButtonClick) {
Text(data.title)
}
}
個案研究:BroadcastReceiver
如需實際範例,建議您在 Compose 中遷移或實作。如要展示 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 */
}