雖然從 View 遷移至 Compose 純粹與 UI 相關,但 執行安全逐步遷移作業時需要考量的事項這個 頁面,瞭解將 以 View 為基礎的應用程式對 Compose。
遷移應用程式主題
Material Design 是為 Android 應用程式設定主題的推薦設計系統。
以 View 為基礎的應用程式有三種 Material 版本:
- Material Design 1 使用 AppCompat 程式庫 (即
Theme.AppCompat.*
) - Material Design 2
MDC-Android
程式庫 (例如
Theme.MaterialComponents.*
) - Material Design 3
MDC-Android
程式庫 (例如
Theme.Material3.*
)
Compose 應用程式有兩種可用的 Material 版本:
- Material Design 2 使用 Compose Material 程式庫 (即
androidx.compose.material.MaterialTheme
) - Material Design 3 使用 Compose Material 3 程式庫 (即
androidx.compose.material3.MaterialTheme
)
如果您應用程式的設計系統,建議您使用最新版本 (Material 3) 可以進行相關操作這兩個檢視表都有遷移指南 以及 Compose:
- 在 View 中從 Material 1 遷移至 Material 2
- 在 View 中從 Material 2 遷移至 Material 3
- 在 Compose 中從 Material 2 遷移至 Material 3
無論 Material 版本為何,在 Compose 中建立新畫面時
採用的設計方式,務必先套用 MaterialTheme
會從 Compose Material 程式庫輸出 UI 的可組合函式。材質
元件 (Button
、Text
等) 需要使用 MaterialTheme
沒有的話,使用者的行為將處於未定義狀態。
所有語言
Jetpack Compose 範例
使用以 MaterialTheme
為基礎建構的自訂 Compose 主題。
詳情請參閱 Compose 中的設計系統和將 XML 主題遷移至 Compose。
導覽
如果您在應用程式中使用 Navigation 元件,請參閱 請參閱 Compose 導覽 - 互通性和 詳情請參閱將 Jetpack Navigation 遷移至 Navigation Compose。
測試混合的 Compose/View 使用者介面
將應用程式的各個部分遷移至 Compose 後,請務必進行測試來確保 你沒有任何破壞的狀況
當活動或片段使用 Compose 時,您需要使用
createAndroidComposeRule
敬上
而不是使用 ActivityScenarioRule
「createAndroidComposeRule
」整合項目
使用 ComposeTestRule
搭配 ActivityScenarioRule
,可讓您測試 Compose
同時查看程式碼。
class MyActivityTest { @Rule @JvmField val composeTestRule = createAndroidComposeRule<MyActivity>() @Test fun testGreeting() { val greeting = InstrumentationRegistry.getInstrumentation() .targetContext.resources.getString(R.string.greeting) composeTestRule.onNodeWithText(greeting).assertIsDisplayed() } }
如要進一步瞭解如何測試,請參閱「測試 Compose 版面配置」。適用對象 與 UI 測試架構互通,請參閱 與 Espresso 互通性和 與 UiAutomator 的互通性。
整合 Compose 與現有的應用程式架構
單向資料流程 (UDF) 架構 這些模式可與 Compose 完美搭配運作如果應用程式使用其他類型 建議您改用 Model View Presenter (MVP) 等架構模式 在採用 Compose 之前或期間,將 UI 的一部分遷移至 UDF。
在 Compose 中使用 ViewModel
如果您使用架構元件
ViewModel
程式庫,您就能存取
透過以下方式寫入任何可組合函式的 ViewModel
呼叫
viewModel()
函式,如 Compose 和其他程式庫所述。
如果採用 Compose,在不同的可組合函式中使用相同的 ViewModel
類型時請務必謹慎,因為 ViewModel
元件會追蹤檢視畫面生命週期的範圍。
範圍可以是代管活動、片段或導覽圖
使用 Navigation 程式庫。
舉例來說,如果可組合項在活動中代管,viewModel()
一律會顯示
會傳回只在活動完成時清除的同一例項。
在以下範例中,系統會問同一個使用者 (「user1」) 兩次,因為
同一個 GreetingViewModel
例項會在
主機活動。第一個建立的 ViewModel
執行個體會重複用於其他執行個體
可組合函式
class GreetingActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { MaterialTheme { Column { GreetingScreen("user1") GreetingScreen("user2") } } } } } @Composable fun GreetingScreen( userId: String, viewModel: GreetingViewModel = viewModel( factory = GreetingViewModelFactory(userId) ) ) { val messageUser by viewModel.message.observeAsState("") Text(messageUser) } class GreetingViewModel(private val userId: String) : ViewModel() { private val _message = MutableLiveData("Hi $userId") val message: LiveData<String> = _message } class GreetingViewModelFactory(private val userId: String) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") override fun <T : ViewModel> create(modelClass: Class<T>): T { return GreetingViewModel(userId) as T } }
由於導覽圖也會設定 ViewModel
元素的範圍,因此導覽圖中要做為目的地的可組合函式就會有不同的 ViewModel
執行個體。在這種情況下,ViewModel
的範圍限定在目的地的生命週期內,而
如果目的地從返回堆疊中移除,系統會清除該物件。在以下範例中,當使用者前往「設定檔」畫面時,系統就會建立新的 GreetingViewModel
執行個體。
@Composable fun MyApp() { NavHost(rememberNavController(), startDestination = "profile/{userId}") { /* ... */ composable("profile/{userId}") { backStackEntry -> GreetingScreen(backStackEntry.arguments?.getString("userId") ?: "") } } }
狀態的可靠資料來源
如果在 UI 的某個部分採用 Compose,Compose 和
View 系統程式碼需要共用資料我們建議你盡可能
將共用狀態封裝在遵循 UDF 最佳做法的其他類別中
用於兩個平台例如,在 ViewModel
公開
發出資料更新
但是,如果共用的資料可變動,或與 UI 元素有緊密關聯,此方法不一定可行。在此情況下,來源必須是一個系統 因此該系統必須與其他系統分享任何資料更新。原則上,真實資訊來源都應來自較接近 UI 根層級的元件。
Compose 做為可靠資料來源
使用
SideEffect
敬上
的可組合函式,將 Compose 狀態發布至非 Compose 程式碼。在此情況下,
可靠資料來源會保留在傳送狀態更新的可組合項中。
舉例來說,數據分析資料庫可能讓您區隔使用者
附加自訂中繼資料來填入人口 (此範例為使用者屬性)
套用至所有後續 Analytics 事件如要將目前使用者的使用者類型連接到數據分析程式庫,請使用 SideEffect
更新其值。
@Composable fun rememberFirebaseAnalytics(user: User): FirebaseAnalytics { val analytics: FirebaseAnalytics = remember { FirebaseAnalytics() } // On every successful composition, update FirebaseAnalytics with // the userType from the current User, ensuring that future analytics // events have this metadata attached SideEffect { analytics.setUserProperty("userType", user.userType) } return analytics }
詳情請參閱「Compose 中的連帶效果」。
View 系統做為可靠資料來源
如果 View 系統擁有狀態並與 Compose 共用,建議您
您會將狀態納入 mutableStateOf
物件中,讓狀態對於
。使用這個方法會簡化可組合函式,因為
而不再擁有可靠資料來源,但 View 系統就需要更新
可變動狀態和使用該狀態的 View
在以下範例中,CustomViewGroup
包含 TextView
,以及內有 TextField
可組合函式的 ComposeView
。TextView
必須顯示 TextField
中的使用者類型內容。
class CustomViewGroup @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyle: Int = 0 ) : LinearLayout(context, attrs, defStyle) { // Source of truth in the View system as mutableStateOf // to make it thread-safe for Compose private var text by mutableStateOf("") private val textView: TextView init { orientation = VERTICAL textView = TextView(context) val composeView = ComposeView(context).apply { setContent { MaterialTheme { TextField(value = text, onValueChange = { updateState(it) }) } } } addView(textView) addView(composeView) } // Update both the source of truth and the TextView private fun updateState(newValue: String) { text = newValue textView.text = newValue } }
遷移共用的 UI
如要逐步遷移至 Compose,您可能需要在 Compose 和 View 系統中都使用共用的 UI 元素。舉例來說,如果應用程式具有自訂的 CallToActionButton
元件,您可能需要在 Compose 和以 View 為基礎的螢幕中都使用該元件。
在 Compose 中,共用的 UI 元素會成為可在整個應用程式中重複使用的可組合項
應用程式。舉例來說,您可以建立適用於自訂行動號召 Button
元件的 CallToActionButton
可組合元件。
如要在以 View 為基礎的畫面中使用可組合函式,請建立自訂檢視畫麵包裝函式,
從 AbstractComposeView
開始。在其覆寫的 Content
可組合函式中,
將您建立的可組合函式納入 Compose 主題中,如
範例如下:
@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) } } }
請注意,可組合參數在自訂檢視中會變為可變動變數。如此一來,自訂 CallToActionViewButton
檢視區塊就會擴充且可供使用。
就和傳統觀點一樣查看搭配使用檢視區塊繫結功能的範例
如下:
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 */ } } } }
如果自訂元件包含可變動狀態,請參閱「狀態真實資訊來源」一節。
優先考慮將狀態從展示檔中分離出來
一般來說,View
是有狀態的。View
負責管理說明顯示 內容 的欄位,以及顯示 方式。將 View
轉換為 Compose 時,請考慮將轉譯的資料分隔開來,以達成單向資料流,如 狀態提升 中進一步說明的那樣。
舉例來說,View
具有 visibility
屬性,用於說明該屬性是可見的、隱藏的或是消失了。這是 View
的固有屬性。雖然其他程式碼可能會變更 View
的瀏覽權限,但只有 View
本身才知道目前的瀏覽權限是哪些。確保 View
可見的邏輯可能出錯,且通常與 View
本身有關。
相較之下,在使用 Kotlin 中有條件的邏輯時,Compose 能輕鬆顯示完全不同的可組合元件:
@Composable fun MyComposable(showCautionIcon: Boolean) { if (showCautionIcon) { CautionIcon(/* ... */) } }
在設計上,CautionIcon
沒有必要瞭解或在意其顯示的原因,也沒有 visibility
的概念:它如果不在 Composition 中,便是不在。
只要將狀態管理和呈現邏輯明確區隔,即可輕鬆將顯示內容方式變更為 UI 狀態的轉換項目。演唱 能夠在需要時提升狀態,也讓可組合項更容易重複使用 狀態擁有權更加靈活。
升級經過封裝的和可重複使用的元件
View
元素通常對於自己所在位置有一定瞭解:例如在 Activity
、Dialog
、Fragment
中或在另一個 View
階層中的某個位置。這些類型通常是從靜態版面配置檔案中加載而來,因此 View
的整體結構往往相當嚴謹。這種做法可以建立更緊密的耦合,還會讓 View
更加難以變更或重複使用。
舉例來說,自訂 View
可能會假設其包含特定類型的子項檢視畫面,當中含有特定 ID,然後直接變更其屬性以回應某些動作。這會將這些 View
元素緊密結合:自訂 View
如果找不到孩子,且孩子可能無法找到孩子,可能會導致裝置當機或異常終止
方法,讓物件在沒有自訂 View
父項的情況下重複使用。
在 Compose 中,由於有重複使用的可組合元件,這個問題就不那麼嚴重了。家長可以 輕鬆指定狀態和回呼,因此您可以編寫可重複使用的可組合函式 而不必知道這些點擊的確切使用位置
@Composable fun AScreen() { var isEnabled by rememberSaveable { mutableStateOf(false) } Column { ImageWithEnabledOverlay(isEnabled) ControlPanelWithToggle( isEnabled = isEnabled, onEnabledChanged = { isEnabled = it } ) } }
在上例中,三個部分的狀態都是封裝更嚴密,耦合性更少:
ImageWithEnabledOverlay
只需要知道目前的isEnabled
請先進行說明不需要知道ControlPanelWithToggle
是否存在; 甚至可以控制如何控制ControlPanelWithToggle
不知道ImageWithEnabledOverlay
的存在。可能無法顯示isEnabled
,也可能有一個或多個方法將其顯示出來,而ControlPanelWithToggle
則無需變更。對父項而言,巢狀
ImageWithEnabledOverlay
或ControlPanelWithToggle
的深度並不重要。這些子項可以是動畫改變、調換內容,或是將內容傳遞給其他子項。
此模式被稱為 反向控制,詳情請參閱 CompositionLocal
說明文件。
處理螢幕大小變更
建立回應式 View
版面配置的主要方式之一就是為不同大小的視窗提供不同的資源。雖然符合條件的資源仍然適用於確定螢幕級別的版面配置,但使用 Compose 時,在程式碼中使用一般條件邏輯即可更加輕鬆地完全變更版面配置。詳情請參閱「視窗大小類別」。
此外,請參閱「支援不同的螢幕大小」。 瞭解 Compose 提供的建構自動調整式 UI 的相關技巧。
View 巢狀結構捲動
如果想進一步瞭解如何啟用可在捲動式檢視畫面與捲動式可組合函式之間同時使用的巢狀捲動互通性,讓兩個方向皆使用巢狀結構,請閱讀「巢狀捲動互通性」一節。
RecyclerView
中的 Compose
RecyclerView
中的可組合函式自 RecyclerView
版起就獲得了良好成效
1.3.0-alpha02。確認您使用 1.3.0-alpha02 以上的版本
RecyclerView
,即可查看這些福利。
WindowInsets
與 View 的互通性
當螢幕同時擁有 View 和 位於同一階層的 Compose 程式碼。在這種情況下,您必須 哪些應使用插邊,哪些則應忽略插邊。
舉例來說,如果最外層的版面配置是 Android View 版面配置,則您應
請使用 View 系統中的插邊,並針對 Compose 忽略這些插邊。
或者,如果最外的版面配置是可組合項,則應使用
Compose 中的插邊,然後據此為 AndroidView
可組合函式加上邊框間距。
根據預設,每個 ComposeView
都會使用
WindowInsetsCompat
用量。如要變更這項預設行為,請設定
ComposeView.consumeWindowInsets
敬上
至 false
。
詳情請參閱 Compose 中的 WindowInsets
說明文件。
為您推薦
- 注意:系統會在 JavaScript 關閉時顯示連結文字
- 顯示表情符號
- Compose 中的質感設計 2
- Compose 中的視窗插邊