Compose中的remember和mutableStateOf

在Compose的官方指導和示例代碼中常常會看到這樣的代碼android

var count by remember{mutableStateOf(0)}
 或者
 var count = remember{mutableStateOf(0)}
複製代碼

首先須要注意幾個問題緩存

  1. 用by的話,count是Int類型【即mutableStateOf參數的類型】
  2. 用「=」, count是MutableState類型,使用時須要經過.value來取值
  3. 使用by的時候可能會報紅,錯誤是

Type 'TypeVariable(T)' has no method 'getValue(Nothing?, KProperty<*>)' and thus it cannot serve as a delegatemarkdown

解決方法很簡單,導包便可ide

import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
也能夠直接:
import androidx.compose.runtime.*
複製代碼

這個問題多是Compose的一個bug,若是隻是 by mutableStateOf(0),也會報錯,可是按alt+enter 會提示須要導包,可是加了remember以後,按alt+enter是不提示導包的,很是奇怪。 4. 注意remember 後面是 {} , 不是()函數

進入正題,來看看mutableStateOf 和 remember 都是幹嗎的spa

普通參數
class MainActivity : ComponentActivity() {
    @ExperimentalMaterialApi
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            println("---- clicked onCreated setContent ")
            Surface() {
                var count = 0 // 無狀態
                Button(
                    onClick = { count++ }, modifier = Modifier
                        .padding(16.dp)
                        .fillMaxWidth()
                        .height(50.dp)
                ) {
                    Text(
                        text = "I have been clicked $count times",
                        modifier = Modifier.align(Alignment.CenterVertically)
                    )
                    SideEffect(effect = { println("---- text count = $count ") })
                }
                SideEffect(effect = { println("---- out count = $count ") })
            }
        }
    }
}
複製代碼

點擊按鈕,數字不變,控制檯只有一次打印日誌

2021-05-26 10:21:45.248 32194-32194/com.shakespace.compose I/System.out: ---- clicked onCreated setContent 
2021-05-26 10:21:45.301 32194-32194/com.shakespace.compose I/System.out: ---- text count = 0 
2021-05-26 10:21:45.301 32194-32194/com.shakespace.compose I/System.out: ---- out count = 0 
複製代碼
有狀態參數
var count by mutableStateOf(0) // 改爲mutableStateOf
複製代碼

點擊按鈕,數字會不斷增長,控制檯輸出code

2021-05-26 10:26:36.949 16988-16988/com.shakespace.compose I/System.out: ---- clicked onCreated setContent 
2021-05-26 10:26:37.002 16988-16988/com.shakespace.compose I/System.out: ---- text count = 0 
2021-05-26 10:26:37.002 16988-16988/com.shakespace.compose I/System.out: ---- out count = 0 
2021-05-26 10:26:40.511 16988-16988/com.shakespace.compose I/System.out: ---- text count = 1 
2021-05-26 10:26:41.141 16988-16988/com.shakespace.compose I/System.out: ---- text count = 2 
2021-05-26 10:26:41.792 16988-16988/com.shakespace.compose I/System.out: ---- text count = 3 
2021-05-26 10:26:42.828 16988-16988/com.shakespace.compose I/System.out: ---- text count = 4 
2021-05-26 10:26:43.596 16988-16988/com.shakespace.compose I/System.out: ---- text count = 5 
複製代碼
  1. 相比普通變量,使用mutableStateOf,使得變量有了被觀察的能力,當值發生變化時就會通知使用這個變量的控件進行更新。
  2. 問題是這裏沒有用到remember,點擊時數字仍是會增長,是否是不須要remember呢?
重繪機制

把Button 的padding 改爲和count 有關orm

setContent {
            println("---- clicked onCreated setContent ")
            Surface() {
                var count by mutableStateOf(0)
                Button(
                    onClick = { count++ }, modifier = Modifier
                        .padding(count.dp) // 把Button的padding 改爲和count 有關
                        .fillMaxWidth()
                        .height(50.dp)
                ) {
                    Text(
                        text = "I have been clicked $count times",
                        modifier = Modifier.align(Alignment.CenterVertically)
                    )
                    SideEffect(effect = { println("---- text count = $count ") })
                }
                SideEffect(effect = { println("---- out count = $count ") })
            }
        }
複製代碼

點擊按鈕,數字不會變化,控制檯輸出內存

2021-05-26 11:49:28.394 19077-19077/com.shakespace.compose I/System.out: ---- clicked onCreated setContent 
2021-05-26 11:49:28.450 19077-19077/com.shakespace.compose I/System.out: ---- text count = 0 
2021-05-26 11:49:28.450 19077-19077/com.shakespace.compose I/System.out: ---- out count = 0 
2021-05-26 11:49:33.240 19077-19077/com.shakespace.compose I/System.out: ---- text count = 0 
2021-05-26 11:49:33.240 19077-19077/com.shakespace.compose I/System.out: ---- out count = 0 
2021-05-26 11:49:34.123 19077-19077/com.shakespace.compose I/System.out: ---- text count = 0 
2021-05-26 11:49:34.123 19077-19077/com.shakespace.compose I/System.out: ---- out count = 0 
2021-05-26 11:49:35.776 19077-19077/com.shakespace.compose I/System.out: ---- text count = 0 
2021-05-26 11:49:35.776 19077-19077/com.shakespace.compose I/System.out: ---- out count = 0 
複製代碼

這時候數字不會變化,是由於Surface的直接子組件Button依賴於count這個state,那麼count變化的時候,Surface接收的這個Composable函數就會重繪[方法從新調用],每次調用count就會是0,因此數字沒有變化。

