Jetpack?Compose狀態(tài)專篇精講
應用中的狀態(tài)是指可以隨時間變化的任何值。這是一個非常寬泛的定義,從 Room 數(shù)據(jù)庫到類的變量,全部涵蓋在內(nèi)。
由于Compose是聲明式UI,會根據(jù)狀態(tài)變化來更新UI,因此狀態(tài)的處理至關重要。這里的狀態(tài)你可以簡單理解為頁面上展示的數(shù)據(jù),那么狀態(tài)管理就是處理數(shù)據(jù)的讀寫。
1.remember
remember就是用來保存狀態(tài)的,下面舉一個小例子。
@Composable
fun HelloContent() {
Column(modifier = Modifier.padding(16.dp)) {
OutlinedTextField(
value = "",
onValueChange = { },
label = { Text("Name") }
)
}
}
比如我們在頁面中加了一個輸入框,如果只是上面代碼中這樣處理,那你會發(fā)現(xiàn)我們輸入的文字不會被記錄起來,輸入框中始終都是空的。這是因為屬性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函數(shù),這樣就會自動更新UI。
Jetpack Compose 并不強制要求你使用 MutableState 存儲狀態(tài)。Jetpack Compose 支持其他可觀察類型。在 Jetpack Compose 中讀取其他可觀察類型之前,您必須將其轉(zhuǎn)換為 State,以便 Jetpack Compose 可以在狀態(tài)發(fā)生變化時自動重組界面。
LiveData中可以使用擴展函數(shù)observeAsState()轉(zhuǎn)換為 State。Flow中可以使用擴展函數(shù)collectAsState()轉(zhuǎn)換為 State。RxJava中可以使用擴展函數(shù)subscribeAsState()轉(zhuǎn)換為 State。
2.rememberSaveable
雖然 remember 可幫助您在重組后保持狀態(tài),但不會幫助您在配置更改后保持狀態(tài)。為此,您必須使用 rememberSaveable。rememberSaveable 會自動保存可保存在 Bundle 中的任何值。
還是上面的例子,如果我們旋轉(zhuǎn)屏幕,就會發(fā)現(xiàn)輸入框中的文字會丟失。此時就可以使用rememberSaveable 替換remember 來幫助我們恢復界面狀態(tài)。
由于保存的數(shù)據(jù)都是在 Bundle 中的,因此可保存的數(shù)據(jù)類型是有限制的。比如基礎類型、String、Parcelable,Serializable等。一般來說需要保存的對象加個 @Parcelize 注解就可以解決問題。
如果某種原因?qū)е聼o法使用 @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函數(shù),我們稱為有狀態(tài)。有狀態(tài)的好處是調(diào)用方不需要控制狀態(tài),并且不必自行管理狀態(tài)。但是,具有內(nèi)部狀態(tài)的Composable往往不易重復使用,也更難測試。
在開發(fā)可重復使用的Composable時,您通常想要同時提供同一Composable的有狀態(tài)和無狀態(tài)版本。有狀態(tài)版本對于不關心狀態(tài)的調(diào)用方來說很方便,而無狀態(tài)版本對于需要控制或提升狀態(tài)的調(diào)用方來說是必要的。
Compose 中的狀態(tài)提升是一種將狀態(tài)移至調(diào)用方以使可組合項無狀態(tài)的模式。
舉例說明一下狀態(tài)提升,比如我們實現(xiàn)一個Dialog,為了方便使用我們可以將里面顯示的文字,點擊事件邏輯寫到dialog的內(nèi)部封裝起來,雖然使用簡單但不具有通用性。那么為了通用,我們可以將文字,點擊事件的回調(diào)當參數(shù)傳入,這樣就靈活了起來。
狀態(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") }
)
}
}這樣就實現(xiàn)了Composable函數(shù)HelloContent 與狀態(tài)的存儲方式解耦,便于我們復用。
狀態(tài)下降、事件上升的這種模式稱為“單向數(shù)據(jù)流”。在這種情況下,狀態(tài)會從 HelloScreen 下降為 HelloContent,事件會從 HelloContent 上升為 HelloScreen。通過遵循單向數(shù)據(jù)流,您可以將在界面中顯示狀態(tài)的可組合項與應用中存儲和更改狀態(tài)的部分解耦。
4.狀態(tài)管理
根據(jù)可組合項的復雜性,需要考慮不同的備選方案:
將Composable作為可信來源
用于管理簡單的界面元素狀態(tài)。比如上一篇提到的LazyColumn滾動到指定item,將交互都放在當前的Composable中進行。
val listState = rememberLazyListState()
val coroutineScope = rememberCoroutineScope()
LazyColumn(
state = listState,
) {
/* ... */
}
Button(
onClick = {
coroutineScope.launch {
listState.animateScrollToItem(index = 0)
}
}
) {
...
}其實查看rememberLazyListState的源碼,可以看到實現(xiàn)很簡單:
@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)的長期引用。否則,可能會導致內(nèi)存泄漏。建議屏幕級Composable使用 ViewModel 來提供對業(yè)務邏輯的訪問權限并作為其界面狀態(tài)的可信來源。如需了解 ViewModel 為何適用于這種情況,請參閱 ViewModel 和狀態(tài)容器部分。
本篇到此結(jié)束,幫忙點個贊~ 給我一點鼓勵,你也可以收藏本篇以備不時之需。
參考
到此這篇關于Jetpack Compose狀態(tài)專篇精講的文章就介紹到這了,更多相關Jetpack Compose狀態(tài)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Android仿網(wǎng)易新聞圖片詳情下滑隱藏效果示例代碼
這篇文章主要給大家介紹了關于利用Android如何仿網(wǎng)易新聞圖片詳情下滑隱藏效果的相關資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2018-07-07
Android TextWatcher內(nèi)容監(jiān)聽死循環(huán)案例詳解
這篇文章主要介紹了Android TextWatcher內(nèi)容監(jiān)聽死循環(huán)案例詳解,本篇文章通過簡要的案例,講解了該項技術的了解與使用,以下就是詳細內(nèi)容,需要的朋友可以參考下2021-08-08
Android自定義View實現(xiàn)可拖拽縮放的矩形框
這篇文章主要為大家詳細介紹了Android自定義View實現(xiàn)可拖拽縮放的矩形框,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2020-05-05
Android無限循環(huán)RecyclerView的完美實現(xiàn)方案
這篇文章主要介紹了Android無限循環(huán)RecyclerView的完美實現(xiàn)方案,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2019-06-06

