天天看點

Compose的state

開始:

目标是在一個text下面設定一個輸入框,随着輸入框變化,text也跟着變化。于是我寫一個Compose function:

@Composable
fun TextAndTextField() {

    Column(modifier = Modifier.padding(16.dp)) {

        Text(
            text = "Hello",
            modifier = Modifier.padding(bottom = 6.dp),
            style = MaterialTheme.typography.h5,
            maxLines = 1,
            overflow = TextOverflow.Ellipsis
        )
        OutlinedTextField(value = "", onValueChange = {
        }, label = {
            Text(text = "name")
        })
    }
}      
Compose的state

 在框内輸入任何東西都不會有反應。

接着:

因為我們text的内容是hard code的,我們可以試着使用一個變量來代替:

@Composable
fun TextAndTextField() {

var name = ""
Column(modifier = Modifier.padding(16.dp)) {

    Text(
        text = "Hello,$name",
        modifier = Modifier.padding(bottom = 6.dp),
        style = MaterialTheme.typography.h5,
        maxLines = 1,
        overflow = TextOverflow.Ellipsis
    )

   OutlinedTextField(value = name, onValueChange = {
       name = it
   }, label = { Text(text = "name")})
}      
}      

但效果卻和上例一樣。因為可組合函數改變UI是靠重新發出(recompose)來改變的,也就是說,當state狀态改變,就重新發出可組合函數的重新組合,在重新繪制UI來改變UI。但是我們的name是一個普通的變量,是不能被compose“記住的”,name改變但系統不知狀态已改變, 我們需要用特殊的變量來表示一個“狀态”,當這個變量改變,使得系統知道這個compose的狀态改變,就重新繪制,改變UI。

remember上場:

@Composable
fun TextAndTextField() {

    val name = remember { mutableStateOf("")}
    Column(modifier = Modifier.padding(16.dp)) {

        Text(
            text = "Hello,${name.value}",
            modifier = Modifier.padding(bottom = 6.dp),
            style = MaterialTheme.typography.h5,
            maxLines = 1,
            overflow = TextOverflow.Ellipsis
        )
        
       OutlinedTextField(value = name.value, onValueChange = {
           name.value = it
       }, label = { Text(text = "name")})
    }

}      

remember可以記住“{}”裡面的表達式傳回的值,當表達式裡面的值改變時,就代表狀态改變了,就通知Compose重新繪制。

note:若是旋轉螢幕,狀态會丢失,可以使用rememberSavable來代替remember。

state hoisting

一個具有高可重用性的compose function應該是stateless的,即不應該在内部包含狀态。我們應該把狀态提到外部去,來提升可重用性。

@Composable
fun HelloScreen() {
    val name = remember {
     mutableStateOf("")
    }
    TextAndTextField(name = name.value, onValueChange = {
        name.value = it
    })
}

@Composable
fun TextAndTextField(name: String, onValueChange: (String) -> Unit) {

    Column(modifier = Modifier.padding(16.dp)) {

        Text(
            text = "Hello,$name",
            modifier = Modifier.padding(bottom = 6.dp),
            style = MaterialTheme.typography.h5,
            maxLines = 1,
            overflow = TextOverflow.Ellipsis
        )

        OutlinedTextField(value = name, onValueChange = onValueChange, label = { Text(text = "name") })
    }

}      

Compose 中的狀态提升是一種将狀态移至可組合項的調用方以使可組合項無狀态的模式。Jetpack Compose 中的正常狀态提升模式是将狀态變量替換為兩個參數:

  • value: T

    :要顯示的目前值
  • onValueChange: (T) -> Unit

    :請求更改值的事件,其中 

    T

     是建議的新值

不過,并不局限于 

onValueChange

。如果更具體的事件适合可組合項,您應使用 lambda 定義這些事件,就像使用 

onExpand

 和 

onCollapse

 定義适合 

ExpandingCard

 的事件一樣。

使用viewmodel和observeAsState:

implementation "androidx.compose.runtime:runtime-livedata:$compose_version"      
class MainActivity : ComponentActivity() {
    private val helloViewModel:HelloViewModel by viewModels()//初始化viewmodel,否則旋轉螢幕無法儲存狀态
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContent {
            HelloScreen(helloViewModel)
        }
    }

}


class HelloViewModel : ViewModel() {
    private val _name = MutableLiveData<String>("")
    val name: LiveData<String> get() = _name

    fun onValueChanged(newName: String) {
        _name.value = newName
    }
}

@Composable
fun HelloScreen(helloViewModel: HelloViewModel = HelloViewModel()) {
    val name: String by helloViewModel.name.observeAsState("")
    val onValueChanged:(String) -> Unit = {helloViewModel.onValueChanged(it)}

    TextAndTextField(name = name, onValueChange = onValueChanged)
}

@Composable
fun TextAndTextField(name: String, onValueChange: (String) -> Unit) {

    Column(modifier = Modifier.padding(16.dp)) {

        Text(
            text = "Hello,$name",
            modifier = Modifier.padding(bottom = 6.dp),
            style = MaterialTheme.typography.h5,
            maxLines = 1,
            overflow = TextOverflow.Ellipsis
        )

        OutlinedTextField(
            value = name,
            onValueChange = onValueChange,
            label = { Text(text = "name") })
    }

}