结合FirstComposeApp项目深入学习案例

2026-03-31 21:35 结合FirstComposeApp项目深入学习案例已关闭评论

009 结合 FirstComposeApp 项目深入学习案例

这篇文章是基于我自己的项目 D:\dazhao\androidStudio\FirstComposeApp 来写的。我发现,当学习不再停留在抽象概念,而是直接对着自己写的代码动手改造时,理解会深刻得多。所以,我打算采用一种 逐步重构 的方式来学习,建议你和我一样,边看边改,每完成一个案例就运行一次,亲眼看看变化。

1. 先看看我的项目现状

目前这个项目已经有了一个非常适合深入学习 Compose 的雏形,各个页面都承载了不同的学习点:

  • MainActivity.kt:目前还是模板工程默认的 Hello Android,一个完美的“新手村”。
  • LoginActivity.kt:非常适合用来学习表单输入、状态管理和校验逻辑
  • PlatformChannelActivity.kt:可以学习双输入框的表单处理和参数提交。
  • TalkieIdSettingActivity.kt:适合研究单字段表单、顶部栏以及 Preview 的用法。
  • SettingsScreen.kt:这里能学到 Scaffold、底部导航、列表和弹窗的综合运用。
  • SatelliteChatActivity.kt:是学习动画、状态驱动 UI 和副作用(Side Effects)的好材料。
  • ui/theme/Theme.kt:从这里可以切入 Material3 主题体系的学习。

同时,我也刻意保留了一些“不完美”的地方,这些都是绝佳的练习素材:

  • 入口页和其他页面之间还没有形成统一的 Compose 导航结构,显得有些割裂。
  • SettingsScreen.kt 中,外层用了 Column + verticalScroll,内层又嵌套了 LazyColumn,这是个典型的布局问题。
  • TalkieIdSettingActivity.kt 的 Preview 函数调错了页面,正好可以学习如何正确组织预览。
  • nav_graph.xml 还是旧的 Fragment 导航思路,但项目主体已经转向 Compose Activity 了。
  • SatelliteChatActivity.kt 中有一部分演示逻辑直接写在了 Composable 函数里,适合进一步做业务逻辑的拆分。

所以,这个项目特别适合我们采用一种务实的学习方式:不是推倒重来,而是针对现有页面,一屏一屏地升级和优化

2. 我建议的学习顺序

为了循序渐进,我给自己规划了三个阶段:

第一阶段:先把基础 UI 和状态管理吃透

  1. LoginActivity.kt 开始,夯实表单和状态基础。
  2. 接着是 PlatformChannelActivity.kt,处理多字段交互。
  3. 然后是 TalkieIdSettingActivity.kt,完善单字段场景。

第二阶段:开始学习页面结构与列表

  1. 深入 SettingsScreen.kt,理解复杂页面的骨架。
  2. 改造 MainActivity.kt,让它成为真正的应用入口。

第三阶段:学习 Compose 的进阶能力

  1. 钻研 SatelliteChatActivity.kt,玩转动画和高级状态管理。
  2. 最后,统一导航、主题和状态流,让应用架构更清晰。

案例 1:把 LoginActivity.kt 练成标准 Compose 表单页

当前我已经掌握了什么

目前这个页面已经有了:
- remembermutableStateOf 管理状态。
- OutlinedTextFieldButton 的基本使用。
- 用 Toast 做简单反馈。
- 基本的条件判断逻辑。

这说明我已经成功跨进了 Compose 的大门,处于“能用”的阶段。

我的下一步学习目标

目标是把当前这个“能跑起来”的页面,升级成一个“结构清晰、便于维护和扩展”的页面。这就像把毛坯房装修成精装房,功能不变,但体验和品质提升了。

我计划的重构点

  1. 拆分组件:把庞大的 LoginScreen() 函数拆开。
    • LoginScreen() 作为容器,负责状态管理和组合。
    • LoginForm(...) 只负责渲染输入框。
    • LoginAction(...) 或直接保留一个清晰的按钮区域。
  2. 状态提升:这是 Compose 的核心思想之一。
    • 让父组件(如 LoginScreen)持有 usernamepassword 等状态。
    • 子组件(如 LoginForm)变为无状态的,只接收数据和事件回调。
  3. 增强交互
    • 增加按钮禁用逻辑:当用户名或密码为空时,保存按钮应不可点击。
    • 增加更友好的错误提示:比如在输入框下方提示“用户名长度不足”或“密码太短”。

