当前位置: 首页 > news >正文

Android compose 无限滚动列表

实现无限滚动列表。 添加依赖:

implementation("androidx.compose.material:material")

创建ViewModel, 用于列表滚动到底部加载数据,以及下拉刷新时重置数据。

package com.example.testcompose1 import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch // 创建无限滚动列表的数据源和 ViewModel class InfiniteListViewModel : ViewModel() { private val _items = MutableStateFlow<List<String>>(emptyList()) val items: StateFlow<List<String>> = _items.asStateFlow() private val _isLoading = MutableStateFlow(false) val isLoading: StateFlow<Boolean> = _isLoading.asStateFlow() private var currentPage = 0 private val pageSize = 20 private val totalItems = 100 // 总共 100 条数据 init { loadMore() } fun loadMore() { // 如果正在加载,或者已经加载完所有数据,则不再加载 if (_isLoading.value || _items.value.size >= totalItems) return viewModelScope.launch(Dispatchers.IO) { // 启动协程,在io线程中执行 _isLoading.value = true // 模拟网络延迟 delay(1000) // 生成新数据 val start = _items.value.size val end = minOf(start + pageSize, totalItems) val newItems = (start until end).map { "项目 $it" } _items.value += newItems _isLoading.value = false } } fun reset() { _items.value = emptyList() currentPage = 0 loadMore() } }

ui单独创建个文件:

