简介:这是一份面向Android开发初学者与入门实践者的简易项目实战资源,基于Android Studio完整实现用户登录、注册、本地数据存储(SQLite)、TabLayout+ViewPager选项卡切换、表单信息采集(含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(回收视图):这是显示任务列表的绝对核心控件。比起古老的ListView,RecyclerView在性能和灵活性上都是现代安卓开发的首选。它负责高效地回收和复用列表项视图,即使有上百条任务也能流畅滚动。这也是很多“Android Studio recyclerview是什么”搜索背后的实际需求。
- Adapter(适配器):它是RecyclerView和你的数据(任务列表)之间的桥梁。Adapter告诉RecyclerView:1)列表有多少项;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 创建数据访问对象(DAO)
DAO(Data 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(): List<Task> @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.kts(app/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”同步。注意,这里使用了kapt(Kotlin注解处理器),而不是Java的annotationProcessor。这是使用Kotlin开发时的一个关键区别。
5. 构建用户界面:Activity、布局与RecyclerView
数据层准备好了,现在我们来构建用户能看到和交互的界面。
5.1 设计主界面布局(activity_main.xml)
打开res/layout/activity_main.xml文件,切换到“Code”视图,我们将用代码来定义布局,这比拖拽控件更能理解其结构。
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".MainActivity"> <!-- 顶部:添加任务的输入区域 --> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:padding="16dp"> <EditText android:id="@+id/editTextTask" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:hint="输入新任务..." android:inputType="text" /> <Button android:id="@+id/buttonAdd" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="8dp" android:text="添加" /> </LinearLayout> <!-- 中间:任务列表 --> <androidx.recyclerview.widget.RecyclerView android:id="@+id/recyclerViewTasks" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" /> <!-- 底部:一个简单的操作提示(可选) --> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center" android:padding="8dp" android:text="长按任务可删除" android:textColor="?android:attr/textColorSecondary" /> </LinearLayout>这个布局使用垂直的LinearLayout。顶部是一个水平布局,包含一个用于输入的EditText和一个“添加”按钮。中间是核心的RecyclerView,layout_weight="1"让它占据剩余的所有空间。底部是一个提示文本。
5.2 创建列表项布局(item_task.xml)
列表中的每一项(一个任务)也需要自己的布局。在res/layout/目录上右键,选择New -> Layout Resource File,文件名填item_task,根元素(Root element)可以先选LinearLayout,创建后再修改。
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/itemLayout" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:padding="16dp" android:background="?android:attr/selectableItemBackground"> <!-- 给项添加点击水波纹效果 --> <CheckBox android:id="@+id/checkBoxCompleted" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" /> <TextView android:id="@+id/textViewTaskTitle" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:layout_gravity="center_vertical" android:layout_marginStart="16dp" android:textSize="18sp" /> </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: List<Task> = emptyList(), private val onTaskCheckedChange: (Task, Boolean) -> Unit, // 复选框状态改变回调 private val onTaskLongClick: (Task) -> Unit // 长按回调 ) : RecyclerView.Adapter<TaskAdapter.TaskViewHolder>() { // 更新整个列表数据 fun submitList(newTasks: List<Task>) { 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(this@MainActivity) 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中,将查询方法的返回类型改为LiveData<List<Task>>或Flow<List<Task>>,然后在Activity中观察这个数据源。一旦数据库中的数据发生变化,观察者会自动收到新数据并更新UI,无需手动刷新。这是MVVM架构的核心思想,建议作为下一步学习重点。 - 主线程安全:Room默认不允许在主线程(UI线程)上执行数据库操作,因为耗时操作会阻塞UI,导致应用无响应(ANR)。我们的代码在协程中执行,而Room的
suspend函数会确保操作在后台线程进行。如果你看到“Cannot access database on the main thread”这样的错误,就是因为违反了这条规则。 - 视图绑定(ActivityMainBinding):和Adapter里一样,我们在Activity中也使用了视图绑定,它比
findViewById更安全、简洁。
7. 运行、测试与迭代:让应用“活”起来
代码编写完成,点击Android Studio工具栏上的绿色运行按钮(或Shift+F10)。在弹出的设备选择器中,你可以连接真机(需开启开发者选项和USB调试),或者使用AVD管理器创建一个安卓虚拟机(对应热词“安卓模拟器”、“android studio虚拟化设置”)。
7.1 基础功能测试
应用安装运行后,请按顺序测试以下核心流程:
- 添加任务:在输入框输入文字,点击“添加”按钮。列表应立即显示新任务。
- 标记完成:点击任务项左侧的复选框,任务标题应出现删除线。再次点击取消勾选,删除线消失。
- 删除任务:长按任意一个任务项,应弹出确认对话框,点击“删除”后,该任务从列表中消失。
- 数据持久化:退出应用(从最近任务中划掉),然后重新打开。之前添加的任务应该还在。这说明Room数据库正常工作。
7.2 常见问题排查(踩坑实录)
即使代码逻辑正确,第一次运行时也可能遇到问题。这里分享几个高频坑点:
应用崩溃:
Cannot access database on the main thread- 原因:在
onCreate或按钮点击事件等主线程上下文中,直接调用了database.taskDao().getAllTasks()这类挂起函数,但没有用协程包裹。 - 解决:确保所有数据库操作都在
lifecycleScope.launch { ... }块内执行。
- 原因:在
列表不显示/空白
- 原因1:
RecyclerView没有设置LayoutManager。LayoutManager负责决定列表项的布局方式(线性、网格等)。 - 解决:检查
onCreate中是否写了binding.recyclerViewTasks.layoutManager = LinearLayoutManager(this)。 - 原因2:Adapter的数据源
tasks初始为空,且后续没有正确更新。 - 解决:在
loadTasks()方法中,通过taskAdapter.submitList(tasks)更新数据后,确认tasks列表确实从数据库查到了数据。可以在loadTasks协程里加一句日志打印tasks.size。
- 原因1:
复选框状态错乱(点击一项,其他项也变化)
- 原因:这是RecyclerView的视图复用机制导致的经典问题。当滚动时,一个被复用的项视图(ViewHolder)可能还保留着之前绑定的数据状态。
- 解决:在我们的
TaskAdapter.bind()方法中,每次绑定数据时,都必须完整地设置视图的所有状态。我们正是这样做的:先setOnCheckedChangeListener(null)清空旧监听器,再设置新监听器;同时根据task.isCompleted来设置文字样式和复选框状态。确保bind方法覆盖了视图的所有可变属性。
构建失败:
Unresolved reference: ItemTaskBinding- 原因:视图绑定类需要项目成功编译后才会生成。如果XML布局文件有错误,或者你刚创建
item_task.xml就立刻在代码中引用ItemTaskBinding,可能会报错。 - 解决:尝试点击菜单栏的
Build -> Rebuild Project。确保所有XML文件语法正确。
- 原因:视图绑定类需要项目成功编译后才会生成。如果XML布局文件有错误,或者你刚创建
7.3 功能迭代与扩展思路
当基础版本稳定运行后,你可以尝试添加新功能来巩固和扩展技能:
- 任务编辑:点击任务项(短按),弹出一个对话框,里面预填了当前任务标题,修改后保存。这需要修改Adapter,添加短按回调,并在Activity中实现一个编辑对话框,调用DAO的
updateTask方法。 - 数据驱动UI(MVVM):引入
ViewModel和LiveData/StateFlow。将数据库操作和任务列表数据放到ViewModel中,Activity只负责观察数据变化并更新UI。这会让代码更清晰,且能更好地处理屏幕旋转等配置变更。 - 任务分类:为
Task实体增加一个category字段。在添加任务时,可以让用户选择或输入分类。在UI上,可以增加一个筛选器(如Spinner下拉框)来按分类显示任务。 - 界面美化:学习使用
Material Components库,将按钮、卡片、颜色主题等替换成更现代化的Material Design组件。
通过这个“简易”的待办清单项目,你实际上已经走过了安卓应用开发一个最核心的流程:定义数据模型、创建数据库、设计用户界面、处理用户交互、实现数据持久化。这个过程里遇到的每一个问题和解决方案,都比单纯阅读文档印象更深。下次当你想做一个“简易”的笔记App、购物清单或者任何列表类应用时,你会发现骨架已经在这里了,你需要做的只是更换“皮囊”和添加更丰富的“血肉”。这才是从“运行示例项目”到“创造自己项目”的真正开始。
本文还有配套的精品资源,点击获取