我能从中学到的 Compose 知识点

  • 什么是有状态(Stateful)和无状态(Stateless)的 Composable。
  • “状态提升”(State Hoisting)的意义和具体做法。
  • 为什么组件拆小后,@Preview 会变得更容易编写和测试。
  • 如何把表单页面写得像“声明UI”,而不是传统“拼控件”的思路。

我的练习建议

可以把目前分散的状态变量:

var username by remember { mutableStateOf("") }
var password by remember { mutableStateOf("") }

提升为一个统一的数据结构,例如:

data class LoginUiState(
    val username: String = "",
    val password: String = "",
    val loading: Boolean = false,
    val errorMessage: String? = null
)

这样做能为后续引入 ViewModel 铺平道路。

进一步升级的思路

当我把本页按照上述思路改造完后,很自然地就能接着学习下面3个东西,让页面更完整:
- 使用 collectAsState() 来收集 ViewModel 中的 StateFlow 状态。
- 为登录按钮添加 loading 状态(比如显示进度条)。
- 登录成功后,如何进行导航跳转。


案例 2:把 PlatformChannelActivity.ktTalkieIdSettingActivity.kt 练成“可复用表单页”

这两个页面简直是绝配,非常适合放在一起学习。因为它们本质上都是“输入一些配置信息然后保存”的场景。

这两个页面在当前项目中的价值

  • PlatformChannelActivity.kt:代表双字段表单。
  • TalkieIdSettingActivity.kt:代表单字段表单。

这正好可以训练我把“具体的业务页面”抽象成“通用的表单模式”的能力。

我设定的学习目标

  1. 抽取公共结构:观察两个页面,找出共同部分。
    • 顶部的 TopAppBar(返回按钮和标题)。
    • 中间的输入区域(一个或多个字段)。
    • 底部的保存按钮。
  2. 分离关注点:学会把表单的校验逻辑从按钮的 onClick 事件里剥离出来。
  3. 掌握输入框细节
    • 学习使用 KeyboardOptions(比如限制数字键盘)。
    • 为输入框补充更多 Material Design 元素:
      • isError(错误状态)
      • supportingText(辅助文本/错误提示)
      • leadingIcon / trailingIcon(前后图标)

项目里一个很适合立即修正的问题

我注意到 TalkieIdSettingActivity.kt 文件底部的 Preview 写成了这样:

@Preview(showBackground = true)
@Composable
fun TalkieIdSettingScreen() {
    MyApplicationTheme {
        PlatformChannelSettingScreen( // 注意!这里预览的是另一个页面!
            onBack = {},
            onConfigure = { _, _ -> }
        )
    }
}

这个问题的典型性在于:
- Preview 函数名是 TalkieIdSettingScreen,但实际渲染的却是 PlatformChannelSettingScreen 组件。
- 这提醒我:Preview 应该尽量做到一页一个,并且命名清晰,避免混淆。

我的练习建议

我可以尝试创建两个更基础、更可复用的无状态组件:
- SettingTextField(...):封装了标签、错误提示等逻辑的输入框。
- SettingSaveButton(...):封装了禁用状态等逻辑的按钮。

然后用这两个基础组件来重构那两个页面。这个过程会让我深入思考:
- 组件的抽取边界在哪里?
- 一个可复用的 Composable,它的参数列表应该如何设计?
- 预览函数在项目中应该如何组织和管理?


案例 3:用 SettingsScreen.kt 学真正的页面结构与列表

这个页面是我项目里目前最复杂、也最值得深入打磨的一页。因为它已经不再是简单的控件堆砌,而是包含了:
- Scaffold(应用骨架)
- 底部导航栏
- 用户信息头部卡片
- 设置项列表
- AlertDialog(弹窗)

它非常接近一个真实的业务页面了。

当前页面最值得我学习的点

SettingItem 数据模型 + SettingList + SettingRow 这个结构,已经初具“声明式列表渲染”的雏形了。数据驱动UI的思想在这里有了很好的体现。

当前页面最值得我修正的问题

我现在外层写了一个 Column 并设置了 verticalScroll,而内层的 SettingList 函数里又用了一个 LazyColumn

