Jetpack?Compose狀態(tài)專篇精講
應用中的狀態(tài)是指可以隨時間變化的任何值。這是一個非常寬泛的定義,從 Room 數據庫到類的變量,全部涵蓋在內。
由于Compose是聲明式UI,會根據狀態(tài)變化來更新UI,因此狀態(tài)的處理至關重要。這里的狀態(tài)你可以簡單理解為頁面上展示的數據,那么狀態(tài)管理就是處理數據的讀寫。
1.remember
remember
就是用來保存狀態(tài)的,下面舉一個小例子。
@Composable fun HelloContent() { Column(modifier = Modifier.padding(16.dp)) { OutlinedTextField( value = "", onValueChange = { }, label = { Text("Name") } ) } }
比如我們在頁面中加了一個輸入框,如果只是上面代碼中這樣處理,那你會發(fā)現我們輸入的文字不會被記錄起來,輸入框中始終都是空的。這是因為屬性value
被固定成了空字符串。我們使用remember
優(yōu)化一下:
@Composable fun HelloContent() { val inputValue = remember { mutableStateOf("") } Column(modifier = Modifier.padding(16.dp)) { OutlinedTextField( value = inputValue.value, onValueChange = { inputValue.value = it }, label = { Text("Name") } ) } }
通過onValueChange
更新value,mutableStateOf
會創(chuàng)建可觀察的 MutableState<T>
,value 變更時,系統(tǒng)會重組讀取 value 的所有Composable
函數,這樣就會自動更新UI。
Jetpack Compose 并不強制要求你使用 MutableState 存儲狀態(tài)。Jetpack Compose 支持其他可觀察類型。在 Jetpack Compose 中讀取其他可觀察類型之前,您必須將其轉換為 State,以便 Jetpack Compose 可以在狀態(tài)發(fā)生變化時自動重組界面。
LiveData
中可以使用擴展函數observeAsState()
轉換為 State。Flow
中可以使用擴展函數collectAsState()
轉換為 State。RxJava
中可以使用擴展函數subscribeAsState()
轉換為 State。
2.rememberSaveable
雖然 remember
可幫助您在重組后保持狀態(tài),但不會幫助您在配置更改后保持狀態(tài)。為此,您必須使用 rememberSaveable
。rememberSaveable
會自動保存可保存在 Bundle 中的任何值。
還是上面的例子,如果我們旋轉屏幕,就會發(fā)現輸入框中的文字會丟失。此時就可以使用rememberSaveable
替換remember
來幫助我們恢復界面狀態(tài)。
由于保存的數據都是在 Bundle
中的,因此可保存的數據類型是有限制的。比如基礎類型、String、Parcelable,Serializable等。一般來說需要保存的對象加個 @Parcelize
注解就可以解決問題。
如果某種原因導致無法使用 @Parcelize
,你可以使用 mapSaver
自定義規(guī)則,定義如何將對象保存和恢復到 Bundle。
data class City(val name: String, val country: String) val CitySaver = run { val nameKey = "Name" val countryKey = "Country" mapSaver( save = { mapOf(nameKey to it.name, countryKey to it.country) }, restore = { City(it[nameKey] as String, it[countryKey] as String) } ) } @Composable fun CityScreen() { var selectedCity = rememberSaveable(stateSaver = CitySaver) { mutableStateOf(City("Madrid", "Spain")) } }
如果你覺得定義map的key麻煩,可以使用 listSaver
并將其索引用作鍵。
data class City(val name: String, val country: String) val CitySaver = listSaver<City, Any>( save = { listOf(it.name, it.country) }, restore = { City(it[0] as String, it[1] as String) } ) @Composable fun CityScreen() { var selectedCity = rememberSaveable(stateSaver = CitySaver) { mutableStateOf(City("Madrid", "Spain")) } }
3.狀態(tài)提升
對于上面使用到remember
或rememberSaveState
方法來保存狀態(tài)的Composable
函數,我們稱為有狀態(tài)。有狀態(tài)的好處是調用方不需要控制狀態(tài),并且不必自行管理狀態(tài)。但是,具有內部狀態(tài)的Composable
往往不易重復使用,也更難測試。
在開發(fā)可重復使用的Composable
時,您通常想要同時提供同一Composable
的有狀態(tài)和無狀態(tài)版本。有狀態(tài)版本對于不關心狀態(tài)的調用方來說很方便,而無狀態(tài)版本對于需要控制或提升狀態(tài)的調用方來說是必要的。
Compose 中的狀態(tài)提升是一種將狀態(tài)移至調用方以使可組合項無狀態(tài)的模式。
舉例說明一下狀態(tài)提升,比如我們實現一個Dialog,為了方便使用我們可以將里面顯示的文字,點擊事件邏輯寫到dialog的內部封裝起來,雖然使用簡單但不具有通用性。那么為了通用,我們可以將文字,點擊事件的回調當參數傳入,這樣就靈活了起來。
狀態(tài)提升其實就是這樣一個編程思想,只是換了個名詞,沒有什么特別了。
對于上面輸入框的例子,我們用狀態(tài)提示優(yōu)化一下:
@Composable fun HelloScreen() { var name by rememberSaveable { mutableStateOf("") } HelloContent(name = name, onNameChange = { name = it }) } @Composable fun HelloContent(name: String, onNameChange: (String) -> Unit) { Column(modifier = Modifier.padding(16.dp)) { OutlinedTextField( value = name, onValueChange = onNameChange, label = { Text("Name") } ) } }
這樣就實現了Composable
函數HelloContent 與狀態(tài)的存儲方式解耦,便于我們復用。
狀態(tài)下降、事件上升的這種模式稱為“單向數據流”。在這種情況下,狀態(tài)會從 HelloScreen 下降為 HelloContent,事件會從 HelloContent 上升為 HelloScreen。通過遵循單向數據流,您可以將在界面中顯示狀態(tài)的可組合項與應用中存儲和更改狀態(tài)的部分解耦。
4.狀態(tài)管理
根據可組合項的復雜性,需要考慮不同的備選方案:
將Composable作為可信來源
用于管理簡單的界面元素狀態(tài)。比如上一篇提到的LazyColumn
滾動到指定item,將交互都放在當前的Composable
中進行。
val listState = rememberLazyListState() val coroutineScope = rememberCoroutineScope() LazyColumn( state = listState, ) { /* ... */ } Button( onClick = { coroutineScope.launch { listState.animateScrollToItem(index = 0) } } ) { ... }
其實查看rememberLazyListState
的源碼,可以看到實現很簡單:
@Composable fun rememberLazyListState( initialFirstVisibleItemIndex: Int = 0, initialFirstVisibleItemScrollOffset: Int = 0 ): LazyListState { return rememberSaveable(saver = LazyListState.Saver) { LazyListState( initialFirstVisibleItemIndex, initialFirstVisibleItemScrollOffset ) } }
將狀態(tài)容器作為可信來源
當可組合項包含涉及多個界面元素狀態(tài)的復雜界面邏輯時,應將相應事務委派給狀態(tài)容器。這樣做更易于單獨對該邏輯進行測試,還降低了可組合項的復雜性。該方法支持分離關注點原則:可組合項負責發(fā)出界面元素,而狀態(tài)容器包含界面邏輯和界面元素的狀態(tài)。
@Composable fun MyApp() { MyTheme { val myAppState = rememberMyAppState() Scaffold( scaffoldState = myAppState.scaffoldState, bottomBar = { if (myAppState.shouldShowBottomBar) { BottomBar( tabs = myAppState.bottomBarTabs, navigateToRoute = { myAppState.navigateToBottomBarRoute(it) } ) } } ) { NavHost(navController = myAppState.navController, "initial") { /* ... */ } } } }
rememberMyAppState
代碼:
class MyAppState( val scaffoldState: ScaffoldState, val navController: NavHostController, private val resources: Resources, /* ... */ ) { val bottomBarTabs = /* State */ val shouldShowBottomBar: Boolean get() = /* ... */ fun navigateToBottomBarRoute(route: String) { /* ... */ } fun showSnackbar(message: String) { /* ... */ } } @Composable fun rememberMyAppState( scaffoldState: ScaffoldState = rememberScaffoldState(), navController: NavHostController = rememberNavController(), resources: Resources = LocalContext.current.resources, /* ... */ ) = remember(scaffoldState, navController, resources, /* ... */) { MyAppState(scaffoldState, navController, resources, /* ... */) }
其實就是再封裝一層,用戶處理邏輯。封裝的部分就叫狀態(tài)容器,用于管理Composable的邏輯和狀態(tài)。
將 ViewModel 作為可信來源
一種特殊的狀態(tài)容器類型,用于提供對業(yè)務邏輯以及屏幕或界面狀態(tài)的訪問權限。
ViewModel 的生命周期比Composable長,因此不應保留對綁定到組合生命周期的狀態(tài)的長期引用。否則,可能會導致內存泄漏。建議屏幕級Composable使用 ViewModel 來提供對業(yè)務邏輯的訪問權限并作為其界面狀態(tài)的可信來源。如需了解 ViewModel 為何適用于這種情況,請參閱 ViewModel 和狀態(tài)容器部分。
本篇到此結束,幫忙點個贊~ 給我一點鼓勵,你也可以收藏本篇以備不時之需。
參考
到此這篇關于Jetpack Compose狀態(tài)專篇精講的文章就介紹到這了,更多相關Jetpack Compose狀態(tài)內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Android TextWatcher內容監(jiān)聽死循環(huán)案例詳解
這篇文章主要介紹了Android TextWatcher內容監(jiān)聽死循環(huán)案例詳解,本篇文章通過簡要的案例,講解了該項技術的了解與使用,以下就是詳細內容,需要的朋友可以參考下2021-08-08Android無限循環(huán)RecyclerView的完美實現方案
這篇文章主要介紹了Android無限循環(huán)RecyclerView的完美實現方案,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2019-06-06