Compose 实战练习

Compose 实战练习

这份文档包含了递进式的练习题,从简单到复杂。


📌 练习说明

  • 难度标记: ⭐ = 简单,⭐⭐ = 中等,⭐⭐⭐ = 难
  • 预计时间: 以小时计
  • 先决条件: 完成前面的学习文档

第1级:基础理解

练习 1.1:添加日志打印(⭐ 30分钟)

目标: 理解数据流的完整过程

任务:

ContactsViewModel 中的 setState()handleIntent() 中添加日志,打印:

  1. 收到哪个 Intent
  2. State 如何变化

代码位置: app/src/main/java/com/volvocars/telephony/china/ui/contacts/ContactsViewModel.kt

修改建议:

kotlin 复制代码
override suspend fun handleIntent(intent: Intent) {
    when (intent) {
        is ContactsIntent.FetchContacts -> {
            BaseLog.i(TAG, "收到 FetchContacts Intent")  // 添加这行
            fetchContacts()
        }
        is ContactsIntent.SetContactDetail -> {
            BaseLog.i(TAG, "收到 SetContactDetail Intent: ${intent.contact}")  // 添加这行
            setContactDetail(intent.contact)
        }
    }
}

private suspend fun fetchContacts() {
    BaseLog.i(TAG, "fetchContacts: 设置 Loading 状态")
    setState { copy(loadState = LoadState.Loading) }
    // ...
}

验证方法:

  • 打开应用
  • 打开联系人页面
  • 在 Android Studio 的 Logcat 中查看日志输出
  • 看日志是否按照预期流程输出

学到的内容: 📚

  • 如何调试 MVI 数据流
  • Intent 的完整处理过程

练习 1.2:修改 Loading 文本(⭐ 15分钟)

目标: 学会修改 UI 显示的文本

任务:

ContactsScreen 中的 Loading 显示文本从 "同步数据" 改成 "正在加载联系人..."

代码位置: app/src/main/java/com/volvocars/telephony/china/ui/contacts/ContactsScreen.kt

修改步骤:

kotlin 复制代码
is LoadState.Loading -> {
    Loading(
        modifier = Modifier.fillMaxSize(),
        text = "正在加载联系人..."  // 修改这里
    )
}

验证方法:

  • 重新构建并运行应用
  • 清除联系人缓存(或强制刷新)
  • 看到新的加载文本

学到的内容: 📚

  • 如何修改 UI 文本
  • when 语句的实际使用

练习 1.3:改变首字母圆圈的颜色(⭐ 20分钟)

目标: 学会使用主题颜色修改 UI

任务:

InitialHeader 中的首字母圆圈颜色改成蓝色

代码位置: app/src/main/java/com/volvocars/telephony/china/ui/contacts/ContactsScreen.ktInitialHeader() 函数

原代码:

