class UserViewModel : ViewModel() {
// 双向绑定:用户输入 → ViewModel(单向:ViewModel → UI)
val userName = MutableStateFlow("")
val userEmail = MutableStateFlow("")
val userAge = MutableStateFlow("")
// 表单是否有效
val isFormValid: StateFlow<Boolean> = combine(
userName, userEmail, userAge
) { name, email, age ->
name.isNotBlank() && email.isValidEmail() && age.isNotBlank() && age.toIntOrNull() != null
}.stateIn(viewModelScope, SharingStarted, false)
// 提交
fun submit() {
viewModelScope.launch {
if (isFormValid.value) {
repository.saveUser(User(
name = userName.value,
email = userEmail.value,
age = userAge.value.toInt()
))
}
}
}
// 清除表单
fun clearForm() {
userName.value = ""
userEmail.value = ""
userAge.value = ""
}
}
// 邮箱校验扩展
fun String.isValidEmail(): Boolean =
Patterns.EMAIL_ADDRESS.matcher(this).matches()
Activity 中启用双向绑定
kotlin复制代码
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private val viewModel: UserViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 创建 binding
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
// 设置 lifecycleOwner(必须)
binding.lifecycleOwner = this
binding.viewModel = viewModel
// 收集 StateFlow 更新 UI
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.isFormValid.collect { valid ->
binding.buttonSubmit.isEnabled = valid
}
}
}
}
}
7. DataBinding vs ViewBinding vsfindViewById
方式
编译检查
性能
功能
findViewById
❌ 无
最快
无
ViewBinding
✅ 有
快
生成绑定类,无数据绑定
DataBinding
✅ 有
中等
完整双向绑定,XML 中写逻辑
8. Compose 中的单向/双向数据流
State Hoisting(状态提升,单向数据流)
kotlin复制代码
@Composable
fun UserScreen(viewModel: UserViewModel = viewModel()) {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
when (val state = uiState) {
is UiState.Loading -> LoadingScreen()
is UiState.Success -> UserListScreen(users = state.data)
is UiState.Error -> ErrorScreen(message = state.message)
}
}
// 子组件接收数据(单向)
@Composable
fun UserListScreen(users: List<User>) {
LazyColumn {
items(users) { user ->
UserItem(user = user) // 只接收数据,不修改
}
}
}
// 事件回调向上传递
@Composable
fun UserItem(user: User, onUserClick: (User) -> Unit) {
Text(
text = user.name,
modifier = Modifier.clickable { onUserClick(user) } // 事件向上传
)
}
双向绑定(remember + mutableStateOf)
kotlin复制代码
@Composable
fun LoginScreen() {
var username by remember { mutableStateOf("") }
var password by remember { mutableStateOf("") }
// 双向绑定:用户输入 → 状态,状态变化 → UI
OutlinedTextField(
value = username,
onValueChange = { username = it }, // 双向绑定
label = { Text("用户名") }
)
OutlinedTextField(
value = password,
onValueChange = { password = it }, // 双向绑定
label = { Text("密码") }
)
Button(
onClick = { /* 提交 */ },
enabled = username.isNotBlank() && password.length >= 6
) {
Text("登录")
}
}
9. Sealed Class / 密封类做状态和事件
kotlin复制代码
// UI 状态(单向数据流)
sealed class UiState<out T> {
data object Idle : UiState<Nothing>()
data object Loading : UiState<Nothing>()
data class Success<T>(val data: T) : UiState<T>()
data class Error(val message: String) : UiState<Nothing>()
}
// 用户意图(单向事件流)
sealed class UserIntent {
data object LoadUsers : UserIntent()
data class DeleteUser(val id: String) : UserIntent()
data class UpdateUser(val user: User) : UserIntent()
}
// Side Effect(一次性事件)
sealed class SideEffect {
data class ShowToast(val message: String) : SideEffect()
data object NavigateToHome : SideEffect()
data class ShowError(val message: String) : SideEffect()
}
// ViewModel 中使用
class UserViewModel : ViewModel() {
private val _uiState = MutableStateFlow<UiState<List<User>>>(UiState.Idle)
val uiState: StateFlow<UiState<List<User>>> = _uiState
private val _sideEffect = MutableSharedFlow<SideEffect>()
val sideEffect: SharedFlow<SideEffect> = _sideEffect
// 处理意图
fun processIntent(intent: UserIntent) {
when (intent) {
is UserIntent.LoadUsers -> loadUsers()
is UserIntent.DeleteUser -> deleteUser(intent.id)
is UserIntent.UpdateUser -> updateUser(intent.user)
}
}
private suspend fun emitSideEffect(effect: SideEffect) {
_sideEffect.emit(effect)
}
}
// UI 收集
lifecycleScope.launch {
viewModel.sideEffect.collect { effect ->
when (effect) {
is SideEffect.ShowToast -> showToast(effect.message)
is SideEffect.NavigateToHome -> navigateToHome()
is SideEffect.ShowError -> showErrorDialog(effect.message)
}
}
}