从Hello World到实战:用Android Studio构建首个完整待办清单应用

发布时间:2026/8/30 21:31:41
从Hello World到实战:用Android Studio构建首个完整待办清单应用 简介这是一份面向Android开发初学者与入门实践者的简易项目实战资源基于Android Studio完整实现用户登录、注册、本地数据存储SQLite、TabLayoutViewPager选项卡切换、表单信息采集含DatePicker、RadioButton、CheckBox等控件及圆形头像渲染等核心功能覆盖应用开发全流程关键知识点。压缩包为ZIP格式大小12.12MB包含完整的Android Studio工程文件如Java/Kotlin源码、XML布局、资源文件及Gradle构建配置可直接导入运行。已有1124人学习下载项目结构清晰、注释充分适合作为课堂实训、课程设计或自学练手材料——读者不仅能获得可运行的完整工程还能深入理解网络请求集成逻辑、Room/SQLite数据持久化方案、Fragment页面管理机制以及UI组件交互验证技巧是夯实Android基础架构能力的典型参考案例。1. 从“Hello World”到“我的第一个App”为什么你的第一个安卓项目不该止步于此每次看到“通过Android Studio编写的简易安卓项目”这个标题我都能回想起自己刚开始接触安卓开发时对着那个默认的“Hello World”界面既兴奋又有点迷茫的心情。兴奋的是一个能跑在手机上的程序从自己手里诞生了迷茫的是接下来该做什么这个项目除了展示一行文字还有什么用我相信这也是很多初学者在完成第一个Demo后的共同疑问。网络上充斥着“Android Studio安装教程”、“如何运行示例项目”这类基础指引它们很重要是必经之路但往往也止步于此让你觉得“哦就这样啊”。今天我想和你聊的不是如何点击“New Project”然后一路“Next”得到一个空壳。我想和你一起把这个“简易项目”当作一块真正的敲门砖去敲开安卓开发实战世界的大门。我们会从一个有明确功能目标的小应用出发——比如一个简易的待办清单To-Do List——来拆解一个完整安卓项目从构思到实现的完整链条。你会发现即使是“简易”项目也涵盖了界面设计、用户交互、数据存储、生命周期管理等核心概念。当你亲手实现一个功能闭环的应用时那种成就感和对框架的理解深度是运行十遍“Hello World”也无法比拟的。这篇文章就是为你铺平从“知道怎么运行”到“知道怎么创造”这段路。2. 项目蓝图定义你的第一个“真正”的简易应用在动手写代码之前花点时间想清楚我们要做什么比盲目开始更重要。一个模糊的目标会导致代码结构混乱中途容易放弃。我们选择“简易待办清单”作为目标因为它需求明确技术点覆盖全面且最终成果有实用价值能给你正反馈。2.1 核心功能需求拆解一个最基础的待办清单应用应该包含以下核心功能点任务展示在一个列表中清晰显示所有已添加的待办事项。这是最直观的界面部分。添加任务用户可以通过一个简单的操作比如点击一个按钮弹出一个输入框来创建新的待办事项。标记完成用户能够将一项任务标记为“已完成”通常视觉上会有变化如文字划掉、复选框勾选。删除任务对于不再需要的任务无论是已完成还是未完成都应该能删除。这四点构成了一个最小可行产品MVP。你可能还会想到编辑任务、按状态筛选、设置提醒等但作为第一个实战项目先聚焦于实现核心闭环。功能越简单你越能把注意力集中在安卓开发的基础机制上。2.2 技术组件选型与对应关系为了实现上述功能我们需要选择并理解Android框架提供的几个基本组件。这是从“用工具”到“懂原理”的关键一步。Activity活动它是用户界面的容器。我们的应用至少需要一个主Activity来承载整个任务列表界面。你可以把它理解成一个“屏幕”或一个“页面”。RecyclerView回收视图这是显示任务列表的绝对核心控件。比起古老的ListViewRecyclerView在性能和灵活性上都是现代安卓开发的首选。它负责高效地回收和复用列表项视图即使有上百条任务也能流畅滚动。这也是很多“Android Studio recyclerview是什么”搜索背后的实际需求。Adapter适配器它是RecyclerView和你的数据任务列表之间的桥梁。Adapter告诉RecyclerView1列表有多少项2每一项长什么样如何创建视图3如何将数据绑定到对应的视图上。编写自定义Adapter是掌握列表视图的必修课。布局文件XML用来定义Activity和每个列表项Item的视觉结构。比如按钮在哪输入框在哪列表项里是纯文字还是文字加复选框。Android Studio的可视化编辑器Design和代码编辑器Code可以帮你完成这部分。数据存储任务数据需要持久化否则应用一关闭就全没了。对于简易项目我们有两种主流选择SharedPreferences以键值对形式存储简单数据。适合存储配置、用户偏好。对于结构简单的任务列表比如只有字符串标题和布尔值完成状态也可以用它但管理起来稍显笨拙。Room数据库这是Android官方推荐的SQLite对象映射库。它更规范适合存储结构化的列表数据。虽然学习曲线比SharedPreferences稍陡但它是未来开发更复杂应用的基石。考虑到这是我们第一个“像样”的项目我强烈建议挑战一下Room它会让你对数据层的理解更深。基于以上分析我们的技术栈就明确了一个Activity RecyclerView Adapter Room数据库。这个组合拳足以应对大量基础型应用的需求。3. 环境搭建与项目创建避开那些“理所当然”的坑我知道你可能已经按照“android studio安装教程”装好了软件。但安装成功和创建一个可稳定开发的项目之间还有一段距离。这里我分享几个比教程更重要的实操细节。3.1 创建项目时的关键抉择打开Android Studio点击“New Project”。这里你会看到很多模板如“Empty Views Activity”、“Basic Views Activity”等。对于初学者我建议选择“Empty Views Activity”。它最干净只生成一个最简单的Activity和相关布局没有多余的、你可能暂时看不懂的代码如某些模板自带的导航组件让你能从零开始搭建理解每一行代码的来意。在配置项目Configure Your Project的页面注意以下几点Name给你的应用起个名字如“MyTodo”。Package name包名通常是域名倒写应用名如com.example.mytodo。这是应用的唯一标识上架应用市场时很重要。Save location保存路径不要包含中文或空格这是很多奇怪错误的源头。Language选择Kotlin。虽然你可能会搜索到大量Java的老教程但Kotlin已是安卓开发的官方首选语言更简洁、安全新项目没有理由不用它。这也是“kotlin安卓开发”成为热词的原因。Minimum SDK选择API 24: Android 7.0 (Nougat)或类似稍高的版本。这能保证你的应用可以使用较新的API同时又能覆盖市面上绝大多数设备。不必为了追求极致的兼容性而选择太老的API否则你会错过很多好用的新特性。点击“Finish”后Android Studio会开始构建项目。这里你会遇到第一个可能的“坑”Gradle构建过程。3.2 解决Gradle构建与依赖下载的“老大难”问题构建过程可能会很慢甚至失败最常见的原因是Gradle包装器或依赖包下载超时。这对应了热词中的“android studio gradle包下载换地址”。为什么慢默认的仓库地址jcenter()、google()在国内访问可能不稳定。怎么办修改项目级的build.gradle.kts注意是项目级不是模块级文件中的仓库配置。将repositories块内容替换为使用国内镜像源这是最有效的加速手段。// 在项目级的 build.gradle.kts 文件中 buildscript { repositories { // 阿里云镜像仓库加速依赖下载 maven { url uri(https://maven.aliyun.com/repository/public/) } maven { url uri(https://maven.aliyun.com/repository/google/) } maven { url uri(https://maven.aliyun.com/repository/gradle-plugin/) } mavenCentral() google() } dependencies { classpath(com.android.tools.build:gradle:8.1.0) // 版本号请与你的AS匹配 classpath(org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.0) } } // 同样在 allprojects 的 repositories 块中也进行类似修改 allprojects { repositories { maven { url uri(https://maven.aliyun.com/repository/public/) } maven { url uri(https://maven.aliyun.com/repository/google/) } mavenCentral() google() } }修改后点击右上角出现的“Sync Now”同步项目。如果还遇到特定依赖下载失败可以尝试在Android Studio设置中File - Settings - Appearance Behavior - System Settings - HTTP Proxy配置网络代理或者使用“android studio 手动添加haxm sdk源”这类思路但修改仓库镜像是最通用的解决方案。注意构建过程中Android Studio会下载对应版本的Gradle发行版。这个过程通常只需要一次耐心等待即可。如果长时间卡住可以检查网络或手动从Gradle官网下载对应版本的发行版放入C:\Users\你的用户名\.gradle\wrapper\dists目录下Windows。4. 从零构建数据层用Room实现持久化存储数据是应用的核心。我们先从底层的数据模型和数据库开始搭建这样上层UI在开发时就有真实数据可以对接。4.1 定义数据实体Entity实体Entity代表数据库中的一张表。我们的待办事项Task就是一个实体。在app/src/main/java/你的包名/目录下新建一个data包用于存放所有数据相关类。在data包下新建Kotlin类文件命名为Task.kt。// Task.kt import androidx.room.Entity import androidx.room.PrimaryKey Entity(tableName tasks) // 指定表名为“tasks” data class Task( PrimaryKey(autoGenerate true) // 主键且自动生成 val id: Long 0L, val title: String, val isCompleted: Boolean false, val createdAt: Long System.currentTimeMillis() // 创建时间戳 )Entity注解将这个数据类标记为数据库表。PrimaryKey定义了主键autoGenerate true表示id由数据库自动分配我们不用管。4.2 创建数据访问对象DAODAOData Access Object是一个接口里面定义了操作数据库的方法比如增删改查。Room会在编译时为我们自动生成这个接口的实现。在data包下新建Kotlin接口文件命名为TaskDao.kt。// TaskDao.kt import androidx.room.* Dao interface TaskDao { Query(SELECT * FROM tasks ORDER BY createdAt DESC) // 查询所有任务按创建时间倒序 suspend fun getAllTasks(): ListTask Insert(onConflict OnConflictStrategy.REPLACE) // 插入或替换 suspend fun insertTask(task: Task) Update suspend fun updateTask(task: Task) // 用于更新任务状态如标记完成 Delete suspend fun deleteTask(task: Task) }这里使用了Kotlin的suspend关键字意味着这些是挂起函数需要在协程中调用。这是Room对协程的良好支持使得数据库操作不会阻塞主线程。4.3 定义数据库Database这是Room数据库的入口需要定义一个抽象类来继承RoomDatabase。在data包下新建Kotlin抽象类文件命名为AppDatabase.kt。// AppDatabase.kt import androidx.room.Database import androidx.room.Room import androidx.room.RoomDatabase import android.content.Context Database(entities [Task::class], version 1, exportSchema false) abstract class AppDatabase : RoomDatabase() { abstract fun taskDao(): TaskDao // 单例模式防止重复打开数据库 companion object { Volatile private var INSTANCE: AppDatabase? null fun getDatabase(context: Context): AppDatabase { return INSTANCE ?: synchronized(this) { val instance Room.databaseBuilder( context.applicationContext, AppDatabase::class.java, todo_database // 数据库文件名 ).build() INSTANCE instance instance } } } }Database注解中列出了所有的实体类目前只有Task和数据库版本。exportSchema false可以简化初始流程避免Schema导出相关的配置。getDatabase方法使用了单例模式确保整个应用生命周期内只有一个数据库实例。4.4 添加Room依赖要让以上代码生效需要在模块级build.gradle.ktsapp/build.gradle.kts文件的dependencies块中添加Room库的依赖。// app/build.gradle.kts dependencies { // ... 其他依赖 val room_version 2.5.0 // 使用一个稳定的版本 implementation(androidx.room:room-runtime:$room_version) kapt(androidx.room:room-compiler:$room_version) // Kotlin注解处理器 implementation(androidx.room:room-ktx:$room_version) // 对Kotlin协程的支持 }添加后点击“Sync Now”同步。注意这里使用了kaptKotlin注解处理器而不是Java的annotationProcessor。这是使用Kotlin开发时的一个关键区别。5. 构建用户界面Activity、布局与RecyclerView数据层准备好了现在我们来构建用户能看到和交互的界面。5.1 设计主界面布局activity_main.xml打开res/layout/activity_main.xml文件切换到“Code”视图我们将用代码来定义布局这比拖拽控件更能理解其结构。?xml version1.0 encodingutf-8? LinearLayout xmlns:androidhttp://schemas.android.com/apk/res/android xmlns:apphttp://schemas.android.com/apk/res-auto xmlns:toolshttp://schemas.android.com/tools android:layout_widthmatch_parent android:layout_heightmatch_parent android:orientationvertical tools:context.MainActivity !-- 顶部添加任务的输入区域 -- LinearLayout android:layout_widthmatch_parent android:layout_heightwrap_content android:orientationhorizontal android:padding16dp EditText android:idid/editTextTask android:layout_width0dp android:layout_heightwrap_content android:layout_weight1 android:hint输入新任务... android:inputTypetext / Button android:idid/buttonAdd android:layout_widthwrap_content android:layout_heightwrap_content android:layout_marginStart8dp android:text添加 / /LinearLayout !-- 中间任务列表 -- androidx.recyclerview.widget.RecyclerView android:idid/recyclerViewTasks android:layout_widthmatch_parent android:layout_height0dp android:layout_weight1 / !-- 底部一个简单的操作提示可选 -- TextView android:layout_widthmatch_parent android:layout_heightwrap_content android:gravitycenter android:padding8dp android:text长按任务可删除 android:textColor?android:attr/textColorSecondary / /LinearLayout这个布局使用垂直的LinearLayout。顶部是一个水平布局包含一个用于输入的EditText和一个“添加”按钮。中间是核心的RecyclerViewlayout_weight1让它占据剩余的所有空间。底部是一个提示文本。5.2 创建列表项布局item_task.xml列表中的每一项一个任务也需要自己的布局。在res/layout/目录上右键选择New - Layout Resource File文件名填item_task根元素Root element可以先选LinearLayout创建后再修改。?xml version1.0 encodingutf-8? LinearLayout xmlns:androidhttp://schemas.android.com/apk/res/android android:idid/itemLayout android:layout_widthmatch_parent android:layout_heightwrap_content android:orientationhorizontal android:padding16dp android:background?android:attr/selectableItemBackground !-- 给项添加点击水波纹效果 -- CheckBox android:idid/checkBoxCompleted android:layout_widthwrap_content android:layout_heightwrap_content android:layout_gravitycenter_vertical / TextView android:idid/textViewTaskTitle android:layout_width0dp android:layout_heightwrap_content android:layout_weight1 android:layout_gravitycenter_vertical android:layout_marginStart16dp android:textSize18sp / /LinearLayout每一项左边是一个CheckBox用于标记完成状态右边是一个TextView显示任务标题。整个项的外层布局设置了点击反馈背景。5.3 实现RecyclerView的适配器Adapter这是连接数据和视图的关键。在app/src/main/java/你的包名/目录下新建一个ui包或adapter包然后新建Kotlin类文件TaskAdapter.kt。// TaskAdapter.kt import android.view.LayoutInflater import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView import com.example.mytodo.databinding.ItemTaskBinding // 注意这个类会自动生成 class TaskAdapter( private var tasks: ListTask emptyList(), private val onTaskCheckedChange: (Task, Boolean) - Unit, // 复选框状态改变回调 private val onTaskLongClick: (Task) - Unit // 长按回调 ) : RecyclerView.AdapterTaskAdapter.TaskViewHolder() { // 更新整个列表数据 fun submitList(newTasks: ListTask) { tasks newTasks notifyDataSetChanged() // 通知适配器数据已变更刷新整个列表 // 注意在复杂列表中建议使用DiffUtil进行高效局部更新此处为简化。 } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TaskViewHolder { val binding ItemTaskBinding.inflate(LayoutInflater.from(parent.context), parent, false) return TaskViewHolder(binding) } override fun onBindViewHolder(holder: TaskViewHolder, position: Int) { val task tasks[position] holder.bind(task) } override fun getItemCount(): Int tasks.size inner class TaskViewHolder(private val binding: ItemTaskBinding) : RecyclerView.ViewHolder(binding.root) { init { // 为整个项设置长按监听器 binding.itemLayout.setOnLongClickListener { val position adapterPosition if (position ! RecyclerView.NO_POSITION) { onTaskLongClick(tasks[position]) true // 表示已消费该事件 } else { false } } } fun bind(task: Task) { binding.textViewTaskTitle.text task.title binding.checkBoxCompleted.isChecked task.isCompleted // 根据完成状态设置文字样式例如划掉效果 if (task.isCompleted) { binding.textViewTaskTitle.paintFlags android.graphics.Paint.STRIKE_THRU_TEXT_FLAG } else { binding.textViewTaskTitle.paintFlags 0 } // 设置复选框监听器避免重复绑定导致的问题 binding.checkBoxCompleted.setOnCheckedChangeListener(null) // 先清空旧的监听器 binding.checkBoxCompleted.setOnCheckedChangeListener { _, isChecked - // 只有当用户交互导致的改变才触发回调 if (binding.checkBoxCompleted.isPressed) { onTaskCheckedChange(task, isChecked) } } } } }这段代码有几个关键点视图绑定ViewBinding我们使用了ItemTaskBinding。这是Android Studio的一个功能它能根据XML布局文件自动生成一个绑定类让我们能安全、直接地访问布局中的视图避免了繁琐的findViewById。要启用它需确保模块级build.gradle.kts中android块内配置了buildFeatures { viewBinding true }。回调函数Adapter只负责显示和视图交互当复选框状态改变或长按时它通过回调函数onTaskCheckedChange,onTaskLongClick通知外部通常是Activity去处理实际的业务逻辑更新数据库。这符合单一职责原则。notifyDataSetChanged这是一个简单但低效的更新方式。对于学习第一个项目来说没问题。在实际项目中当列表数据变化复杂时应使用DiffUtil来精确计算差异并高效更新。6. 串联一切在Activity中实现业务逻辑最后我们需要在MainActivity.kt中将数据库、界面和适配器连接起来并处理用户的交互。6.1 初始化组件与观察数据// MainActivity.kt import android.os.Bundle import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.LinearLayoutManager import com.example.mytodo.data.AppDatabase import com.example.mytodo.data.Task import com.example.mytodo.databinding.ActivityMainBinding import com.example.mytodo.ui.TaskAdapter import kotlinx.coroutines.launch class MainActivity : AppCompatActivity() { private lateinit var binding: ActivityMainBinding private lateinit var taskAdapter: TaskAdapter private lateinit var database: AppDatabase override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) // 1. 初始化数据库 database AppDatabase.getDatabase(this) // 2. 初始化RecyclerView和Adapter taskAdapter TaskAdapter( onTaskCheckedChange { task, isChecked - // 当复选框状态改变时更新数据库 lifecycleScope.launch { database.taskDao().updateTask(task.copy(isCompleted isChecked)) // 注意这里更新后需要重新从数据库加载数据并刷新列表。 // 更优的做法是在ViewModel中使用LiveData/StateFlow自动观察数据变化此处为简化流程。 loadTasks() } }, onTaskLongClick { task - // 长按任务时弹出删除确认对话框 showDeleteDialog(task) } ) binding.recyclerViewTasks.apply { layoutManager LinearLayoutManager(thisMainActivity) adapter taskAdapter } // 3. 设置“添加”按钮的点击事件 binding.buttonAdd.setOnClickListener { val taskTitle binding.editTextTask.text.toString().trim() if (taskTitle.isNotEmpty()) { lifecycleScope.launch { database.taskDao().insertTask(Task(title taskTitle)) binding.editTextTask.text.clear() // 清空输入框 loadTasks() // 重新加载任务列表 } } } // 4. 初始加载任务列表 loadTasks() } private fun loadTasks() { lifecycleScope.launch { val tasks database.taskDao().getAllTasks() taskAdapter.submitList(tasks) } } private fun showDeleteDialog(task: Task) { AlertDialog.Builder(this) .setTitle(删除任务) .setMessage(确定要删除“${task.title}”吗) .setPositiveButton(删除) { _, _ - lifecycleScope.launch { database.taskDao().deleteTask(task) loadTasks() } } .setNegativeButton(取消, null) .show() } }6.2 关键逻辑与避坑点解析生命周期协程lifecycleScope所有数据库操作都是挂起函数必须在协程中调用。lifecycleScope是Activity生命周期感知的协程作用域当Activity销毁时它会自动取消所有在其中启动的协程避免内存泄漏。这是处理后台任务的最佳实践之一。数据更新与UI刷新我们的代码中每次数据库操作增、删、改后都手动调用了loadTasks()重新查询并刷新列表。这种方式在简单场景下可行但不够优雅。更推荐的做法是使用LiveData或Flow。在DAO中将查询方法的返回类型改为LiveDataListTask或FlowListTask然后在Activity中观察这个数据源。一旦数据库中的数据发生变化观察者会自动收到新数据并更新UI无需手动刷新。这是MVVM架构的核心思想建议作为下一步学习重点。主线程安全Room默认不允许在主线程UI线程上执行数据库操作因为耗时操作会阻塞UI导致应用无响应ANR。我们的代码在协程中执行而Room的suspend函数会确保操作在后台线程进行。如果你看到“Cannot access database on the main thread”这样的错误就是因为违反了这条规则。视图绑定ActivityMainBinding和Adapter里一样我们在Activity中也使用了视图绑定它比findViewById更安全、简洁。7. 运行、测试与迭代让应用“活”起来代码编写完成点击Android Studio工具栏上的绿色运行按钮或ShiftF10。在弹出的设备选择器中你可以连接真机需开启开发者选项和USB调试或者使用AVD管理器创建一个安卓虚拟机对应热词“安卓模拟器”、“android studio虚拟化设置”。7.1 基础功能测试应用安装运行后请按顺序测试以下核心流程添加任务在输入框输入文字点击“添加”按钮。列表应立即显示新任务。标记完成点击任务项左侧的复选框任务标题应出现删除线。再次点击取消勾选删除线消失。删除任务长按任意一个任务项应弹出确认对话框点击“删除”后该任务从列表中消失。数据持久化退出应用从最近任务中划掉然后重新打开。之前添加的任务应该还在。这说明Room数据库正常工作。7.2 常见问题排查踩坑实录即使代码逻辑正确第一次运行时也可能遇到问题。这里分享几个高频坑点应用崩溃Cannot access database on the main thread原因在onCreate或按钮点击事件等主线程上下文中直接调用了database.taskDao().getAllTasks()这类挂起函数但没有用协程包裹。解决确保所有数据库操作都在lifecycleScope.launch { ... }块内执行。列表不显示/空白原因1RecyclerView没有设置LayoutManager。LayoutManager负责决定列表项的布局方式线性、网格等。解决检查onCreate中是否写了binding.recyclerViewTasks.layoutManager LinearLayoutManager(this)。原因2Adapter的数据源tasks初始为空且后续没有正确更新。解决在loadTasks()方法中通过taskAdapter.submitList(tasks)更新数据后确认tasks列表确实从数据库查到了数据。可以在loadTasks协程里加一句日志打印tasks.size。复选框状态错乱点击一项其他项也变化原因这是RecyclerView的视图复用机制导致的经典问题。当滚动时一个被复用的项视图ViewHolder可能还保留着之前绑定的数据状态。解决在我们的TaskAdapter.bind()方法中每次绑定数据时都必须完整地设置视图的所有状态。我们正是这样做的先setOnCheckedChangeListener(null)清空旧监听器再设置新监听器同时根据task.isCompleted来设置文字样式和复选框状态。确保bind方法覆盖了视图的所有可变属性。构建失败Unresolved reference: ItemTaskBinding原因视图绑定类需要项目成功编译后才会生成。如果XML布局文件有错误或者你刚创建item_task.xml就立刻在代码中引用ItemTaskBinding可能会报错。解决尝试点击菜单栏的Build - Rebuild Project。确保所有XML文件语法正确。7.3 功能迭代与扩展思路当基础版本稳定运行后你可以尝试添加新功能来巩固和扩展技能任务编辑点击任务项短按弹出一个对话框里面预填了当前任务标题修改后保存。这需要修改Adapter添加短按回调并在Activity中实现一个编辑对话框调用DAO的updateTask方法。数据驱动UIMVVM引入ViewModel和LiveData/StateFlow。将数据库操作和任务列表数据放到ViewModel中Activity只负责观察数据变化并更新UI。这会让代码更清晰且能更好地处理屏幕旋转等配置变更。任务分类为Task实体增加一个category字段。在添加任务时可以让用户选择或输入分类。在UI上可以增加一个筛选器如Spinner下拉框来按分类显示任务。界面美化学习使用Material Components库将按钮、卡片、颜色主题等替换成更现代化的Material Design组件。通过这个“简易”的待办清单项目你实际上已经走过了安卓应用开发一个最核心的流程定义数据模型、创建数据库、设计用户界面、处理用户交互、实现数据持久化。这个过程里遇到的每一个问题和解决方案都比单纯阅读文档印象更深。下次当你想做一个“简易”的笔记App、购物清单或者任何列表类应用时你会发现骨架已经在这里了你需要做的只是更换“皮囊”和添加更丰富的“血肉”。这才是从“运行示例项目”到“创造自己项目”的真正开始。本文还有配套的精品资源点击获取