package com.example.testcompose1 import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material.* import androidx.compose.material.pullrefresh.PullRefreshIndicator import androidx.compose.material.pullrefresh.pullRefresh import androidx.compose.material.pullrefresh.rememberPullRefreshState import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel @OptIn(ExperimentalMaterialApi::class) @Composable fun InfiniteListPage( viewModel: InfiniteListViewModel = viewModel() ) { val items by viewModel.items.collectAsState() val isLoading by viewModel.isLoading.collectAsState() // 1. 控制下拉刷新的状态 var isRefreshing by remember { mutableStateOf(false) } val refreshState = rememberPullRefreshState( refreshing = isRefreshing, onRefresh = { // 下拉刷新时,清空列表并重新加载第一页 isRefreshing = true // 这里简单重置:清空 ViewModel 中的数据,重新加载 viewModel.reset() isRefreshing = false } ) // 2. 监听滚动到底部,自动加载更多。 创建并记忆LazyColumn的滚动状态 val listState = rememberLazyListState() // LaunchedEffect监听listState的变化,创建协程作用域 LaunchedEffect(listState) { // 将"最后一个可见item的索引"转换成可监听的数据流Flow. snapshotFlow { listState.layoutInfo.visibleItemsInfo.lastOrNull()?.index } // 收集数据流的变化(每次滚动,这个值都会变)。 订阅数据流 .collect { lastVisibleIndex -> // 如果最后一个可见项索引接近列表末尾(比如倒数第 3 项),且没有正在加载,则加载更多 if (lastVisibleIndex != null && lastVisibleIndex >= items.size - 3 && !isLoading && items.size < 100) { viewModel.loadMore() } } } Box( modifier = Modifier .fillMaxSize() .pullRefresh(refreshState) // 下拉刷新修饰符 ) { LazyColumn( state = listState, modifier = Modifier.fillMaxSize() ) { items(items) { item -> ListItem(item = item) } // 当正在加载时,在列表末尾显示加载指示器 if (isLoading) { item { // 插入单个列表项 Box( modifier = Modifier .fillMaxWidth() .padding(16.dp), contentAlignment = Alignment.Center ) { CircularProgressIndicator() } } } } // 下拉刷新指示器 PullRefreshIndicator( refreshing = isRefreshing, state = refreshState, modifier = Modifier.align(Alignment.TopCenter) ) } } @Composable fun ListItem(item: String) { Card( modifier = Modifier .fillMaxWidth() .padding(horizontal = 16.dp, vertical = 4.dp), elevation = 2.dp ) { Text( text = item, modifier = Modifier.padding(16.dp) ) } }

修改下MainActivity, 显示这个无限滚动列表:

运行下,先显示loading:

再显示数据:

滑动到底部加载更多数据:

滑动到底部:

然后再滑动到顶部下拉会重置刷新数据。ok。

mainActivity完整代码:

package com.example.testcompose1 import android.os.Bundle import android.widget.Toast import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.compose.foundation.background 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.padding import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack 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.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.TextField import androidx.compose.material3.TextFieldDefaults import androidx.compose.material3.TopAppBar import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.draw.clip import androidx.compose.ui.platform.LocalContext import androidx.lifecycle.viewmodel.compose.viewModel import com.example.testcompose1.ui.theme.MyAppTheme import com.example.testcompose1.ui.theme.TestCompose1Theme class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // enableEdgeToEdge() setContent { MyAppTheme( darkTheme = ThemeManager.isDarkTheme // 覆盖系统设置 ,dynamicColor = false // 暂时禁用动态颜色。 ) { todoJobList() } } } } @OptIn(ExperimentalMaterial3Api::class) @Preview @Composable fun todoJobList( viewModel: TodoViewModel = viewModel() // 获取ViewModel实例。 在同一个activity作用域中是单例。 ) { var showInfiniteList by remember { mutableStateOf(true) } 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 { // 显示原待办事项列表 val context = LocalContext.current // 使用 remember 和 mutableStateOf 保存输入框的文本 var text by remember { mutableStateOf("") } // 使用 mutableStateListOf 保存待办项列表 // val todoItems = remember { mutableStateListOf<String>() } // 将 StateFlow 转换为 Compose 可观察的 State val todoItems by viewModel.todoItems.collectAsState() Column(modifier = Modifier.padding(16.dp)) { ThemeSwitch() // 添加开关 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.addItem(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 = todoItems) { item -> TodoItem(item = item , onDelete = { viewModel.removeItem(item) }) } } } } } @Composable fun TodoItem(item: String, onDelete: () -> Unit // 添加删除回调,删除逻辑放在上层。即把回调传给里面的按钮。 ) { Card( modifier = Modifier .fillMaxWidth() .padding(vertical = 4.dp), shape = MaterialTheme.shapes.medium, // 使用主题形状 colors = CardDefaults.cardColors( containerColor = MaterialTheme.colorScheme.surface ) ) { Row( modifier = Modifier .fillMaxWidth() .padding(vertical = 8.dp), horizontalArrangement = Arrangement.SpaceBetween // 横向布局子元素两端对齐,剩余空白空间平均分配到子元素之间 ) { Text(text = item ,style = MaterialTheme.typography.bodyLarge, color = MaterialTheme.colorScheme.onSurface) IconButton(onClick = onDelete) { Icon(Icons.Default.Delete, contentDescription = "删除" , tint = MaterialTheme.colorScheme.error) } } } } // 主题切换开关 @Composable fun ThemeSwitch() { 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 = ThemeManager.isDarkTheme, onCheckedChange = { ThemeManager.toggleTheme() } ) } } // 为了允许手动切换深色/浅色模式,在应用中保存用户的选择,并在主题中读取. 后面改用DataStore保存 object ThemeManager { var isDarkTheme by mutableStateOf(false) private set fun toggleTheme() { // 切换是否为深色主题 isDarkTheme = !isDarkTheme } }

ok.

InfiniteListPage组合函数中用到LaunchedEffect启动协程,当Composable 进入组合 或key 变化时自动触发。若key为空,仅在首次进入组合时触发,后续即使 Composable 重组(比如父组件状态变化),也不会重新执行。LaunchedEffect是Compose UI 层的 API,协程的生命周期和单个Composable 组件绑定。注意区别于viewModelScope,viewModelScope处理业务层异步逻辑。

http://www.jsqmd.com/news/523308/

相关文章:

  • Python实战:5步搞定激光雷达点云转RGB-D深度图(附外参校准避坑指南)
  • Kook Zimage真实幻想Turbo保姆级教程:Streamlit WebUI自定义CSS美化与多用户配置
  • 2026全国纤维水泥压力板行业信赖企业:老牌沉淀,品质之选 - 深度智识库
  • 从卫星到生产线:拆解FPGA+GPU异构计算在5个真实场景下的落地实战(附资源消耗对比)
  • 从CCProxy缓冲区溢出漏洞复现到Shellcode实战:一次完整的攻击链剖析
  • 【24年最新算法】NRBO-XGboost回归交叉验证 你就是第一个人使用 基于牛顿-拉夫逊优...
  • 2025-2026年AI营销智能体公司推荐:出海营销本地化需求口碑服务商盘点 - 品牌推荐
  • MES工单管理实战:从创建到结算的完整流程解析(附常见问题解决方案)
  • 深入评测三家业内具有代表性的温度冲击试验箱厂家(2026) - 品牌推荐大师1
  • DolphinScheduler 资源中心大文件上传超时问题分析与解决
  • 港科大MBA蝉联香港第一,《金融时报》全球排名第24位,十五度跻身全球前25强 - 速递信息
  • 云原生隐匿:利用CDN和Serverless构建无法追踪的C2基础设施
  • DataWorks PyODPS避坑指南:如何绕过内存限制与第三方包安装难题
  • 2026年AI营销智能体公司推荐:大中企业营销提效高性价比方案与选购避坑指南 - 品牌推荐
  • 利用Karabiner-Elements优化MacOS输入法切换:将Shift键变身为高效切换工具
  • 学生课堂行为识别数据集(2000张高质量标注)| YOLO训练数据集 AI智慧教育
  • 2025-2026年AI营销智能体公司推荐:助力大中企业全域营销提效与合规安全实践 - 品牌推荐
  • 行业变革下的产线革命:谁是真正的倍速链生产线专业厂家? - 丁华林智能制造
  • 串口通信设置
  • LobeChat效果展示:实测语音合成与多模态对话,效果惊艳
  • 2025-2026年AI营销智能体公司推荐:全域营销效率提升热门服务商对比分析 - 品牌推荐
  • 2026年求推荐泛仕达机构,北京艾尔凡机电设备供应定制两不误 - 工业品网
  • linux 安装简易 git 服务端并使用
  • 2026网站设计公司盘点推荐|国内优秀网站建设公司遴选指南
  • 2025-2026年羊绒衫厂家推荐:高端羊绒原料与工艺技术实力口碑厂家盘点 - 十大品牌推荐
  • 以Ai相伴,脑机赋能|狄耐克联合主办 “321脑机睡眠健康万里行”圆满落幕 - 速递信息
  • 简单理解:IAR配置代码优化(-O2)步骤、生成 HEX 文件(Intel 格式)步骤
  • 2026最新成人补钙避坑权威指南:骨胶原加持,筑牢骨骼健康防线 - 速递信息
  • SC16IS7XX UART扩展库:工业级双通道串口驱动详解
  • 2025-2026年羊绒衫厂家推荐:时尚羊绒衫ODM设计生产一体化服务商盘点 - 十大品牌推荐