- 使用compose 实现类似AbsListView 的界面
- 理解:不需要setAdapter. compose编程与早期的xml方式为何差别这么大
- 理解AbsListViewActivity.kt的设计思路
- 不需要findViewById
- 注解加在函数前,告诉框架这是 UI
Compose = 框架/工具包(名词)
Compose 是 Google 推出的 UI 框架,类似于"装修公司的工具箱"。
Composable = 函数/组件(形容词)
Composable 是 Compose 框架中的 UI 组件/函数,类似于"工具箱里的工具"。
先上图,后看代码AbsListViewActivity.kt

![]() 数据初始化+更多完成
|
![]() 加载更多
|
设计介绍
该代码是一个基于 Jetpack Compose 开发的 Android 列表/网格展示示例(AbsListViewActivity),核心功能是实现可切换展示模式、支持增删改查、加载更多、下拉刷新、状态管理的列表/网格组件,整体遵循 MVVM 架构模式。
用户点击按钮 → View 调用 ViewModel 方法 → ViewModel 修改 StateFlow 状态 → View 通过 collectAsState 自动感知变化 → 界面自动重绘
Model:数据、业务逻辑、网络 / 本地数据源
View:纯 UI 界面,只负责展示、接收用户点击事件,不存业务逻辑 ---View 层(对应代码:Compose UI 部分)---MVVM 中间枢纽,完全不依赖 View
ViewModel:连接 View 和 Model,持有状态、处理业务,不持有 View 引用 核心特征:单向数据流、View 被动响应状态、View 不直接操作数据、无 View 持有、解耦。---Model 层(隐含在 ViewModel 内);Model 就是数据源与数据实体;实体:List<String> 列表数据
一、核心架构与角色
代码分为三大核心部分,符合 MVVM 设计思想:
| 组件 | 作用 |
|---|---|
AbsListViewActivity |
入口 Activity,负责设置 Compose 内容、承载 UI 容器 |
AbsListViewModel |
核心 viewModel,管理数据、状态逻辑(加载、增删、切换模式等) |
| Composable 函数 | 纯 UI 展示层(AbsListViewDemo/DataItemCard),响应式展示 viewModel 状态 |
二、核心功能拆解
1. 数据管理(ViewModel 核心能力)
ViewModel 封装了所有数据和状态逻辑,通过 StateFlow 实现数据响应式更新:
-
基础数据操作:
-
addItem():新增一条数据项(计数器自增); -
deleteItem(item):删除指定数据项; -
clearAll():清空所有数据; -
toggleMode():切换「列表/网格」展示模式。
-
-
加载相关逻辑:
- 初始加载:
loadInitialData()模拟网络请求(延迟 1.5s),加载 10 条初始数据; - 加载更多:
loadMore()模拟滚动到底部触发的网络请求(延迟 1s),每次加载 5 条新数据; - 刷新数据:
refreshData()清空现有数据后重新执行初始加载,实现「下拉刷新」核心逻辑;
- 初始加载:
-
状态管理:
-
_dataList:存储列表数据(响应式); -
_isGridMode:标记当前是列表/网格模式; -
_loading:标记是否正在加载(加载更多/刷新); -
_initialLoading:标记是否是「首次初始加载」(区分首次加载和加载更多)。
-
2. UI 展示与交互(Composable 核心)
AbsListViewDemo 是核心 UI 组件,响应式监听 ViewModel 状态并渲染:
-
顶部操作区:
- 标题:「AbsListView Compose 示例」;
- 功能按钮:添加数据、清空数据、切换列表/网格模式、刷新数据(刷新按钮加载时禁用);
- 数据统计卡片:展示当前数据条数,加载中显示「加载状态+进度条」。
-
状态适配展示:
- 首次加载中:全屏显示加载动画+提示文字(「加载数据中...」);
- 空数据状态:全屏显示空状态图标+提示(「暂无数据」);
- 有数据状态:根据
isGridMode切换LazyColumn(列表)/LazyVerticalGrid(网格,3列)。
-
滚动加载更多:
- 监听
LazyListState的滚动状态,当滚动到底部且非加载状态时,自动触发loadMore(); - 加载中时,列表/网格底部显示「加载更多+进度条」提示。
- 监听
-
数据项组件(DataItemCard):
- 每个数据项封装为卡片,显示文本内容;
- 右侧带删除按钮,点击触发
deleteItem()删除当前项。
3. 辅助能力
-
预览支持:
@PreviewScreenSizes注解支持多屏幕尺寸预览; -
协程管理:通过
viewModelScope(ViewModel 生命周期)、rememberCoroutineScope(Composable 生命周期)管理异步操作; -
Material3 风格:基于 Material3 组件(
Surface/Card/Button/CircularProgressIndicator)实现样式,适配主题色。
三、核心流程示例
- 页面启动 → ViewModel 初始化 → 执行
loadInitialData()→ 显示「初始加载中」; - 初始数据加载完成 → 渲染列表/网格(默认列表模式);
- 用户滚动到底部 → 触发
loadMore()→ 底部显示「加载更多」→ 加载完成后追加数据; - 用户点击「切换到网格」→
isGridMode变为 true → UI 切换为 3 列网格; - 用户点击「刷新数据」→ 清空数据 → 重新执行初始加载 → 显示加载状态 → 加载完成后展示新数据;
- 用户点击数据项删除按钮 → 移除该数据项 → UI 自动刷新。
四、核心亮点
-
响应式状态管理:通过
collectAsState()监听StateFlow,状态变化自动驱动 UI 刷新; - 状态边界清晰:ViewModel 负责业务逻辑,UI 层只负责渲染,符合「单向数据流」;
- 友好的用户体验:区分首次加载/加载更多/空数据状态,加载过程有明确的视觉反馈;
-
可复用性:数据项封装为独立
DataItemCard,便于复用和修改; -
生命周期安全:使用
viewModelScope确保异步操作跟随 ViewModel 生命周期,避免内存泄漏。
AbsListViewActivity.kt
package com.younghare.qqhelper
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.Info
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.PreviewScreenSizes
import androidx.compose.ui.unit.dp
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.lifecycle.viewmodel.compose.viewModel
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
class AbsListViewActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
// 直接使用 MaterialTheme
MaterialTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
AbsListViewDemo()
}
}
}
}
}
// ============================================
// ViewModel - 管理数据和状态
// ============================================
class AbsListViewModel : ViewModel() {
private val _dataList = MutableStateFlow<List<String>>(emptyList())
val dataList: StateFlow<List<String>> = _dataList
private val _isGridMode = MutableStateFlow(false)
val isGridMode: StateFlow<Boolean> = _isGridMode
// 加载状态
private val _loading = MutableStateFlow(false)
val loading: StateFlow<Boolean> = _loading
// ✅ 新增:初始加载状态(用于区分首次加载和加载更多)
private val _initialLoading = MutableStateFlow(true)
val initialLoading: StateFlow<Boolean> = _initialLoading
private var counter = 0
init {
// ✅ 模拟网络请求加载初始数据
viewModelScope.launch {
loadInitialData()
}
}
// ✅ 新增:模拟网络请求加载初始数据
private suspend fun loadInitialData() {
_initialLoading.value = true
_loading.value = true
try {
// 模拟网络延迟 1500ms(让用户看到加载效果)
delay(1500)
val initialData = (1..10).map { "项目 $it" }
_dataList.value = initialData
counter = 10
} finally {
_initialLoading.value = false
_loading.value = false
}
}
fun addItem() {
counter++
_dataList.value = _dataList.value + "新项目 $counter"
}
fun deleteItem(item: String) {
_dataList.value = _dataList.value.filter { it != item }
}
fun clearAll() {
_dataList.value = emptyList()
// 清空后重置计数器(可选)
// counter = 0
}
fun toggleMode() {
_isGridMode.value = !_isGridMode.value
}
// 模拟网络请求加载更多
suspend fun loadMore() {
// 如果正在加载或没有数据,不执行
if (_loading.value || _dataList.value.isEmpty()) return
_loading.value = true
try {
// 模拟网络请求延迟 1000ms
delay(1000)
val currentSize = _dataList.value.size
val newItems = (currentSize + 1..currentSize + 5).map { "加载项目 $it" }
_dataList.value = _dataList.value + newItems
} finally {
_loading.value = false
}
}
// ✅ 新增:刷新数据(重新加载)
fun refreshData() {
viewModelScope.launch {
// 清空现有数据
_dataList.value = emptyList()
// 重新加载
loadInitialData()
}
}
}
// ============================================
// UI Composable 函数
// ============================================
@PreviewScreenSizes
@Composable
fun AbsListViewDemo(
viewModel: AbsListViewModel = viewModel() //viewModel() 检测到当前在 Activity 中 创建(或复用)AbsListViewModel 实例
// 或 viewModel: AbsListViewModel = viewModel<AbsListViewModel>()
) {
val dataList by viewModel.dataList.collectAsState()
val isGridMode by viewModel.isGridMode.collectAsState()
val loading by viewModel.loading.collectAsState()
val initialLoading by viewModel.initialLoading.collectAsState() // ✅ 获取初始加载状态
val listState = rememberLazyListState()
val coroutineScope = rememberCoroutineScope()
// 检测滚动到底部触发加载更多
LaunchedEffect(listState) {
snapshotFlow { listState.layoutInfo }
.collect { layoutInfo ->
val visibleItems = layoutInfo.visibleItemsInfo
if (visibleItems.isNotEmpty()) {
val lastVisibleIndex = visibleItems.last().index
val totalItems = layoutInfo.totalItemsCount
// 滚动到底部且不处于加载状态
if (lastVisibleIndex >= totalItems - 1 && totalItems > 0 && !loading) {
coroutineScope.launch {
viewModel.loadMore()
}
}
}
}
}
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
) {
// 标题
Text(
text = "AbsListView Compose 示例",
style = MaterialTheme.typography.headlineSmall,
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 16.dp),
textAlign = androidx.compose.ui.text.style.TextAlign.Center
)
// 操作按钮行
Row(
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 16.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
Button(
onClick = { viewModel.addItem() },
modifier = Modifier.weight(1f)
) {
Text("添加数据")
}
Button(
onClick = { viewModel.clearAll() },
modifier = Modifier.weight(1f),
colors = ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.error
)
) {
Text("清空数据")
}
Button(
onClick = { viewModel.toggleMode() },
modifier = Modifier.weight(1f)
) {
Text(if (isGridMode) "切换到列表" else "切换到网格")
}
}
// ✅ 新增:刷新按钮
Row(
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 8.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
Button(
onClick = { viewModel.refreshData() },
modifier = Modifier.fillMaxWidth(),
enabled = !loading // 加载时禁用
) {
Text(if (loading) "加载中..." else "🔄 刷新数据")
}
}
// 数据统计信息
Card(
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 8.dp),
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.primaryContainer
)
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(12.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = "数据数量: ${dataList.size} 项",
style = MaterialTheme.typography.bodyMedium
)
// 显示加载状态
if (loading) {
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalAlignment = Alignment.CenterVertically
) {
CircularProgressIndicator(
modifier = Modifier.size(16.dp),
strokeWidth = 2.dp
)
Text(
text = if (initialLoading) "初始化加载中..." else "加载中...",
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.primary
)
}
}
}
}
// 内容区域:列表/网格 或 空状态
// ✅ 处理初始加载状态
if (initialLoading) {
// 初始加载中的全屏占位
Box(
modifier = Modifier
.fillMaxSize()
.padding(32.dp),
contentAlignment = Alignment.Center
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
CircularProgressIndicator(
modifier = Modifier.size(64.dp),
strokeWidth = 4.dp
)
Text(
text = "加载数据中...",
style = MaterialTheme.typography.headlineSmall,
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f)
)
Text(
text = "正在从服务器获取数据",
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.4f)
)
}
}
} else if (dataList.isEmpty()) {
// 空状态视图
Box(
modifier = Modifier
.fillMaxSize()
.padding(32.dp),
contentAlignment = Alignment.Center
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
Icon(
imageVector = Icons.Default.Info,
contentDescription = "空状态",
modifier = Modifier.size(64.dp),
tint = MaterialTheme.colorScheme.primary.copy(alpha = 0.5f)
)
Text(
text = "暂无数据",
style = MaterialTheme.typography.headlineSmall,
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f)
)
Text(
text = "点击\"添加数据\"按钮添加项目",
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.4f)
)
}
}
} else {
// 使用 Box 包裹列表,以便在列表底部显示加载提示
Box(modifier = Modifier.fillMaxSize()) {
if (isGridMode) {
LazyVerticalGrid(
columns = GridCells.Fixed(3),
modifier = Modifier.fillMaxSize(),
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
items(dataList) { item ->
DataItemCard(
item = item,
onDelete = { viewModel.deleteItem(item) }
)
}
// 在网格模式下显示加载底部
if (loading && !initialLoading) {
item {
Box(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
contentAlignment = Alignment.Center
) {
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalAlignment = Alignment.CenterVertically
) {
CircularProgressIndicator(
modifier = Modifier.size(20.dp),
strokeWidth = 2.dp
)
Text(
text = "加载更多...",
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.primary
)
}
}
}
}
}
} else {
LazyColumn(
state = listState,
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.spacedBy(4.dp)
) {
items(dataList) { item ->
DataItemCard(
item = item,
onDelete = { viewModel.deleteItem(item) }
)
}
// 在列表底部显示加载提示
if (loading && !initialLoading) {
item {
Box(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
contentAlignment = Alignment.Center
) {
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalAlignment = Alignment.CenterVertically
) {
CircularProgressIndicator(
modifier = Modifier.size(20.dp),
strokeWidth = 2.dp
)
Text(
text = "加载更多...",
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.primary
)
}
}
}
}
}
}
}
}
}
}
// ============================================
// 数据项卡片组件
// ============================================
@Composable
fun DataItemCard(
item: String,
onDelete: () -> Unit
) {
Card(
modifier = Modifier.fillMaxWidth(),
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp)
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(12.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = item,
style = MaterialTheme.typography.bodyLarge,
modifier = Modifier.weight(1f)
)
IconButton(
onClick = onDelete,
modifier = Modifier.size(32.dp)
) {
Icon(
imageVector = Icons.Default.Close,
contentDescription = "删除",
tint = MaterialTheme.colorScheme.error
)
}
}
}
}
为何不需要setAdapter. compose编程与早期的xml方式为何差别这么大
非常好的问题!让我详细解释这些关键概念:
1. 是否使用了 AbsListView?
答案:没有使用 AbsListView
您的代码使用的是 Jetpack Compose 的 LazyColumn 和 LazyVerticalGrid,它们不是 AbsListView 的替代品,而是完全不同的 UI 框架。
| 对比项 | XML 方式 (AbsListView) | Compose 方式 |
|---|---|---|
| 基类 |
AbsListView (ListView/GridView) |
LazyColumn / LazyVerticalGrid
|
| UI 框架 | Android View System | Jetpack Compose |
| 适配器 | 需要 Adapter (如 ArrayAdapter) |
不需要,直接使用 items()
|
| 更新方式 | adapter.notifyDataSetChanged() |
状态变化自动重组 |
| 代码风格 | 命令式 | 声明式 |
| 出现时间 | Android 1.0 (2008) | Android 2019 年推出 |
2. 为什么不需要 setAdapter?
XML 方式(需要 Adapter)
// XML 方式 - 需要 Adapter
class MyActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val listView = findViewById<ListView>(R.id.listView)
val data = listOf("项目1", "项目2", "项目3")
// ❗必须创建 Adapter
val adapter = ArrayAdapter<String>(
this,
android.R.layout.simple_list_item_1,
data
)
// ❗必须设置 Adapter
listView.adapter = adapter
// 更新数据需要手动通知
data.add("新项目")
adapter.notifyDataSetChanged() // ❗必须手动刷新
}
}
Compose 方式(不需要 Adapter)
// Compose 方式 - 不需要 Adapter
@Composable
fun MyScreen() {
// 数据直接作为状态
var dataList by remember { mutableStateOf(listOf("项目1", "项目2", "项目3")) }
// ❗直接使用 LazyColumn,不需要 Adapter
LazyColumn {
items(dataList) { item -> // ❗直接传入数据
Text(item)
}
}
// 更新数据:直接修改状态,UI 自动更新!
Button(onClick = {
dataList = dataList + "新项目" // ❗不需要 notifyDataSetChanged()
}) {
Text("添加")
}
}
3. 为什么差别这么大?
核心理念不同
| 特性 | XML 方式 (命令式) | Compose 方式 (声明式) |
|---|---|---|
| 编程范式 | 命令式:告诉"怎么做" | 声明式:告诉"是什么" |
| UI 更新 | 手动调用方法更新 | 状态变化自动更新 |
| 数据绑定 | Adapter 作为桥梁 | 直接使用状态 |
| 代码量 | 多(需要 Adapter、ViewHolder 等) | 少(直接描述 UI) |
对比示例:添加数据
XML 方式(命令式):
// 1. 定义数据
private val dataList = mutableListOf("项目1", "项目2")
// 2. 创建 Adapter
private lateinit var adapter: ArrayAdapter<String>
// 3. 设置 Adapter
adapter = ArrayAdapter(this, R.layout.item, dataList)
listView.adapter = adapter
// 4. 添加数据(需要手动通知)
fun addItem() {
dataList.add("新项目")
adapter.notifyDataSetChanged() // ❗必须手动调用
}
Compose 方式(声明式):
// 1. 定义状态
var dataList by remember { mutableStateOf(listOf("项目1", "项目2")) }
// 2. 描述 UI
LazyColumn {
items(dataList) { item ->
Text(item)
}
}
// 3. 添加数据(自动更新)
fun addItem() {
dataList = dataList + "新项目" // ✅ 自动更新 UI!
}
4. 架构对比图
┌─────────────────────────────────────────────────────────────┐
│ XML 方式 (View System) │
├─────────────────────────────────────────────────────────────┤
│ │
│ 数据源 ──→ Adapter ──→ View (ListView/GridView) │
│ ↑ ↑ │
│ notifyDataSetChanged getView() │
│ │ │ │
│ 手动触发更新 创建/复用 View │
│ │
│ 特点: │
│ • 需要 Adapter 做桥梁 │
│ • 手动管理 View 复用 (ViewHolder) │
│ • 手动通知更新 │
│ • 命令式编程 │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Compose 方式 (声明式 UI) │
├─────────────────────────────────────────────────────────────┤
│ │
│ 状态 (State) ──→ Composable UI ──→ 自动渲染 │
│ ↑ ↑ │
│ 修改状态 自动重组 │
│ │ │ │
│ 用户交互 观察状态变化 │
│ │
│ 特点: │
│ • 不需要 Adapter │
│ • 自动管理渲染 │
│ • 状态变化自动更新 │
│ • 声明式编程 │
└─────────────────────────────────────────────────────────────┘
5. 您的代码中的关键点
// ❗您的代码不需要 Adapter,因为 Compose 自动处理
// 1. 状态(数据源)
private val _dataList = MutableStateFlow<List<String>>(emptyList())
val dataList: StateFlow<List<String>> = _dataList
// 2. 在 Composable 中读取状态
@Composable
fun AbsListViewDemo(viewModel: AbsListViewModel = viewModel()) {
val dataList by viewModel.dataList.collectAsState() // ❗自动订阅
// 3. 直接使用数据(不需要 Adapter)
LazyColumn {
items(dataList) { item -> // ❗直接传入数据
DataItemCard(item)
}
}
}
// 4. 修改数据(自动更新 UI)
fun addItem() {
_dataList.value = _dataList.value + "新项目" // ✅ UI 自动重组
}
6. 总结
| 问题 | 答案 |
|---|---|
| 使用 AbsListView 吗? | ❌ 没有,使用的是 Compose 的 LazyColumn/LazyVerticalGrid |
| 为什么不需要 setAdapter? | Compose 是声明式框架,状态变化自动更新 UI,不需要 Adapter 作为桥梁 |
| 为什么差别这么大? | XML 是命令式(告诉怎么做),Compose 是声明式(告诉是什么),编程范式完全不同 |
| 哪个更好? | Compose 更现代、代码更少、更易维护,但需要学习新的思维方式 |
简单类比
XML 方式 = 手工做饭(需要洗菜、切菜、炒菜、摆盘每一步都要自己操作)
Compose 方式 = 点外卖(告诉外卖平台要什么,自动送来)
两者都能吃到饭,但方式完全不同!🍱
希望这个解释能帮您理解为什么 Compose 和传统的 XML 方式差别这么大!如果还有疑问,欢迎继续提问!😊