@Composable
private fun SettingList(items: List<SettingItem>) {
    LazyColumn( // 内层滚动
        modifier = Modifier.fillMaxWidth(),
        verticalArrangement = Arrangement.spacedBy(4.dp),
        contentPadding = PaddingValues(horizontal = 16.dp, vertical = 8.dp)
    ) {
        items(items) { item ->
            SettingRow(item = item)
        }
    }
}
// 而在 SettingsScreen() 中:
Column(
    modifier = Modifier
        .verticalScroll(rememberScrollState()) // 外层滚动
        .padding(innerPadding)
) { ... }

这会导致一个经典的 Compose 布局问题:外层和内层同时可以纵向滚动,体验非常奇怪。

我打算怎么改

最好的办法是把整个页面改造成一个单一的 LazyColumn,把头部卡片和列表项都作为它的子项。例如:

LazyColumn(
    modifier = Modifier
        .fillMaxSize()
        .padding(innerPadding),
    contentPadding = PaddingValues(vertical = 16.dp)
) {
    item {
        UserHeaderCard() // 头部作为单独一项
    }
    items(settingsList, key = { it.id }) { item -> // 列表项
        SettingRow(item = item)
    }
}

我能从中学到的 Compose 点

  • 为什么复杂的、需要滚动的页面应该优先使用单一的滚动容器。
  • LazyColumn 不只是用来做纯列表,它可以作为整个页面的布局骨架。
  • key = { it.id } 参数对于列表性能优化的重要性。
  • 在一个 LazyColumn 中,如何和谐地共存头部、列表项和可能出现的弹窗等元素。

进阶练习想法

  1. 为不同的设置项(如“通知”、“隐私”)配上不同的图标,而不是都用 Icons.Default.Person
  2. 实现点击“对讲机ID设置”项时,能跳转到 TalkieIdSettingActivity
  3. 实现点击“平台通道设置”项时,能跳转到 PlatformChannelActivity
  4. 把“版本号”的文本内容改成动态获取自 BuildConfig.VERSION_NAME

案例 4:把 MainActivity.kt 从模板页升级为项目入口

目前的 MainActivity.kt 还是 Android Studio 生成的模板代码:

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContent {
            MyApplicationTheme {
                Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
                    Greeting(
                        name = "Android",
                        modifier = Modifier.padding(innerPadding)
                    )
                }
            }
        }
    }
}

这页最适合用来学习 一个 Compose 工程的入口应该怎么组织

我设定的学习目标

MainActivity 升级为整个 App 的 Compose 总入口,让它承担起更重要的职责:
- 统一包裹 MyApplicationTheme,确保全局主题一致。
- 放置整个应用的 NavHost(导航宿主)。
- 决定应用的首页是登录页还是直接进入主设置页。
- 逐步替代掉那些零散的、独立的 Activity 入口。

我的练习建议

一开始可以不用做太复杂的导航图,先做一个“案例菜单页”试试水。在这个菜单页里,用列表列出所有我们已经写好的页面:
- 登录页
- 对讲机 ID 设置页
- 平台通道设置页
- 设置页
- 卫星聊天页

点击每一项就跳转到对应的页面。这个简单的练习能让我快速理解在 Compose 中,“一个容器页如何组织和管理多个子页面”的基本思想。

我能从中学到的 Compose 点

  • 如何使用 Scaffold 来组织一个页面的顶层结构。
  • Compose 中页面间导航的基本思想。
  • 为什么现代 Android 开发中,Compose 更适合与单 Activity 架构结合。

案例 5:结合 nav_graph.xml 学“从 XML 导航迁移到 Navigation Compose”

我的项目里现在还保留着一个 nav_graph.xml 文件,这说明我的一部分思路还停留在旧的 XML Navigation 模式上。

但从现状看,项目的主要页面都已经用 Compose 写了,所以现在正是开始迁移到 navigation-compose 库的绝佳时机。

学习目标

通过这个案例,我可以做一次清晰的思路对照:
1. 旧思路
- 在 nav_graph.xml 中定义目的地(Destination)和动作(Action)。
- 目的地是 Fragment
- 在代码中使用 findNavController() 进行导航。
2. 新思路
- 在 Kotlin 代码中直接定义 NavHost
- 使用 composable(route = "...") { ... } 来声明可组合目的地。
- 使用 navController.navigate(...) 进行导航。

我计划在这个项目里怎么落地

不用一步到位。可以先为最核心的3个页面建立路由(Route):
- login(登录页)
- settings(设置页)
- satellite_chat(卫星聊天页)

然后在改造好的 MainActivity 中放置一个 NavHost,并设置 settings 为起始目的地。