而前面Button不依賴於count的例子,外部不須要重繪(看日誌 out 只打印了一次),count也不會重置,因此數字會增長。

remember

在這種狀況下,若是還想記住變量值,就要用到remember

setContent {
            println("---- clicked onCreated setContent ")
            Surface() {
                var count by remember{ mutableStateOf(0)} // 使用remember
                Button(
                    onClick = { count++ }, modifier = Modifier
                        .padding(count.dp)
                        .fillMaxWidth()
                        .height(50.dp)
                ) {
                    Text(
                        text = "I have been clicked $count times",
                        modifier = Modifier.align(Alignment.CenterVertically)
                    )
                    SideEffect(effect = { println("---- text count = $count ") })
                }
                SideEffect(effect = { println("---- out count = $count ") })
            }
        }
複製代碼

點擊按鈕,數字會增長,控制檯輸出:

2021-05-26 11:56:28.675 5449-5449/com.shakespace.compose I/System.out: ---- clicked onCreated setContent 
2021-05-26 11:56:28.728 5449-5449/com.shakespace.compose I/System.out: ---- text count = 0 
2021-05-26 11:56:28.729 5449-5449/com.shakespace.compose I/System.out: ---- out count = 0 
2021-05-26 11:56:36.311 5449-5449/com.shakespace.compose I/System.out: ---- text count = 1 
2021-05-26 11:56:36.311 5449-5449/com.shakespace.compose I/System.out: ---- out count = 1 
2021-05-26 11:56:36.840 5449-5449/com.shakespace.compose I/System.out: ---- text count = 2 
2021-05-26 11:56:36.840 5449-5449/com.shakespace.compose I/System.out: ---- out count = 2 
2021-05-26 11:56:37.206 5449-5449/com.shakespace.compose I/System.out: ---- text count = 3 
2021-05-26 11:56:37.206 5449-5449/com.shakespace.compose I/System.out: ---- out count = 3 
複製代碼

外部仍是會重繪,可是每次值都會內存緩存中讀取,這就是remember的做用,噹噹前Composable重繪的時候,能夠暫存變量值。

若是把變量放在外面?
setContent {
            println("---- clicked onCreated setContent ")
            var count by mutableStateOf(0) // 移到外面,不適用remember
            Surface() {
                Button(
                    onClick = { count++ }, modifier = Modifier
                        .padding(count.dp)
                        .fillMaxWidth()
                        .height(50.dp)
                ) {
                    Text(
                        text = "I have been clicked $count times",
                        modifier = Modifier.align(Alignment.CenterVertically)
                    )
                    SideEffect(effect = { println("---- text count = $count ") })
                }
                SideEffect(effect = { println("---- out count = $count ") })
            }
        }
複製代碼

數字會增長,控制檯輸出

2021-05-26 12:03:32.605 9375-9375/com.shakespace.compose I/System.out: ---- clicked onCreated setContent 
2021-05-26 12:03:32.658 9375-9375/com.shakespace.compose I/System.out: ---- text count = 0 
2021-05-26 12:03:32.658 9375-9375/com.shakespace.compose I/System.out: ---- out count = 0 
2021-05-26 12:03:35.100 9375-9375/com.shakespace.compose I/System.out: ---- text count = 1 
2021-05-26 12:03:35.100 9375-9375/com.shakespace.compose I/System.out: ---- out count = 1 
2021-05-26 12:03:38.646 9375-9375/com.shakespace.compose I/System.out: ---- text count = 2 
2021-05-26 12:03:38.646 9375-9375/com.shakespace.compose I/System.out: ---- out count = 2 
2021-05-26 12:03:39.642 9375-9375/com.shakespace.compose I/System.out: ---- text count = 3 
2021-05-26 12:03:39.642 9375-9375/com.shakespace.compose I/System.out: ---- out count = 3 
複製代碼

這和重繪那個例子是相似的,setContent的參數也是個Composable,Surface做爲直接子類,並無依賴於count,因此自己不會重繪,count也不會被重置。

可是

若是咱們把上面的Surface換成Column之類的Composable,其餘都不變,結果就不同了

2021-05-26 12:07:16.163 9780-9780/com.shakespace.compose I/System.out: ---- clicked onCreated setContent 
2021-05-26 12:07:16.166 9780-9780/com.shakespace.compose I/System.out: ---- text count = 0 
2021-05-26 12:07:16.166 9780-9780/com.shakespace.compose I/System.out: ---- out count = 0 
2021-05-26 12:07:17.047 9780-9780/com.shakespace.compose I/System.out: ---- clicked onCreated setContent 
2021-05-26 12:07:17.050 9780-9780/com.shakespace.compose I/System.out: ---- text count = 0 
2021-05-26 12:07:17.050 9780-9780/com.shakespace.compose I/System.out: ---- out count = 0 
2021-05-26 12:07:17.883 9780-9780/com.shakespace.compose I/System.out: ---- clicked onCreated setContent 
2021-05-26 12:07:17.885 9780-9780/com.shakespace.compose I/System.out: ---- text count = 0 
2021-05-26 12:07:17.885 9780-9780/com.shakespace.compose I/System.out: ---- out count = 0 
複製代碼

數字不變,並且 「clicked onCreated setContent」 每次都打印,說明setContent裏面的Composable每次都會重繪

目前所能知道的是,Column、Row、Box之類的組件,傳入的Composable參數是給對應的Scope的擴展函數,而Surface接收的就是一個普通的Composable函數,一個可能不夠準確的結論是:擴展函數中的直接組件重繪的話,至關於接收這個擴展函數的組件也要重繪。【即Button須要重繪,Column也會重繪】

相關文章
相關標籤/搜索