接着修改待办事项demo 动画有问题 导致初始不显示数据其实数据库是有数据的。原代码如下package com.example.testcompose1 import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.FastOutSlowInEasing import androidx.compose.animation.core.tween import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.scaleIn import androidx.compose.animation.slideInHorizontally import androidx.compose.animation.slideOutHorizontally import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material.icons.filled.Delete import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults import androidx.compose.material3.Checkbox import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Switch import androidx.compose.material3.Text import androidx.compose.material3.TextField import androidx.compose.material3.TextFieldDefaults import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.SideEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateMapOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel import com.example.testcompose1.data.TodoEntity import kotlinx.coroutines.delay import kotlinx.coroutines.launch import java.util.Collections.rotate OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class) Composable fun TodoListScreen( viewModel: TodoViewModel, // 获取ViewModel实例。 在同一个activity作用域中是单例。 settingsViewModel: SettingsViewModel, onNavigateToDetail: (Int) - Unit {} ) { // val screenWidth LocalConfiguration.current.screenWidthDp.dp.value val configuration LocalConfiguration.current val density LocalDensity.current val screenWidthPx with(density) { configuration.screenWidthDp.dp.toPx() } val offsetX -(screenWidthPx * 3).toInt() // 从屏幕左侧3倍宽度外滑入 var showInfiniteList by remember { mutableStateOf(false) } if (showInfiniteList) { // 显示无限滚动列表并提供一个返回按钮 Scaffold( topBar { TopAppBar( title { Text(无限滚动列表) }, navigationIcon { IconButton(onClick { showInfiniteList false }) { Icon(Icons.Default.ArrowBack, contentDescription 返回) } } ) } ) { innerPadding - // 给 InfiniteListPage 添加内边距 Box(modifier Modifier.padding(innerPadding)) { InfiniteListPage() } } } else { // 显示原待办事项列表 // 使用 remember 和 mutableStateOf 保存输入框的文本 var text by remember { mutableStateOf() } // 使用 mutableStateListOf 保存待办项列表 // val todoItems remember { mutableStateListOfString() } // 将 StateFlow 转换为 Compose 可观察的 State // val todoItems by viewModel.todoItems.collectAsState() val todos by viewModel.todos.collectAsState() // 获取协程作用域用于延迟删除 val scope rememberCoroutineScope() // 管理每个项的可见性初始为 true新添加的项先设为 false然后立即设为 true val itemVisibility remember { mutableStateMapOfInt, Boolean() } // key改为用id // 同步 itemVisibility 与 todoItems为新增项添加初始 false并在下一帧设为 true LaunchedEffect(todos) { todos.forEach { todo - if (!itemVisibility.containsKey(todo.id)) { // 新项初始不可见 itemVisibility[todo.id] false // 等待一帧然后设为可见触发进入动画 launch { delay(50) // 短暂延迟确保重组 itemVisibility[todo.id] true } } } // 清理已删除的项 // itemVisibility.keys.retainAll(todoItems.toSet()) val currentIds todos.map { it.id }.toSet() itemVisibility.keys.retainAll(currentIds) } Column(modifier Modifier.padding(16.dp)) { ThemeSwitch(settingsViewModel) // 添加开关 Spacer(modifier Modifier.height(8.dp)) // 文本输入框 TextField( value text, onValueChange { text it }, // 反向绑定视图变化-- 数据变化 label { Text(输入待办事项) }, colors TextFieldDefaults.colors( focusedContainerColor MaterialTheme.colorScheme.surface, // 获得焦点时的背景色 unfocusedContainerColor MaterialTheme.colorScheme.surfaceVariant, // 失去焦点时输入框背景色 focusedIndicatorColor MaterialTheme.colorScheme.primary, // 输入框底部下划线的颜色。 unfocusedIndicatorColor MaterialTheme.colorScheme.onSurface.copy(alpha 0.5f) ), modifier Modifier.fillMaxWidth() ) // 添加按钮 Button( onClick { viewModel.addTodo(text) text }, shape MaterialTheme.shapes.small, // 使用主题形状 colors ButtonDefaults.buttonColors( containerColor MaterialTheme.colorScheme.primary, // 容器背景色按钮底色 contentColor MaterialTheme.colorScheme.onPrimary // 内容颜色按钮上文字 / 图标的颜色 ), modifier Modifier.padding(top 8.dp) ) { Text(添加) } // 显示待办列表 Spacer(modifier Modifier.height(16.dp)) Text(待办列表, style MaterialTheme.typography.titleMedium) LazyColumn { items(items todos ,key { it.id }) // 使用唯一 id 作为 key确保动画正确识别 { todo - val visible itemVisibility[todo.id] ?: true // 为每个项添加动画。 AnimatedVisibility没起作用 AnimatedVisibility( visible visible, enter fadeIn(animationSpec tween(1500, easing FastOutSlowInEasing)) slideInHorizontally( initialOffsetX { -3000 }, // 固定大偏移量从左侧 3000 像素外滑入 animationSpec tween(1500, easing FastOutSlowInEasing) ) scaleIn( initialScale 0.1f, animationSpec tween(1500, easing FastOutSlowInEasing) ), exit fadeOut(animationSpec tween(500)) slideOutHorizontally(targetOffsetX { 200 } , animationSpec tween(500)) ) { // SideEffect 是一个专门用于执行副作用的可组合函数。它的主要作用是在每次 重组recomposition 时安全地执行那些不直接影响 UI、但需要与外部系统交互的操作例如日志记录、埋点、更新非 Compose 管理的状态等。 SideEffect { println(Item ${todo.title} 显示动画执行) } TodoItemRow(todo todo , onDelete { // 触发删除动画 itemVisibility[todo.id] false scope.launch { delay(500) // viewModel.removeItem(item) // deletingItems deletingItems - item viewModel.deleteTodo(todo) // 清理状态由 LaunchedEffect 的 retainAll 负责 } } ,onToggle { viewModel.toggleComplete(todo) // 切换完成状态 } , onClick { onNavigateToDetail(todo.id) }// 点击跳转 ) } } } } } } Composable fun TodoItemRow( todo: TodoEntity , onDelete: () - Unit // 添加删除回调删除逻辑放在上层。即把回调传给里面的按钮。 ,onToggle: () - Unit , onClick: () - Unit , modifier: Modifier Modifier ) { Card( modifier modifier .fillMaxWidth() .padding(vertical 4.dp) .clickable { onClick() }, // 现在 modifier 应该会叠加动画修饰符 elevation CardDefaults.cardElevation( defaultElevation 2.dp // 这里传你要的默认高度 ), shape MaterialTheme.shapes.medium, // 使用主题形状 colors CardDefaults.cardColors( containerColor MaterialTheme.colorScheme.surface ) ) { Row( verticalAlignment Alignment.CenterVertically, modifier Modifier .fillMaxWidth() .padding(vertical 8.dp), horizontalArrangement Arrangement.SpaceBetween // 横向布局子元素两端对齐剩余空白空间平均分配到子元素之间 ) { // 新增Checkbox切换事项是否已完成的状态 Checkbox( checked todo.isCompleted, onCheckedChange { onToggle() } ) Text(text todo.title ,style MaterialTheme.typography.bodyLarge, color MaterialTheme.colorScheme.onSurface ,textDecoration if (todo.isCompleted) TextDecoration.LineThrough else null // LineThrough是中划线 ) IconButton(onClick onDelete) { Icon(Icons.Default.Delete, contentDescription 删除 , tint MaterialTheme.colorScheme.error) } } } } // 主题切换开关 Composable fun ThemeSwitch(settingsViewModel: SettingsViewModel) { val isDarkTheme by settingsViewModel.isDarkTheme.collectAsState() Row( modifier Modifier .fillMaxWidth() .clip(MaterialTheme.shapes.medium) .background(MaterialTheme.colorScheme.surface) .padding(horizontal 16.dp, vertical 8.dp), horizontalArrangement Arrangement.SpaceBetween, verticalAlignment Alignment.CenterVertically ) { Text( text 深色模式, style MaterialTheme.typography.bodyLarge, color MaterialTheme.colorScheme.onSurface ) Switch( checked isDarkTheme, onCheckedChange { settingsViewModel.toggleDarkMode() } ) } } // 为了允许手动切换深色/浅色模式在应用中保存用户的选择并在主题中读取. 后面改用DataStore保存 //object ThemeManager { // var isDarkTheme by mutableStateOf(false) // private set // // fun toggleTheme() { // 切换是否为深色主题 // isDarkTheme !isDarkTheme // } //}修改后package com.example.testcompose1 import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.FastOutSlowInEasing import androidx.compose.animation.core.tween import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.scaleIn import androidx.compose.animation.slideInHorizontally import androidx.compose.animation.slideOutHorizontally import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material.icons.filled.Delete import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults import androidx.compose.material3.Checkbox import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Switch import androidx.compose.material3.Text import androidx.compose.material3.TextField import androidx.compose.material3.TextFieldDefaults import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.SideEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateMapOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel import com.example.testcompose1.data.TodoEntity import kotlinx.coroutines.delay import kotlinx.coroutines.launch import java.util.Collections.rotate OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class) Composable fun TodoListScreen( viewModel: TodoViewModel, // 获取ViewModel实例。 在同一个activity作用域中是单例。 settingsViewModel: SettingsViewModel, onNavigateToDetail: (Int) - Unit {} ) { // val screenWidth LocalConfiguration.current.screenWidthDp.dp.value val configuration LocalConfiguration.current val density LocalDensity.current val screenWidthPx with(density) { configuration.screenWidthDp.dp.toPx() } val offsetX -(screenWidthPx * 3).toInt() // 从屏幕左侧3倍宽度外滑入 var showInfiniteList by remember { mutableStateOf(false) } if (showInfiniteList) { // 显示无限滚动列表并提供一个返回按钮 Scaffold( topBar { TopAppBar( title { Text(无限滚动列表) }, navigationIcon { IconButton(onClick { showInfiniteList false }) { Icon(Icons.Default.ArrowBack, contentDescription 返回) } } ) } ) { innerPadding - // 给 InfiniteListPage 添加内边距 Box(modifier Modifier.padding(innerPadding)) { InfiniteListPage() } } } else { // 显示原待办事项列表 // 使用 remember 和 mutableStateOf 保存输入框的文本 var text by remember { mutableStateOf() } // 使用 mutableStateListOf 保存待办项列表 // val todoItems remember { mutableStateListOfString() } // 将 StateFlow 转换为 Compose 可观察的 State // val todoItems by viewModel.todoItems.collectAsState() val todos by viewModel.todos.collectAsState() // 获取协程作用域用于延迟删除 val scope rememberCoroutineScope() // 管理每项的 AnimatedVisibility新 id 先 false 再 true 以触发进入动画 val itemVisibility remember { mutableStateMapOfInt, Boolean() } // 必须用 collect 持续监听不能用 LaunchedEffect(todos)Room 每次发射新 List 都会让 // LaunchedEffect 重启并取消子协程导致 delay(50) 里「设为可见」永远跑不完界面一直空白。 LaunchedEffect(Unit) { viewModel.todos.collect { current - current.forEach { todo - if (!itemVisibility.containsKey(todo.id)) { itemVisibility[todo.id] false launch { delay(50) itemVisibility[todo.id] true } } } itemVisibility.keys.retainAll(current.map { it.id }.toSet()) } } Column(modifier Modifier.padding(16.dp)) { ThemeSwitch(settingsViewModel) // 添加开关 Spacer(modifier Modifier.height(8.dp)) // 文本输入框 TextField( value text, onValueChange { text it }, // 反向绑定视图变化-- 数据变化 label { Text(输入待办事项) }, colors TextFieldDefaults.colors( focusedContainerColor MaterialTheme.colorScheme.surface, // 获得焦点时的背景色 unfocusedContainerColor MaterialTheme.colorScheme.surfaceVariant, // 失去焦点时输入框背景色 focusedIndicatorColor MaterialTheme.colorScheme.primary, // 输入框底部下划线的颜色。 unfocusedIndicatorColor MaterialTheme.colorScheme.onSurface.copy(alpha 0.5f) ), modifier Modifier.fillMaxWidth() ) // 添加按钮 Button( onClick { viewModel.addTodo(text) text }, shape MaterialTheme.shapes.small, // 使用主题形状 colors ButtonDefaults.buttonColors( containerColor MaterialTheme.colorScheme.primary, // 容器背景色按钮底色 contentColor MaterialTheme.colorScheme.onPrimary // 内容颜色按钮上文字 / 图标的颜色 ), modifier Modifier.padding(top 8.dp) ) { Text(添加) } // 显示待办列表 Spacer(modifier Modifier.height(16.dp)) Text(待办列表, style MaterialTheme.typography.titleMedium) LazyColumn { items(items todos ,key { it.id }) // 使用唯一 id 作为 key确保动画正确识别 { todo - val visible itemVisibility[todo.id] ?: true // 为每个项添加动画。 AnimatedVisibility没起作用 AnimatedVisibility( visible visible, enter fadeIn(animationSpec tween(1500, easing FastOutSlowInEasing)) slideInHorizontally( initialOffsetX { -3000 }, // 固定大偏移量从左侧 3000 像素外滑入 animationSpec tween(1500, easing FastOutSlowInEasing) ) scaleIn( initialScale 0.1f, animationSpec tween(1500, easing FastOutSlowInEasing) ), exit fadeOut(animationSpec tween(500)) slideOutHorizontally(targetOffsetX { 200 } , animationSpec tween(500)) ) { // SideEffect 是一个专门用于执行副作用的可组合函数。它的主要作用是在每次 重组recomposition 时安全地执行那些不直接影响 UI、但需要与外部系统交互的操作例如日志记录、埋点、更新非 Compose 管理的状态等。 SideEffect { println(Item ${todo.title} 显示动画执行) } TodoItemRow(todo todo , onDelete { // 触发删除动画 itemVisibility[todo.id] false scope.launch { delay(500) // viewModel.removeItem(item) // deletingItems deletingItems - item viewModel.deleteTodo(todo) // 清理状态由 LaunchedEffect 的 retainAll 负责 } } ,onToggle { viewModel.toggleComplete(todo) // 切换完成状态 } , onClick { onNavigateToDetail(todo.id) }// 点击跳转 ) } } } } } } Composable fun TodoItemRow( todo: TodoEntity , onDelete: () - Unit // 添加删除回调删除逻辑放在上层。即把回调传给里面的按钮。 ,onToggle: () - Unit , onClick: () - Unit , modifier: Modifier Modifier ) { Card( modifier modifier .fillMaxWidth() .padding(vertical 4.dp) .clickable { onClick() }, // 现在 modifier 应该会叠加动画修饰符 elevation CardDefaults.cardElevation( defaultElevation 2.dp // 这里传你要的默认高度 ), shape MaterialTheme.shapes.medium, // 使用主题形状 colors CardDefaults.cardColors( containerColor MaterialTheme.colorScheme.surface ) ) { Row( verticalAlignment Alignment.CenterVertically, modifier Modifier .fillMaxWidth() .padding(vertical 8.dp), horizontalArrangement Arrangement.SpaceBetween // 横向布局子元素两端对齐剩余空白空间平均分配到子元素之间 ) { // 新增Checkbox切换事项是否已完成的状态 Checkbox( checked todo.isCompleted, onCheckedChange { onToggle() } ) Text(text todo.title ,style MaterialTheme.typography.bodyLarge, color MaterialTheme.colorScheme.onSurface ,textDecoration if (todo.isCompleted) TextDecoration.LineThrough else null // LineThrough是中划线 ) IconButton(onClick onDelete) { Icon(Icons.Default.Delete, contentDescription 删除 , tint MaterialTheme.colorScheme.error) } } } } // 主题切换开关 Composable fun ThemeSwitch(settingsViewModel: SettingsViewModel) { val isDarkTheme by settingsViewModel.isDarkTheme.collectAsState() Row( modifier Modifier .fillMaxWidth() .clip(MaterialTheme.shapes.medium) .background(MaterialTheme.colorScheme.surface) .padding(horizontal 16.dp, vertical 8.dp), horizontalArrangement Arrangement.SpaceBetween, verticalAlignment Alignment.CenterVertically ) { Text( text 深色模式, style MaterialTheme.typography.bodyLarge, color MaterialTheme.colorScheme.onSurface ) Switch( checked isDarkTheme, onCheckedChange { settingsViewModel.toggleDarkMode() } ) } } // 为了允许手动切换深色/浅色模式在应用中保存用户的选择并在主题中读取. 后面改用DataStore保存 //object ThemeManager { // var isDarkTheme by mutableStateOf(false) // private set // // fun toggleTheme() { // 切换是否为深色主题 // isDarkTheme !isDarkTheme // } //}关键改动没看出原因借助的cursor修改的cursor挺强大的还分析处出问题原因。注释说了必须用 collect 持续监听不能用 LaunchedEffect(todos)Room 每次发射新 List 都会让 LaunchedEffect 重启并取消子协程导致 delay(50) 里「设为可见」永远跑不完界面一直空白。 ok.