Android Compose 狀態(tài)的概念及實(shí)際應(yīng)用
Jetpack Compose 中的狀態(tài)
一、什么是“狀態(tài)”?
應(yīng)用里可以隨時(shí)間變化的任何值都叫“狀態(tài)”——這個(gè)定義很寬泛,小到一個(gè)按鈕的點(diǎn)擊狀態(tài)、輸入框的文本,大到數(shù)據(jù)庫(kù)里的博文和評(píng)論、網(wǎng)絡(luò)連接狀態(tài),都屬于“狀態(tài)”。
Android 應(yīng)用本質(zhì)上就是在“展示狀態(tài)”:比如沒(méi)網(wǎng)時(shí)顯示的提示、點(diǎn)擊按鈕時(shí)的漣漪效果、圖片上用戶添加的貼紙,本質(zhì)都是在呈現(xiàn)不同時(shí)刻的“狀態(tài)”。
二、狀態(tài)和“組合/重組”的關(guān)系
Compose 是“聲明式”工具集,和傳統(tǒng) XML 布局的“命令式”完全不同——它更新界面的唯一方式,是用新參數(shù)重新調(diào)用可組合項(xiàng)(也就是那些帶 @Composable 注解的函數(shù))。這些參數(shù),其實(shí)就是界面狀態(tài)的“表現(xiàn)形式”。
這里要先搞懂三個(gè)關(guān)鍵術(shù)語(yǔ):
- 組合:Compose 執(zhí)行可組合項(xiàng)后,生成的“界面描述”(相當(dāng)于把代碼翻譯成了手機(jī)能顯示的界面結(jié)構(gòu));
- 初始組合:第一次運(yùn)行可組合項(xiàng),創(chuàng)建出初始界面的過(guò)程;
- 重組:當(dāng)狀態(tài)變化時(shí),重新運(yùn)行可組合項(xiàng)、更新“組合”(也就是更新界面)的過(guò)程。
舉個(gè)直觀例子, HelloContent 函數(shù):
@Composable private fun HelloContent() {
Column(modifier = Modifier.padding(16.dp)) {
Text(text = "Hello!", ...)
OutlinedTextField(
value = "", // 固定空字符串,沒(méi)有關(guān)聯(lián)狀態(tài)
onValueChange = { }, // 沒(méi)處理輸入變化
label = { Text("Name") }
)
}
}
運(yùn)行后輸入文本沒(méi)反應(yīng)——因?yàn)?TextField 不會(huì)“自動(dòng)更新”:它的 value 參數(shù)是固定空字符串,狀態(tài)沒(méi)變化,就不會(huì)觸發(fā)“重組”,界面自然不變。
這就是 Compose 的核心規(guī)則:只有狀態(tài)變了,且可組合項(xiàng)用到了這個(gè)狀態(tài),才會(huì)觸發(fā)重組更新界面。
三、可組合項(xiàng)里怎么存狀態(tài)?(2個(gè)核心API)
要讓界面能跟著狀態(tài)變,得解決兩個(gè)問(wèn)題:① 把狀態(tài)存起來(lái);② 讓狀態(tài)變化能觸發(fā)重組。Compose 提供了兩個(gè)關(guān)鍵 API 配合解決:
1. remember:負(fù)責(zé)“存儲(chǔ)狀態(tài)”
remember 是個(gè)“記憶工具”,作用是把對(duì)象存儲(chǔ)在“組合”里:
- 初始組合時(shí),計(jì)算一個(gè)值并存起來(lái);
- 重組時(shí),直接返回之前存儲(chǔ)的值(不會(huì)重新計(jì)算);
- 注意:如果調(diào)用
remember的可組合項(xiàng)被從界面中移除(比如切換頁(yè)面時(shí)),remember會(huì)“忘記”存儲(chǔ)的值(相當(dāng)于臨時(shí)緩存,隨界面生命周期變化)。
它既能存不可變對(duì)象(比如固定的字符串),也能存可變對(duì)象——但要配合下面的 API 才能觸發(fā)重組。
2. mutableStateOf:負(fù)責(zé)“讓狀態(tài)可觀察”
mutableStateOf 會(huì)創(chuàng)建一個(gè) MutableState<T> 類型的對(duì)象,這是 Compose 內(nèi)置的“可觀察類型”,核心特點(diǎn)是:
- 它是個(gè)接口,核心是
var value: T(可以修改值); - 只要修改
value的值,所有“讀取過(guò)這個(gè) value”的可組合項(xiàng),都會(huì)被自動(dòng)安排“重組”(也就是更新界面)。
簡(jiǎn)單說(shuō):remember 負(fù)責(zé)“存住狀態(tài)”,mutableStateOf 負(fù)責(zé)“讓狀態(tài)變化能被 Compose 感知”,兩者搭配才能實(shí)現(xiàn)“狀態(tài)變 → 界面變”。
四、聲明 MutableState 的3種方式(語(yǔ)法糖,按需選)
mutableStateOf 通常和 remember 一起用,有3種等效寫法,只是語(yǔ)法不同,目的是讓代碼更易讀:
| 特性 | val nameState = remember { mutableStateOf(“”) } | var name by remember { mutableStateOf(“”) } | val (name, setName) = remember { mutableStateOf(“”) } |
|---|---|---|---|
| 變量類型 | val (不可變引用) | var (可變屬性) | val (兩個(gè)不可變引用) |
| 訪問(wèn)值 | nameState.value | name | name |
| 修改值 | nameState.value = "New" | name = "New" | setName("New") |
| 本質(zhì) | 直接持有 State 對(duì)象 | 使用屬性委托,編譯器自動(dòng)處理 .value | 使用解構(gòu)聲明 |
| 代碼風(fēng)格 | 顯式,能清楚看到狀態(tài)對(duì)象 | 簡(jiǎn)潔,類似普通的可變變量 | 類似于 React Hooks 的風(fēng)格 |
| 需要導(dǎo)入 | 無(wú)特殊導(dǎo)入 | import androidx.compose.runtime.getValueimport androidx.compose.runtime.setValue | 無(wú)特殊導(dǎo)入 |
| 適用場(chǎng)景 | 需要將 State 對(duì)象本身傳遞給其他函數(shù)時(shí) | 最常用、最推薦的寫法,代碼簡(jiǎn)潔直觀 | 習(xí)慣函數(shù)式風(fēng)格,希望將值和設(shè)置器分開(kāi) |
在實(shí)際開(kāi)發(fā)中,第二種方式 (by 委托) 因其極高的可讀性和簡(jiǎn)潔性而成為社區(qū)和官方最推崇的標(biāo)準(zhǔn)寫法(傳遞的是值)。第一種方式在需要傳遞狀態(tài)引用時(shí)很有用。第三種方式則提供了一種不同的代碼風(fēng)格,適合那些喜歡顯式分離“值”和“更新函數(shù)”的開(kāi)發(fā)者。
五、傳遞對(duì)象和傳遞值的區(qū)別
傳遞狀態(tài)對(duì)象和傳遞值在 Compose 中有本質(zhì)的區(qū)別,這關(guān)系到重組機(jī)制和數(shù)據(jù)流方向。
1. 核心區(qū)別
| 特性 | 傳遞值 | 傳遞狀態(tài)對(duì)象 |
|---|---|---|
| 傳遞的內(nèi)容 | 當(dāng)前的數(shù)據(jù)值 | 包含數(shù)據(jù)和更新能力的對(duì)象 |
| 接收方能否修改 | ? 不能 | ? 能 |
| 重組范圍 | 傳遞方重組 → 接收方重組 | 接收方可獨(dú)立重組 |
| 數(shù)據(jù)流 | 單向數(shù)據(jù)流 | 雙向數(shù)據(jù)流 |
2. 具體例子說(shuō)明
例子1:傳遞值(單向數(shù)據(jù)流)
@Composable
fun ParentComponent() {
var name by remember { mutableStateOf("") }
Column {
// 傳遞值給子組件
ChildComponentReadOnly(value = name)
// 父組件自己處理修改
TextField(
value = name,
onValueChange = { name = it }
)
}
}
@Composable
fun ChildComponentReadOnly(value: String) {
// 子組件只能讀取值,不能修改
Text("Hello, $value!")
// 如果嘗試修改會(huì)編譯錯(cuò)誤:
// value = "New" // ? 編譯錯(cuò)誤!
}特點(diǎn):
- 數(shù)據(jù)流是單向的:父組件 → 子組件
- 子組件是純展示的,沒(méi)有副作用
- 狀態(tài)管理完全由父組件控制
例子2:傳遞狀態(tài)對(duì)象(雙向數(shù)據(jù)流)
@Composable
fun ParentComponent() {
val nameState = remember { mutableStateOf("") }
Column {
// 傳遞狀態(tài)對(duì)象給子組件
ChildComponentWithState(state = nameState)
// 父組件也能看到子組件的修改
Text("Parent sees: ${nameState.value}")
}
}
@Composable
fun ChildComponentWithState(state: MutableState<String>) {
TextField(
value = state.value,
onValueChange = { state.value = it }
)
// 子組件可以直接修改狀態(tài)
Button(onClick = { state.value = "Reset" }) {
Text("Reset")
}
}特點(diǎn):
- 數(shù)據(jù)流是雙向的:父組件 ↔ 子組件
- 子組件可以直接修改狀態(tài)
- 雙方都能實(shí)時(shí)看到對(duì)方的修改
3. 重組行為的區(qū)別
傳遞值的情況:
@Composable
fun Parent() {
var count by remember { mutableStateOf(0) }
// 當(dāng)count變化時(shí),Parent會(huì)重組
// Child也會(huì)重組,因?yàn)樗邮樟诵碌闹?
Child(value = count)
Button(onClick = { count++ }) {
Text("Increment")
}
}
@Composable
fun Child(value: Int) {
Text("Count: $value") // 接收新值,會(huì)重組
}傳遞狀態(tài)對(duì)象的情況:
@Composable
fun Parent() {
val countState = remember { mutableStateOf(0) }
// Parent不會(huì)重組,因?yàn)閏ountState引用沒(méi)變
// Child自己處理重組
Child(state = countState)
Button(onClick = { countState.value++ }) {
Text("Increment")
}
}
@Composable
fun Child(state: MutableState<Int>) {
Text("Count: ${state.value}") // 自己讀取狀態(tài),自己重組
}4. 小結(jié)
| 方面 | 傳遞值 | 傳遞狀態(tài)對(duì)象 |
|---|---|---|
| 控制權(quán) | 集中控制 | 分散控制 |
| 數(shù)據(jù)流 | 單向 | 雙向 |
| 組件職責(zé) | 展示職責(zé) | 業(yè)務(wù)邏輯職責(zé) |
| 測(cè)試難度 | 容易測(cè)試 | 較難測(cè)試 |
| 推薦程度 | ? 優(yōu)先使用 | ?? 特定場(chǎng)景使用 |
最佳實(shí)踐:優(yōu)先使用傳遞值的方式,遵循單向數(shù)據(jù)流原則。只有在子組件確實(shí)需要直接修改狀態(tài),且這種修改是合理的業(yè)務(wù)需求時(shí),才考慮傳遞狀態(tài)對(duì)象。
六、實(shí)際用法:讓狀態(tài)控制界面
狀態(tài)的核心作用,是“決定界面該顯示什么”。比如優(yōu)化版的 HelloContent:
@Composable fun HelloContent() {
Column(modifier = Modifier.padding(16.dp)) {
// 用 by 委托語(yǔ)法,聲明并存儲(chǔ)“姓名狀態(tài)”,初始值為空
var name by remember { mutableStateOf("") }
// 狀態(tài)控制界面:只有姓名不為空時(shí),才顯示問(wèn)候語(yǔ)
if (name.isNotEmpty()) {
Text(text = "Hello, $name!") // 用到了 name 狀態(tài)
}
OutlinedTextField(
value = name, // 綁定狀態(tài):輸入框的文本 = 姓名狀態(tài)
onValueChange = { name = it }, // 輸入變化時(shí),更新?tīng)顟B(tài)
label = { Text("Name") }
)
}
}這里的邏輯鏈很清晰:
- 輸入框輸入文本 →
onValueChange回調(diào)更新name狀態(tài); name狀態(tài)變化 → 觸發(fā)用到name的可組合項(xiàng)(if里的Text和TextField本身)重組;- 重組時(shí),
if (name.isNotEmpty())條件生效,顯示問(wèn)候語(yǔ);輸入框也同步顯示新的name值。
這就是 Compose 處理狀態(tài)的核心流程:狀態(tài)存儲(chǔ)(remember)→ 狀態(tài)觀察(mutableStateOf)→ 狀態(tài)變化觸發(fā)重組 → 界面更新。
到此這篇關(guān)于Android Compose 狀態(tài)的概念的文章就介紹到這了,更多相關(guān)Android Compose 狀態(tài)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android實(shí)現(xiàn)Tab布局的4種方式(Fragment+TabPageIndicator+ViewPager)
Android現(xiàn)在實(shí)現(xiàn)Tab類型的界面方式越來(lái)越多,本文詳細(xì)介紹了Android實(shí)現(xiàn)Tab布局的4種方式,具有一定的參考價(jià)值,有興趣的可以了解一下。2016-11-11
GridView實(shí)現(xiàn)桌面圖標(biāo)顯示案例
這篇文章主要為大家詳細(xì)介紹了GridView實(shí)現(xiàn)桌面圖標(biāo)顯示案例,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-08-08
Android編寫簡(jiǎn)單的網(wǎng)絡(luò)爬蟲(chóng)
網(wǎng)絡(luò)爬蟲(chóng)是捜索引擎抓取系統(tǒng)的重要組成部分。爬蟲(chóng)的主要目的是將互聯(lián)網(wǎng)上的網(wǎng)頁(yè)下載到本地形成一個(gè)或聯(lián)網(wǎng)內(nèi)容的鏡像備份。本文的主要內(nèi)容是講在Android中如何編寫簡(jiǎn)單的網(wǎng)絡(luò)爬蟲(chóng)。2016-07-07
Android如何實(shí)現(xiàn)時(shí)間線效果
這篇文章主要介紹了?Android如何實(shí)現(xiàn)時(shí)間線效果,下面文章圍繞?Android如何實(shí)現(xiàn)時(shí)間線效果的相關(guān)資料展開(kāi)詳細(xì)內(nèi)容,具有一定的參考價(jià)值?,需要的朋友可以參考一下2021-11-11
Android編程簡(jiǎn)單實(shí)現(xiàn)九宮格示例
這篇文章主要介紹了Android編程簡(jiǎn)單實(shí)現(xiàn)九宮格,結(jié)合具體實(shí)例形式分析了Android實(shí)現(xiàn)九宮格的具體步驟與相關(guān)布局、功能實(shí)現(xiàn)技巧,需要的朋友可以參考下2017-06-06
Ubuntu Android源碼以及內(nèi)核下載與編譯
本文主要介紹Android源碼的下載和編譯,這里整理了相關(guān)資料及如何下載和編譯的詳細(xì)步驟,有需要的小伙伴可以參考下2016-09-09