为什么这个案例特别适合现阶段的我

因为我正处在一个非常典型的“进阶”阶段:
- 已经学会了如何编写独立的 Compose 页面。
- 但这些页面还是分散在多个 Activity 中,像一个个孤岛。
- 还没有真正把这些页面组织成一个有机的、完整的 Compose 应用。

掌握 Navigation Compose,正是从“会写 Compose 控件”跨越到“会搭建 Compose 应用架构”的关键分水岭。


案例 6:用 SatelliteChatActivity.kt 学动画、状态机和副作用

这是我项目里最有潜力、也最像真实产品原型的一页。它已经包含了很多 Compose 的进阶能力:
- rememberInfiniteTransition(无限动画)
- LaunchedEffect(副作用)
- Canvas(自绘)
- 自定义的视觉效果(如信号波纹)。
- 复杂的按压交互逻辑。
- 根据状态(连接中、通话中、发送成功/失败)切换不同的 UI。

这一页我建议重点学什么

1. 把 UI 状态和演示逻辑分开

目前页面里有一些用于演示的逻辑,比如:
- 每5秒自动切换一次消息发送状态。
- 松开通话按钮后,随机决定成功或失败。

在初学阶段这样写没问题,但下一步我建议改为更清晰的结构:
- 定义一个 SatelliteChatUiState 数据类来集中管理所有状态。
- 定义明确的事件函数,如 onPressTalk()onReleaseTalk()
- 让所有的状态变化都通过响应这些事件来集中管理。

2. 学会识别哪些逻辑属于“副作用”

比如:
- 定时器(每5秒刷新状态)。
- 发起真实的网络请求来获取连接状态。
- 开始或结束录音。

这些逻辑都不应该散落在 UI 组件的内部,而应该被提取到 ViewModel 中,或者通过 LaunchedEffectrememberCoroutineScope 等 Compose 副作用 API 来妥善管理。

3. 深入学习手势输入和“按住说话”交互

TalkButtonArea() 里已经用到了 pointerInput。这是一个非常好的起点,可以继续深入:
- 按下按钮时开始录音(或模拟开始)。
- 抬起手指时结束并发送。
- 手指滑出按钮区域时取消发送。
- 增加长按阈值判断。
- 处理防止误触的逻辑。

这些都是业务中非常常见的交互需求,在 Compose 里用 pointerInput 可以很优雅地实现。

4. 实践更精细的组件拆分

当前页面已经初步拆成了几个大组件:
- SatelliteConnectionArea
- MessageStatusArea
- TalkButtonArea

方向是对的。下一步可以继续把每个大组件内部的不同状态(比如成功态、失败态、发送中态)拆分成更小、更专注的状态组件。

我能从中学到的 Compose 点

  • 动画如何与具体的业务状态进行绑定和驱动。
  • LaunchedEffect 适合在什么场景下使用(生命周期、条件触发)。
  • 面对一个复杂的页面,如何有条理地将其拆分成多个可组合函数。
  • 在 Compose 中,实现自定义视觉效果不一定需要回到 XML 写 Drawable,直接用 Canvas 绘制也很方便。

案例 7:统一主题与设计语言

我注意到项目中目前有两套主题思路:
- ui/theme/Theme.kt 中定义的 MyApplicationTheme(全局主题)。
- SatelliteChatActivity.kt 文件底部单独定义的 SatelliteAppTheme(页面局部主题)。

这其实也是一个很好的学习案例,关于如何管理应用的设计系统。

我设定的学习目标

学习如何将主题合理地分为两层:
1. 全局 App Theme:定义品牌色、字体、形状等基础设计令牌(Design Tokens)。
2. 业务页面局部扩展:在全局主题的基础上,为特定页面(如深色模式的聊天页)进行微调。

我的推荐做法

  • 全局统一使用 `MyApplication

当前文章价值3.29元,扫一扫支付后添加微信提供帮助!(如不能解决您的问题,可以申请退款)

你可能感兴趣的文章

来源:每日教程每日一例,深入学习实用技术教程,关注公众号TeachCourse
转载请注明出处: https://teachcourse.cn/3844.html ,谢谢支持!

资源分享

php方法调用详细介绍 php方法调用详细介绍
100个python小工具002:目录同步工具 100个python小工具002:目录同步
浅谈AsyncTask 浅谈AsyncTask
Android学习笔记六:Java基础知识 Android学习笔记六:Java基础知

评论已关闭!