以下将为你创建一个基于 Android Studio 的简单 待办事项(Todo List) 项目实例,包含基础的增、删、查功能,适合入门学习。项目将使用 RecyclerView 展示列表,Room
/ 路径:app/src/main/java/com/example/todolist/data/TodoDatabase.kt。// 路径:app/src/main/java/com/example/todolist/view/MainActivity.kt。// 路径:app/src/main/java/com/example/todolist/data/TodoDao.kt。// 路径:ap
一、项目准备
1. 环境配置
• Android Studio 版本:建议 Arctic Fox 及以上(确保支持 Jetpack 组件)
• Gradle 配置:在 app/build.gradle 中添加依赖(同步后生效):
// 基础组件
implementation 'androidx.core:core-ktx:1.9.0'
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.11.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
// RecyclerView(列表展示)
implementation 'androidx.recyclerview:recyclerview:1.3.2'
// Room(本地数据库)
implementation 'androidx.room:room-runtime:2.5.2'
kapt 'androidx.room:room-compiler:2.5.2'
implementation 'androidx.room:room-ktx:2.5.2' // Kotlin 扩展
// ViewModel + LiveData(MVVM 架构)
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.6.2'
implementation 'androidx.lifecycle:lifecycle-viewmodel-savedstate:2.6.2'
// Kotlin 协程(异步操作)
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3'
二、数据模型设计(Entity)
创建 Todo 数据类,对应数据库表结构:
// 路径:app/src/main/java/com/example/todolist/model/Todo.kt
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "todo_table") // 数据库表名
data class Todo(
@PrimaryKey(autoGenerate = true) val id: Int = 0, // 自增主键
val title: String, // 待办事项标题
val isCompleted: Boolean = false // 是否完成(默认未完成)
)
三、本地数据库(Room)
1. 数据访问接口(DAO)
定义对数据库的操作(增、删、查):
// 路径:app/src/main/java/com/example/todolist/data/TodoDao.kt
import androidx.lifecycle.LiveData
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query
import kotlinx.coroutines.flow.Flow
@Dao
interface TodoDao {
// 查询所有待办事项(按 ID 升序),返回 Flow(实时监听数据变化)
@Query("SELECT * FROM todo_table ORDER BY id ASC")
fun getAllTodos(): Flow<List<Todo>>
// 插入单个待办事项
@Insert
suspend fun insertTodo(todo: Todo)
// 删除单个待办事项
@Delete
suspend fun deleteTodo(todo: Todo)
}
2. 数据库实例(Database)
创建数据库单例,关联表和 DAO:
// 路径:app/src/main/java/com/example/todolist/data/TodoDatabase.kt
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import android.content.Context
// 数据库版本号(升级时需递增)
@Database(entities = [Todo::class], version = 1, exportSchema = false)
abstract class TodoDatabase : RoomDatabase() {
// 暴露 DAO 接口
abstract fun todoDao(): TodoDao
// 单例模式(避免重复创建数据库实例)
companion object {
@Volatile
private var INSTANCE: TodoDatabase? = null
fun getDatabase(context: Context): TodoDatabase {
return INSTANCE ?: synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
TodoDatabase::class.java,
"todo_database" // 数据库文件名
).build()
INSTANCE = instance
instance
}
}
}
}
四、业务逻辑(Repository)
统一数据操作入口,隔离数据库与ViewModel:
// 路径:app/src/main/java/com/example/todolist/repository/TodoRepository.kt
import androidx.lifecycle.LiveData
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
class TodoRepository(private val todoDao: TodoDao) {
// 从 DAO 获取所有待办事项(通过 Flow 实时更新)
val allTodos: Flow<List<Todo>> = todoDao.getAllTodos()
// 插入待办事项(协程中执行,避免主线程阻塞)
suspend fun insert(todo: Todo) = withContext(Dispatchers.IO) {
todoDao.insertTodo(todo)
}
// 删除待办事项(协程中执行)
suspend fun delete(todo: Todo) = withContext(Dispatchers.IO) {
todoDao.deleteTodo(todo)
}
}
五、视图模型(ViewModel)
处理UI逻辑,关联Repository与界面:
// 路径:app/src/main/java/com/example/todolist/viewmodel/TodoViewModel.kt
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.launch
class TodoViewModel(private val repository: TodoRepository) : ViewModel() {
// 暴露所有待办事项(供UI观察)
val allTodos: Flow<List<Todo>> = repository.allTodos
// 添加待办事项(调用Repository,在协程中执行)
fun insertTodo(title: String) {
if (title.isNotBlank()) { // 避免空内容
viewModelScope.launch {
repository.insert(Todo(title = title))
}
}
}
// 删除待办事项
fun deleteTodo(todo: Todo) {
viewModelScope.launch {
repository.delete(todo)
}
}
}
// 用于创建ViewModel(关联Repository和Database)
class TodoViewModelFactory(private val repository: TodoRepository) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(TodoViewModel::class.java)) {
@Suppress("UNCHECKED_CAST")
return TodoViewModel(repository) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}
六、界面设计(UI)
1. 主布局(activity_main.xml)
包含输入框、添加按钮和RecyclerView列表:
<!-- 路径:app/src/main/res/layout/activity_main.xml -->
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
tools:context=".view.MainActivity">
<!-- 输入区域:输入框 + 添加按钮 -->
<LinearLayout
android:id="@+id/inputLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="16dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<EditText
android:id="@+id/etTodoTitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:hint="输入待办事项..."
android:inputType="textCapSentences"/>
<Button
android:id="@+id/btnAdd"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:text="添加"/>
</LinearLayout>
<!-- 待办事项列表 -->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvTodoList"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintTop_toBottomOf="@id/inputLayout"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
tools:listitem="@layout/item_todo"/>
</androidx.constraintlayout.widget.ConstraintLayout>
2. 列表项布局(item_todo.xml)
每个待办事项的布局(标题 + 删除按钮):
<!-- 路径:app/src/main/res/layout/item_todo.xml -->
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="16dp"
android:gravity="center_vertical">
<!-- 待办事项标题(如果完成,显示中划线) -->
<TextView
android:id="@+id/tvTodoTitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textSize="16sp"
android:textStyle="bold"/>
<!-- 删除按钮 -->
<Button
android:id="@+id/btnDelete"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="删除"
android:backgroundTint="@color/red"/>
</LinearLayout>
七、列表适配器(RecyclerView.Adapter)
连接数据与列表项视图:
// 路径:app/src/main/java/com/example/todolist/adapter/TodoAdapter.kt
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.example.todolist.databinding.ItemTodoBinding
import com.example.todolist.model.Todo
// 利用 DiffUtil 优化列表更新(只刷新变化的项)
class TodoAdapter(private val onDeleteClick: (Todo) -> Unit) :
ListAdapter<Todo, TodoAdapter.TodoViewHolder>(TodoDiffCallback()) {
// 视图持有者(绑定列表项视图)
inner class TodoViewHolder(private val binding: ItemTodoBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(todo: Todo) {
binding.tvTodoTitle.text = todo.title
// 如果待办事项已完成,添加中划线
binding.tvTodoTitle.paint.isStrikeThruText = todo.isCompleted
// 删除按钮点击事件
binding.btnDelete.setOnClickListener {
onDeleteClick(todo)
}
}
}
// 创建视图持有者(加载列表项布局)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TodoViewHolder {
val binding = ItemTodoBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
return TodoViewHolder(binding)
}
// 绑定数据到视图
override fun onBindViewHolder(holder: TodoViewHolder, position: Int) {
holder.bind(getItem(position))
}
// DiffUtil 回调(判断数据是否变化)
class TodoDiffCallback : DiffUtil.ItemCallback<Todo>() {
override fun areItemsTheSame(oldItem: Todo, newItem: Todo): Boolean {
return oldItem.id == newItem.id // 按 ID 判断是否为同一 item
}
override fun areContentsTheSame(oldItem: Todo, newItem: Todo): Boolean {
return oldItem == newItem // 按内容判断是否变化
}
}
}
八、主界面逻辑(MainActivity)
关联ViewModel与UI,处理用户交互:
// 路径:app/src/main/java/com/example/todolist/view/MainActivity.kt
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.LinearLayoutManager
import com.example.todolist.adapter.TodoAdapter
import com.example.todolist.data.TodoDatabase
import com.example.todolist.databinding.ActivityMainBinding
import com.example.todolist.repository.TodoRepository
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private lateinit var todoViewModel: TodoViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 初始化视图绑定
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
// 初始化 Repository(关联数据库)
val todoDao = TodoDatabase.getDatabase(this).todoDao()
val repository = TodoRepository(todoDao)
// 初始化 ViewModel(通过 Factory 关联 Repository)
val viewModelFactory = TodoViewModelFactory(repository)
todoViewModel = ViewModelProvider(this, viewModelFactory)[TodoViewModel::class.java]
// 初始化适配器(设置删除点击事件)
val adapter = TodoAdapter { todo ->
todoViewModel.deleteTodo(todo)
}
// 配置 RecyclerView(线性布局)
binding.rvTodoList.adapter = adapter
binding.rvTodoList.layoutManager = LinearLayoutManager(this)
// 观察数据变化(当数据库数据更新时,自动刷新列表)
todoViewModel.allTodos.observe(this) { todos ->
adapter.submitList(todos)
}
// 添加按钮点击事件(获取输入内容并添加到数据库)
binding.btnAdd.setOnClickListener {
val title = binding.etTodoTitle.text.toString()
todoViewModel.insertTodo(title)
binding.etTodoTitle.text.clear() // 清空输入框
}
}
}
九、运行效果
1. 启动应用后,界面显示输入框和“添加”按钮,下方为空白列表。
2. 在输入框中输入内容(如“学习Android”),点击“添加”,列表会实时显示该待办事项。
3. 点击列表项右侧的“删除”按钮,对应的待办事项会从列表
更多推荐
所有评论(0)