TODO 待办事项应用实现步骤
一、创建项目与配置
1. 打开 Android Studio,选择 "Empty Activity",项目名称设为 "TodoApp",包名默认,语言选择 Kotlin,最小 SDK 选 API 21(兼容 Android 5.0+)。
2. 无需额外依赖,默认的 RecyclerView 和 ConstraintLayout 已满足需求。
二、数据模型设计(Todo.kt)
创建待办事项的数据类,存储标题、是否完成状态:
data class Todo(
val id: Int, // 唯一标识,用于区分不同待办项
val title: String, // 待办事项标题
var isCompleted: Boolean // 是否完成
)
三、布局文件设计
1. 主界面布局(activity_main.xml)
使用 ConstraintLayout 包含输入框、添加按钮和 RecyclerView:
<?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=".MainActivity">
<!-- 输入框与添加按钮 -->
<EditText
android:id="@+id/etTodoTitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:hint="请输入待办事项..."
android:inputType="textCapSentences"
app:layout_constraintEnd_toStartOf="@id/btnAdd"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintMarginStart="16dp"
app:layout_constraintMarginTop="16dp" />
<Button
android:id="@+id/btnAdd"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="添加"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/etTodoTitle"
app:layout_constraintTop_toTopOf="@id/etTodoTitle"
app:layout_constraintMarginStart="8dp"
app:layout_constraintMarginEnd="16dp" />
<!-- 待办事项列表 -->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvTodoList"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintTop_toBottomOf="@id/etTodoTitle"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintMarginTop="16dp" />
</androidx.constraintlayout.widget.ConstraintLayout>
2. 列表项布局(item_todo.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"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
android:background="?attr/selectableItemBackground">
<CheckBox
android:id="@+id/cbCompleted"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
<TextView
android:id="@+id/tvTitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:textSize="16sp"
android:layout_marginStart="16dp"
app:layout_constraintStart_toEndOf="@id/cbCompleted"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
四、适配器设计(TodoAdapter.kt)
连接数据与 RecyclerView,处理列表项的点击和状态更新:
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.todoapp.databinding.ItemTodoBinding
// 使用 ListAdapter 优化列表刷新(基于 DiffUtil)
class TodoAdapter(
private val onTodoChecked: (Todo, Boolean) -> Unit, // 复选框状态变化回调
private val onTodoLongClick: (Todo) -> Unit // 长按删除回调
) : ListAdapter<Todo, TodoAdapter.TodoViewHolder>(TodoDiffCallback()) {
// 视图持有者
inner class TodoViewHolder(private val binding: ItemTodoBinding) :
RecyclerView.ViewHolder(binding.root) {
init {
// 复选框状态变化监听
binding.cbCompleted.setOnCheckedChangeListener { _, isChecked ->
val todo = getItem(adapterPosition)
onTodoChecked(todo, isChecked)
}
// 长按删除
binding.root.setOnLongClickListener {
val todo = getItem(adapterPosition)
onTodoLongClick(todo)
true
}
}
// 绑定数据到视图
fun bind(todo: Todo) {
binding.tvTitle.text = todo.title
binding.cbCompleted.isChecked = todo.isCompleted
// 完成的项添加删除线
binding.tvTitle.paint.isStrikeThruText = todo.isCompleted
}
}
// 创建视图持有者
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.kt)
处理添加待办、数据存储、列表更新等核心逻辑:
import android.content.SharedPreferences
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Toast
import androidx.recyclerview.widget.LinearLayoutManager
import com.example.todoapp.databinding.ActivityMainBinding
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private lateinit var todoAdapter: TodoAdapter
private lateinit var sharedPreferences: SharedPreferences
private val gson = Gson() // 用于将对象转为 JSON 字符串(存储需要)
private var todoList = mutableListOf<Todo>()
private var nextId = 1 // 用于生成待办项的唯一 id
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
// 初始化 SharedPreferences(本地存储)
sharedPreferences = getSharedPreferences("TodoPrefs", MODE_PRIVATE)
// 从本地加载数据
loadTodos()
// 初始化适配器
todoAdapter = TodoAdapter(
onTodoChecked = { todo, isCompleted ->
// 更新待办项的完成状态
todo.isCompleted = isCompleted
saveTodos() // 保存到本地
todoAdapter.notifyDataSetChanged() // 刷新列表
},
onTodoLongClick = { todo ->
// 长按删除
todoList.remove(todo)
saveTodos() // 保存到本地
todoAdapter.submitList(todoList.toList()) // 提交新列表
Toast.makeText(this, "已删除", Toast.LENGTH_SHORT).show()
}
)
// 配置 RecyclerView
binding.rvTodoList.apply {
adapter = todoAdapter
layoutManager = LinearLayoutManager(this@MainActivity)
setHasFixedSize(true) // 列表大小固定,优化性能
}
// 提交新列表(初始化显示)
todoAdapter.submitList(todoList.toList())
// 添加待办项按钮点击事件
binding.btnAdd.setOnClickListener {
val title = binding.etTodoTitle.text.toString().trim()
if (title.isNotEmpty()) {
// 创建新待办项
val newTodo = Todo(
id = nextId++,
title = title,
isCompleted = false
)
todoList.add(newTodo)
saveTodos() // 保存到本地
todoAdapter.submitList(todoList.toList()) // 刷新列表
binding.etTodoTitle.text.clear() // 清空输入框
} else {
Toast.makeText(this, "请输入待办事项", Toast.LENGTH_SHORT).show()
}
}
}
// 从 SharedPreferences 加载待办列表
private fun loadTodos() {
val json = sharedPreferences.getString("todoList", null)
if (json != null) {
// 解析 JSON 为 List<Todo>
val type = object : TypeToken<MutableList<Todo>>() {}.type
todoList = gson.fromJson(json, type)
// 更新 nextId(避免新增项 id 重复)
if (todoList.isNotEmpty()) {
nextId = todoList.maxOf { it.id } + 1
}
}
}
// 将待办列表保存到 SharedPreferences
private fun saveTodos() {
val json = gson.toJson(todoList)
sharedPreferences.edit().putString("todoList", json).apply()
}
}
六、功能说明与扩展方向
已实现功能:
• 输入标题并点击“添加”,新增待办事项到列表;
• 点击复选框标记待办项为“已完成”(标题显示删除线);
• 长按待办项可删除;
• 数据通过 SharedPreferences 本地存储,重启应用后不丢失。
扩展方向:
• 添加“编辑待办项”功能(点击项弹出编辑框);
• 按“已完成/未完成”筛选列表;
• 使用 Room 数据库替代 SharedPreferences,支持更复杂的查询;
• 添加动画效果(如添加/删除项时的过渡动画)。
运行效果
1. 打开应用后,在输入框中输入“学习Android”,点击“添加”,列表中新增对应项;
2. 点击复选框,标题显示删除线,标记为完成;
3. 长按某一项,弹出“已删除”提示,该项从列表中移除;
4. 关闭应用后重新打开,之前的待办项依然存在。
该实例覆盖了 Android 开发的核心知识点:布局设计、RecyclerView 用法、数据存储、事件处理等,适合初学者上手实践。
604

被折叠的 条评论
为什么被折叠?



