Android 学习笔记 D01|从空工程到第一张会响应点击的文章卡片
摘要:第一天不追求页面数量,而是打通工程构建、设备运行、点击事件和状态更新。用一张文章卡片解释 Activity、Composable 与可观察状态的分工,再通过故障实验确认代码为什么有效。
标签:Android入门、Kotlin、Jetpack Compose、状态管理、第一行代码
今天要解决的具体问题
DevCommunity 是一个文章阅读练习项目。第一天的需求只有三个:显示文章标题和作者;点击按钮切换收藏;增加“已读”按钮时,不能影响收藏。看起来简单,却足以暴露刚入门时最常见的误解:代码编译成功就等于功能完成,变量变了就等于界面会变。
《第一行代码》第 1 章帮助建立开发环境,第 3 章帮助认识 Activity。本篇使用 Compose 展示界面,这是结合当前 Android 文档的课程扩展,不是把书中的 XML 示例换几个类名。先理解同一个业务目标,再区分两套界面的实现方式。
从系统入口找到自己的代码
普通 Kotlin 控制台程序通常从 main 函数开始。Android 则由系统根据应用清单启动组件;在 Compose 模板中,可以沿着 Manifest 中的启动 Activity 找到 onCreate,再找到 setContent。前者连接系统生命周期,后者建立 Compose 内容。一个按钮对应的业务不需要另建一个 Activity。
建工程时选择支持 Compose 的空 Activity 模板,项目保持单模块。第一次运行先保留模板页面,确认构建、安装和启动都能完成,再修改界面。这种顺序能够隔离问题:模板都启动失败,应先查环境;模板可运行而自定义页面失败,再查新增代码。不要同时换 JDK、更新全部依赖和重写界面,否则很难说明究竟哪一步修好了问题。
记录 Android Studio、Gradle Wrapper、AGP、Kotlin、JDK、SDK 与设备系统版本。它们各有职责,不能用“装的是最新版”代替记录。版本取自实际工程与运行环境,并按构建配置文档检查配套关系。
先让卡片只负责显示
下列代码是已有 Compose Material 3 工程中的局部示例,需要 activity-compose、Compose runtime、foundation 和 Material 3,沿用模板已通过同步的依赖组合。它不包含 Gradle、应用主题和 Manifest,本次写稿未逐篇编译。
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
@Composable
fun FirstArticle(modifier: Modifier = Modifier) {
val bookmarked = remember { mutableStateOf(false) }
val read = remember { mutableStateOf(false) }
Column(modifier.padding(16.dp)) {
Text("第一张 Android 文章卡片")
Text("作者:学习者")
Button(onClick = { bookmarked.value = !bookmarked.value }) {
Text(if (bookmarked.value) "取消收藏" else "收藏")
}
Button(onClick = { read.value = !read.value }) {
Text(if (read.value) "标为未读" else "标为已读")
}
Text(if (read.value) "阅读状态:已读" else "阅读状态:未读")
}
}
在模板 Scaffold 的内容区域调用 FirstArticle,并把 innerPadding 转成 Modifier.padding 传入,避免页面内容被系统栏遮住。保留主题包装,先让文字与按钮按模板样式正常工作;第一天暂不加入图片库、网络库或复杂动画。
代码有两份状态,因为“是否收藏”和“是否读过”是两个独立事实。如果只用一个 Boolean 同时控制两组文案,就会出现点击已读却取消收藏的错误。拆成两个变量不是写法偏好,而是业务中确实存在四种合法组合。
点击之后究竟发生了什么
按钮收到点击后执行回调,回调修改 MutableState 的 value。读取相关状态的组合范围会被安排重组,界面据此显示新的文案。remember 负责在对应组合位置仍然存在时保留状态对象;mutableStateOf 负责让值的读取和修改进入 Compose 的状态观察机制,两者解决的问题不同。Compose 状态文档
可以把这一过程口述成“事件改变事实,界面展示事实”。不要另写一个字符串变量保存“收藏”文案,再手工让它和 Boolean 同步。文案本来就能从收藏状态计算出来,额外存储只会增加不一致的可能。
也不要在 Composable 函数体中记录一次“用户点击”。界面函数可能因为其他状态变化重新执行,函数执行次数并不等于用户操作次数。真正的点击日志应写在 onClick 中,必要时记录目标文章 ID 和切换后的值。Compose 思维方式
两个故障实验与排查顺序
实验一,把收藏状态换成普通局部 Boolean,在回调中取反。预期是点击不能可靠驱动界面更新;即使后来因其他变化重新执行,也可能重新初始化。先确认点击回调确实执行,再检查数据是否变更,最后检查界面读的是不是可观察状态。不要一看到文字不动就反复调整布局。
实验二,在 onClick 中加入 android.util.Log.d,标签使用 ArticlePractice,记录前后值。只筛选当前应用进程与该标签,连续点击三次,预期出现三次操作记录。随后旋转设备,注意按钮状态可能回到初始值:本例只练重组期间的保留,尚未实现 Activity 重建恢复。
实验结果应自己填写。如果没有运行,就写“预期”,不能写成已验证的设备记录。截图也只能证明某个时刻的显示,不能单独证明交互前后的状态正确。
三道原创面试问答
1. Activity、Composable 和状态各负责什么? Activity 是系统管理的界面宿主;Composable 根据输入描述内容;状态承载会随操作变化的事实。追问:一张卡片必须对应一个 Activity 吗?不需要,多个组件可以属于同一个宿主,组件复用与系统组件划分是两件事。
2. val 为什么可以配合收藏切换? val 限制变量重新指向另一个对象,本例修改的是 MutableState 对象的 value 属性。追问:val 指向 MutableList,列表能否追加元素?能,引用不能重新赋值不代表对象内部不可变。
3. 为什么 UI 函数不能被当作只执行一次的初始化函数? 它的执行由组合和重组调度,需要允许重复执行和跳过。追问:把“增加阅读次数”直接写进函数体会怎样?可能把绘制过程当作业务事件重复计数,应在明确事件或适当副作用中处理。
以上为课程自拟题。可延伸阅读题库的四大组件主题,但本篇没有把四大组件全部展开成完整专题。
今天的验收
关闭示例后重新增加“已读”状态,并验证收藏、阅读四种组合均可出现;能从 Manifest 找到启动 Activity;能在 Logcat 定位一次点击;能说明旋转后为什么尚不能保证恢复。最后保存工程版本记录与一份实际故障记录。完成这些,比只交一张漂亮的模板截图更能证明第一天学到了什么。
转载自 CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/2403_85733230/article/details/167349451