kotlin 复制代码
Box(
    modifier = modifier
        .padding(start = appDimens.spacing.spacing3xLarge)
        .size(appDimens.heightAndWidth.size3xSmall)
        .background(
            color = SemanticColorKeyTokens.ForegroundL4.value,  // 原颜色
            shape = CircleShape
        ),

修改建议:

kotlin 复制代码
.background(
    color = SemanticColorKeyTokens.BrandBlueL1.value,  // 改成蓝色
    shape = CircleShape
),

验证方法:

  • 重新构建并运行应用
  • 打开联系人页面
  • 看到首字母圆圈变成蓝色

学到的内容: 📚

  • 如何使用设计令牌修改颜色
  • Modifier 的 background() 参数

第2级:组件理解

练习 2.1:添加搜索框(⭐⭐ 1小时)

目标: 学会添加新的 UI 组件

任务:

ContactsScreen 的顶部添加一个搜索框,显示 "搜索联系人"

需要:

  1. ContactsScreen 中添加一个 TextField 组件
  2. 把搜索框放在列表上方

代码结构:

kotlin 复制代码
@Composable
fun ContactsScreen(...) {
    val uiState by contactsVM.uiState.collectAsStateWithLifecycle()
    var searchText by rememberSaveable { mutableStateOf("") }  // 搜索文本状态
    
    Column(modifier = Modifier.fillMaxSize()) {
        // 搜索框
        TextField(
            value = searchText,
            onValueChange = { searchText = it },
            label = { Text("搜索联系人") },
            modifier = Modifier
                .fillMaxWidth()
                .padding(16.dp)
        )
        
        // 下面是原来的 Box 和列表
        Box(modifier = Modifier.weight(1f)) {
            when (uiState.loadState) {
                // ... 原来的代码
            }
        }
    }
}

学到的内容: 📚

  • 使用 TextField 组件
  • Column 的嵌套使用
  • Modifier.weight() 分配空间

练习 2.2:改变列表的每一项高度(⭐⭐ 20分钟)

目标: 学会调整组件的大小

任务:

增加 CallContactListItem 的高度,让列表项更宽敞(比如增加 padding)

代码位置: app/src/main/java/com/volvocars/telephony/china/ui/contacts/ContactsScreen.ktContactList() 函数

修改建议:

kotlin 复制代码
itemsIndexed(contactsInGroup) { index, data ->
    CallContactListItem(
        modifier = Modifier
            .width(itemWidth)
            .padding(vertical = 8.dp),  // 添加竖向 padding
        callContactEntry = data,
        onClick = { onItemClick(data) }
    )
    // ...
}

验证方法:

  • 重新构建并运行
  • 看列表项之间的间距是否增加

学到的内容: 📚

  • Modifier.padding() 的使用
  • 如何微调 UI 布局

第3级:状态管理

练习 3.1:添加新的 Intent(⭐⭐ 45分钟)

目标: 完整的 MVI 流程实践

任务:

添加一个新的功能:用户可以删除一个联系人

需要做的:

  1. ContactsIntent 中添加新的 Intent
kotlin 复制代码
sealed class ContactsIntent : Intent {
    object FetchContacts : ContactsIntent()
    data class SetContactDetail(val contact: ICallLogContact?) : ContactsIntent()
    data class DeleteContact(val contactId: String) : ContactsIntent()  // 新增
}
  1. ContactsViewModel 中处理新 Intent
kotlin 复制代码
override suspend fun handleIntent(intent: Intent) {
    when (intent) {
        is ContactsIntent.FetchContacts -> fetchContacts()
        is ContactsIntent.SetContactDetail -> setContactDetail(intent.contact)
        is ContactsIntent.DeleteContact -> deleteContact(intent.contactId)  // 新增
    }
}

private suspend fun deleteContact(contactId: String) {
    BaseLog.i(TAG, "删除联系人: $contactId")
    // 这里你可以调用 UseCase 删除数据
    // 然后重新加载列表
    fetchContacts()
}
  1. 在 UI 中添加删除按钮
kotlin 复制代码
// 在 CallContactListItem 中添加长按事件
CallContactListItem(
    modifier = Modifier.width(itemWidth),
    callContactEntry = data,
    onClick = { onItemClick(data) },
    onLongClick = {  // 新增
        contactsVM.sendIntent(ContactsIntent.DeleteContact(data.id))
    }
)

验证方法:

  • 运行应用
  • 长按某个联系人
  • 看日志是否打印删除事件

学到的内容: 📚

  • 完整的 Intent 定义流程
  • 如何扩展现有的 MVI 系统
  • 如何在 UI 中添加新的交互

练习 3.2:添加新的 State 字段(⭐⭐ 1小时)

目标: 学会扩展 State

任务:

添加一个"选中的联系人数量"字段,显示在页面顶部

需要做的:

  1. 扩展 ContactsUiState
kotlin 复制代码
data class ContactsUiState(
    override val loadState: LoadState,
    override val data: List<CallLogContactGroup>?,
    val dialogState: ContactDialog = ContactDialog.None,
    val selectedCount: Int = 0  // 新增:选中的联系人数
) : SimpleUiState<List<CallLogContactGroup>>
  1. ContactsViewModel 中更新
kotlin 复制代码
private fun setContactDetail(contact: ICallLogContact?) {
    // ...
    // 更新选中数量
    setState { 
        copy(
            // ...,
            selectedCount = if (contact != null) 1 else 0
        )
    }
}
  1. 在 UI 中显示
kotlin 复制代码
@Composable
fun ContactsScreen(...) {
    Column(modifier = Modifier.fillMaxSize()) {
        // 显示选中数量
        if (uiState.selectedCount > 0) {
            Text(
                text = "已选中 ${uiState.selectedCount} 个联系人",
                modifier = Modifier.padding(16.dp)
            )
        }
        
        // 下面是原来的代码
        Box(modifier = Modifier.weight(1f)) {
            // ...
        }
    }
}

验证方法:

  • 运行应用
  • 点击某个联系人
  • 页面顶部显示 "已选中 1 个联系人"

学到的内容: 📚

  • 如何扩展 State
  • State 和 UI 的同步
  • 条件性显示 UI

第4级:高级应用

练习 4.1:实现搜索过滤(⭐⭐⭐ 2小时)

目标: 综合运用 State、Intent、Filter

任务:

实现联系人搜索:用户输入文字时,列表自动过滤

需要做的:

  1. 扩展 Intent
kotlin 复制代码
sealed class ContactsIntent : Intent {
    // ...
    data class Search(val keyword: String) : ContactsIntent()
}
  1. 扩展 State
kotlin 复制代码
data class ContactsUiState(
    // ...
    val searchKeyword: String = "",  // 搜索关键词
    val filteredData: List<CallLogContactGroup>? = null  // 过滤后的数据
) : SimpleUiState<List<CallLogContactGroup>>
  1. 处理搜索 Intent
kotlin 复制代码
override suspend fun handleIntent(intent: Intent) {
    when (intent) {
        // ...
        is ContactsIntent.Search -> search(intent.keyword)
    }
}

private suspend fun search(keyword: String) {
    setState { copy(searchKeyword = keyword) }
    
    if (keyword.isEmpty()) {
        // 清空搜索,显示所有
        setState { copy(filteredData = data) }
    } else {
        // 过滤数据
        val filtered = data?.filter { group ->
            group.second.any { contact ->
                contact.name.contains(keyword, ignoreCase = true)
            }
        }
        setState { copy(filteredData = filtered) }
    }
}
  1. 在 UI 中使用
kotlin 复制代码
TextField(
    value = searchKeyword,
    onValueChange = { keyword ->
        contactsVM.sendIntent(ContactsIntent.Search(keyword))
    },
    label = { Text("搜索") }
)

// 显示过滤后的数据
ContactList(
    contactGroups = uiState.filteredData ?: uiState.data
)

学到的内容: 📚

  • 完整的搜索实现流程
  • 数据过滤和转换
  • 状态管理的复杂场景

练习 4.2:添加滑动删除功能(⭐⭐⭐ 2小时)

目标: 学会处理复杂的用户交互

任务:

在列表项上向左滑动时,显示删除按钮

需要做的:

这个涉及到自定义 Gesture 处理,比较复杂。

建议:

  1. 先完成前面的练习
  2. 学习 swipeToDismiss Modifier(Compose 的库函数)
  3. 逐步实现

学到的内容: 📚

  • 手势识别(Gesture)
  • 动画效果
  • 复杂的 UI 交互

第5级:性能优化

练习 5.1:分析列表性能(⭐⭐ 1小时)

目标: 学会性能调试

任务:

使用 Android Studio 的 Profiler 分析 ContactsScreen 的性能

步骤:

  1. 打开 Android Studio → View → Tool Windows → Profiler
  2. 运行应用
  3. 打开联系人页面
  4. 观察:
    • CPU 使用率
    • 内存使用
    • Frame Rate(帧率)
  5. 快速滚动列表,看是否有掉帧

优化建议:

  • 如果掉帧,考虑用 LazyColumn 替代 Column(你的代码已经用了)
  • 检查是否有不必要的 recomposition

学到的内容: 📚

  • 性能监控工具的使用
  • Recomposition 的概念

🎯 推荐的学习顺序

复制代码
第1级 (今天完成)
└─ 1.1 日志打印 → 1.2 修改文本 → 1.3 改颜色

第2级 (明天完成)
└─ 2.1 搜索框 → 2.2 调整高度

第3级 (后天完成)
└─ 3.1 删除功能 → 3.2 选中数量

第4级 (一周后)
└─ 4.1 搜索过滤

第5级 (两周后)
└─ 5.1 性能优化

💡 练习技巧

卡住了?这样做:

  1. 查看相似代码

    • 搜索项目中的类似实现
    • 比如 MainViewModel 是如何处理 Intent 的
  2. 查看文档

    • 回到 Compose学习指南.md
    • 查看相关的代码例子
  3. 使用日志调试

    kotlin 复制代码
    BaseLog.i(TAG, "调试信息: $value")
  4. Google 搜索

    • "Compose TextField 例子"
    • "Compose 搜索过滤"
  5. 提问

    • 如果真的卡住,不要死磕,改做其他练习

✅ 完成检查清单

  • 练习 1.1:日志打印能看到
  • 练习 1.2:Loading 文本已修改
  • 练习 1.3:首字母圆圈颜色已改变
  • 练习 2.1:搜索框能输入
  • 练习 2.2:列表项间距已调整
  • 练习 3.1:删除 Intent 已实现
  • 练习 3.2:选中计数已显示
  • 练习 4.1:搜索过滤工作正常
  • 练习 5.1:运行过 Profiler

每完成一个练习,✅ 打勾。


🎉 最后

完成这些练习后,你就能:

  • ✅ 理解 MVI 架构
  • ✅ 使用 Compose 构建 UI
  • ✅ 实现复杂的用户交互
  • ✅ 调试性能问题
  • ✅ 快速上手新的功能开发
相关推荐
遇见火星3 天前
Docker Compose 实战教程,理解Docker Compose核心概念,学会编写 compose.yml,掌握常用命令!
运维·docker·容器·compose
csdn12259873367 天前
JetPack Compose 入门先搞清楚
android·compose·jetpack
モンキー・D・小菜鸡儿15 天前
Android Jetpack Compose 基础控件介绍
android·kotlin·android jetpack·compose
hnlgzb1 个月前
androidx.compose.material3哪几个文件是经常用到的?
androidx·compose
drsonxu1 个月前
Android开发自学笔记 --- 构建简单的UI视图
android·compose
魑魅魍魉都是鬼2 个月前
不练不熟,不写就忘 之 compose 之 动画之 animateSizeAsState动画练习
android·compose
氦客2 个月前
Android Compose 状态的概念
android·compose·重组·状态·组合·mutablestate·mutablestateof
袁震2 个月前
Android-Compose 列表组件详解
android·recyclerview·compose