我们在很多应用中都见过 RecyclerView 的身影,它无疑是现代 Android 开发的基石。不仅如此,通常还伴随着许多高级交互功能,比如滑动删除。在 Gmail 应用中,我们见过这种功能,可以通过向右或向左滑动条目来删除邮件或将其归档。在 2026 年的今天,虽然我们已经习惯了 Jetpack Compose 的声明式 UI,但传统的 RecyclerView 依然因其极高的灵活性和性能在大型复杂列表中占据一席之地。
在这篇文章中,我们将不仅重温如何在 Android 中实现 RecyclerView 条目的滑动删除功能并加入撤销功能,我们还将探讨如何结合 2026 年最新的工程化实践——如 AI 辅助编程和严格的容灾设计——来打造一个企业级的交互体验。我们将使用 Java 语言来实现这个项目,但请记住,这些原理在任何语言中都是通用的。
我们将在本文中构建什么?
我们将构建一个简单的应用程序,其中会显示一个简单的 RecyclerView,用于展示课程列表及其描述。此外,我们还将为其添加滑动删除和撤销功能。这听起来很基础,但在生产环境中,"撤销"功能涉及到状态管理、UI 线程同步以及用户心理模型的深层理解。
分步实现
#### 第一步:创建一个新项目
要在 Android Studio 中创建新项目,请参考相关指南。请注意选择 Java 作为编程语言。
要实现 RecyclerView,需要三个子部分,这有助于控制 RecyclerView。这三个子部分包括:
- 卡片布局:卡片布局是一个 XML 文件,用于代表 RecyclerView 内部的每一个单独的网格项。
- ViewHolder:ViewHolder 类是一个 Java 类,它存储了对卡片布局中 UI 元素的引用,并且可以在程序执行期间通过数据列表动态修改这些元素。
- 数据类:数据类是一个对象类,用于保存要在 RecyclerView 中显示的每个 RecyclerView 条目中显示的信息。
#### 第二步:为 RecyclerView 卡片条目创建卡片布局
进入 app > res > layout> 右键点击 > New > Layout Resource File,将文件命名为 cardlayout。在这个文件中,编写与 RecyclerView 中卡片条目相关的所有 XML 代码。下面是 cardlayout.xml 文件的代码。
#### 第三步:为 Modal 数据创建一个 Java 类
进入 app > java > 右键点击你的应用包名 > New > Java Class,将文件命名为 RecyclerData。这个类将处理要显示的每个 Recycler 条目的数据。下面是 RecyclerData.java 文件的代码。
public class RecyclerData {
// string for displaying title and description.
private String title;
private String description;
// constructor for our title and description.
public RecyclerData(String title, String description) {
this.title = title;
this.description = description;
}
// creating getter and setter methods.
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}
第四步:为 Adapter 创建一个新的 Java 类
同样地,创建一个新的 Java Class 并将文件命名为 RecyclerViewAdapter。Adapter 是负责 RecyclerView 的主要类。它包含了所有在 RecyclerView 中有用的方法。在编写代码之前,我们建议你利用 Cursor 或 GitHub Copilot 等 AI 辅助工具来生成基础样板代码。这不仅提高了效率,还能减少低级拼写错误。我们可以直接向 AI 提示:"创建一个 RecyclerView Adapter 类,包含 ArrayList 和 ViewHolder"。
下面是 RecyclerViewAdapter.java 文件的代码。请注意,我们添加了 onItemDismiss 方法,这是处理撤销逻辑的关键接口。
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;
public class RecyclerViewAdapter extends RecyclerView.Adapter {
private ArrayList courseDataArrayList;
private Context context;
// 创建一个接口来处理点击和滑动删除后的回调
public interface OnItemDismissListener {
void onItemDismiss(int position);
}
private OnItemDismissListener onItemDismissListener;
public void setOnItemDismissListener(OnItemDismissListener listener) {
this.onItemDismissListener = listener;
}
public RecyclerViewAdapter(ArrayList courseDataArrayList, Context context) {
this.courseDataArrayList = courseDataArrayList;
this.context = context;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
// 为我们的 card_view 项目 infalte 布局。
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.card_layout, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
// 设置数据到 textview 和我们的 imageview。
RecyclerData modal = courseDataArrayList.get(position);
holder.courseNameTV.setText(modal.getTitle());
holder.courseDescTV.setText(modal.getDescription());
}
@Override
public int getItemCount() {
// 返回课程列表的大小。
return courseDataArrayList.size();
}
public class ViewHolder extends RecyclerView.ViewHolder {
private TextView courseNameTV, courseDescTV;
public ViewHolder(@NonNull View itemView) {
super(itemView);
// 初始化我们的 text views。
courseNameTV = itemView.findViewById(R.id.idTVCourseName);
courseDescTV = itemView.findViewById(R.id.idTVCourseDesc);
}
}
// 删除特定项目的方法
public void removeItem(int position) {
courseDataArrayList.remove(position);
notifyItemRemoved(position);
}
// 在特定位置插入项目的方法(用于撤销)
public void restoreItem(RecyclerData item, int position) {
courseDataArrayList.add(position, item);
notifyItemInserted(position);
}
}
第五步:实现滑动回调与撤销逻辑
这是 2026 年开发理念体现得最淋漓尽致的部分。我们不仅要实现滑动,还要优雅地处理状态。我们将使用 ItemTouchHelper.SimpleCallback。在我们的最近项目中,我们发现将回调逻辑解耦,并利用 Kotlin 或 Java 的 Lambda 表达式可以极大提升代码可读性。
让我们来看一下 INLINECODEdb105103 的核心逻辑。这里我们使用了 INLINECODE5a06bb2f 来提供撤销操作,这是 Material Design 推荐的最佳实践。
import android.graphics.Color;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.snackbar.Snackbar;
import java.util.ArrayList;
public class MainActivity extends AppCompatActivity {
private ArrayList dataArrayList;
private RecyclerViewAdapter adapter;
private RecyclerView recyclerView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 初始化 RecyclerView
recyclerView = findViewById(R.id.idRecyclerView);
dataArrayList = new ArrayList();
// 填充一些模拟数据
dataArrayList.add(new RecyclerData("Data Structures", "Learn about DSA"));
dataArrayList.add(new RecyclerData("Python", "Learn Python language"));
dataArrayList.add(new RecyclerData("C++", "Learn C++ language"));
dataArrayList.add(new RecyclerData("Java", "Learn Java language"));
dataArrayList.add(new RecyclerData("Kotlin", "Learn Kotlin language"));
adapter = new RecyclerViewAdapter(dataArrayList, this);
LinearLayoutManager manager = new LinearLayoutManager(this);
recyclerView.setLayoutManager(manager);
recyclerView.setAdapter(adapter);
// 设置滑动删除的回调
new ItemTouchHelper(new ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) {
@Override
public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) {
return false;
}
@Override
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
// 获取被删除的位置
int position = viewHolder.getAdapterPosition();
// 获取被删除的数据对象,以便后续恢复
RecyclerData deletedItem = dataArrayList.get(position);
// 从适配器中移除
adapter.removeItem(position);
// 显示 Snackbar 提供撤销选项
Snackbar snackbar = Snackbar.make(recyclerView, "Item deleted", Snackbar.LENGTH_LONG);
snackbar.setAction("Undo", new View.OnClickListener() {
@Override
public void onClick(View view) {
// 撤销操作:恢复数据
adapter.restoreItem(deletedItem, position);
Toast.makeText(MainActivity.this, "Item restored!", Toast.LENGTH_SHORT).show();
}
});
// 设置 Snackbar 的颜色和样式
snackbar.setActionTextColor(Color.YELLOW);
snackbar.show();
}
}).attachToRecyclerView(recyclerView);
}
}
深入探讨:2026 视角下的状态管理与容灾设计
在上述代码中,我们实现了一个基础的撤销功能。但在现代企业级应用中,我们需要思考得更深。比如,如果应用在用户撤销之前崩溃了怎么办?如果用户旋转了屏幕导致 Activity 重建,Undo 功能是否会失效?
1. ViewModel 与 LiveData 的集成
为了解决这个问题,我们通常会结合 INLINECODE2621273f 和 INLINECODE603f9503 来保存数据状态。数据不应该只存在于 Adapter 中,而应该由 ViewModel 管理。这样,即使配置发生变化,数据也不会丢失。我们可以定义一个 INLINECODEbf045563 来持有 INLINECODE6211c207,并通过 LiveData 通知 UI 更新。这不仅仅是 Google 的推荐,更是我们在构建高可用应用时的标准操作。
2. 边界情况处理与性能优化
在我们的实际项目中,我们遇到过列表数据量巨大(数万条)的情况。此时,直接调用 INLINECODE2ea17915 和 INLINECODEddb9bf07 可能会导致 UI 闪烁或性能瓶颈。
- DiffUtil: 使用 INLINECODEbbbc0599 来计算差异,而不是简单地刷新整个列表。虽然对于单条删除,INLINECODE93909581 已经足够高效,但在复杂的数据变更中,DiffUtil 是救星。
- 防抖动: 对于快速连续的滑动操作,我们需要确保 Undo 的 Snackbar 不会相互覆盖。在 2026 年,我们可以利用协程或 RxJava 来处理这种异步的 UI 状态队列。
3. 可观测性与调试
当用户报告删除功能不工作时,我们如何快速定位?传统的 Logcat 依然有用,但现代开发更倾向于使用结构化的日志系统。在 Undo 逻辑中,我们可以埋点记录每次删除和撤销的行为。利用 AI 辅助的日志分析工具,我们可以快速发现是否是由于并发修改导致了 IndexOutOfBoundsException。
4. AI 辅助开发的未来
想象一下,如果你想让 AI 帮你优化这段代码。你可以在 Cursor IDE 中选中 INLINECODE09df47f7 方法,然后输入提示词:"Refactor this code to use a ViewModel for state persistence and add error handling for null items."。AI 不仅能生成代码,还能解释为什么使用 INLINECODE75cfd24b 能解决生命周期问题。这就是 2026 年的"氛围编程"——开发者专注于逻辑构思,而繁琐的实现细节由 AI 伴侣完成。
常见陷阱与最佳实践
在我们的开发历程中,总结了以下几点经验,希望能帮助你避坑:
- 不要在 Adapter 中持有 Context 引用太久: 这可能会导致内存泄漏。如果确实需要,请使用 INLINECODE3063dcad 或者在 INLINECODE460fd827 时动态获取。
- Undo 的延迟: Snackbar 的显示时间(LENGTH_LONG)通常是 2.5 秒到 3 秒。这在移动端交互中是一个"黄金时间",太短用户来不及反应,太长则阻碍了后续操作。
- 多模态交互: 考虑支持长按删除或其他辅助操作,以适应不同的用户习惯和无障碍需求。
总结
通过这篇文章,我们从零构建了一个具有滑动删除和撤销功能的 RecyclerView。我们不仅看到了具体的代码实现,更重要的是,我们讨论了如何利用现代架构组件来保证状态的稳定性,以及如何利用 AI 工具来提升开发效率。在 2026 年,技术栈在不断更新,但"以用户为中心"的交互设计原则和"健壮性至上"的工程理念永远不会过时。希望这些代码和思考能对你的项目有所帮助!
扩展:2026 年的架构演进与 AI 协同开发
随着我们步入 2026 年,Android 开发已经不再仅仅是编写代码,更多的是关于系统设计和人机协作。在我们最近的一个大型金融应用重构中,我们面临了一个挑战:如何在一个包含分页、过滤和多类型 ViewHolder 的复杂列表中,实现零错误的滑动删除体验?
#### 状态持久化:超越 ViewModel
虽然 ViewModel 能解决屏幕旋转带来的问题,但在"进程死亡"(Process Death)场景下,ViewModel 也会丢失。在 2026 年,我们更倾向于使用 SavedStateHandle 结合 DataStore 或 Room 数据库 来实现真正的持久化撤销。
当用户滑动删除一条数据时,我们不仅仅是从列表中移除它,而是将其标记为"待删除"状态并写入数据库。Snackbar 的 Undo 操作实际上是将数据库中的标记重置。这种架构保证了即使应用被系统杀死,用户回来后依然能看到那个"撤销"按钮,或者数据已经正确更新。让我们思考一下这个场景:用户正在清理邮件列表,突然接了个电话,应用被系统回收。如果没有持久化,那些"待删除"的邮件状态就会丢失。而在我们的新架构中,这一切都是透明的。
#### 氛围编程:你的 AI 结对伙伴
在实现上述复杂逻辑时,我们大量使用了类似 Cursor 或 Windsurf 这样的 AI 原生 IDE。你可能已经注意到,编写大量的 ItemTouchHelper 回调和数据库事务代码非常枯燥且容易出错。
现在,我们可以这样工作:我只需要写下注释,"// 处理向左滑动,将数据存入 Undo 日志表,并更新 UI",然后按下一个快捷键,AI 就会根据我现有的数据库 Schema 自动生成事务代码。甚至,当我们不确定该用 INLINECODEddc3d610 还是 INLINECODE7195a42a 来展示 Undo 按钮时,我们可以问 AI:"根据 Material Design 3 的最新规范,对于列表删除操作,推荐哪种交互模式?为什么?" AI 会直接给出答案并附上设计文档的链接。
#### 性能监控:从 Logcat 到 可观测性
传统的 Log.d("Swipe", "Item removed at " + position) 在简单的 Demo 中很有用,但在生产环境中,它们往往是海量的垃圾信息。在 2026 年的工程实践中,我们更关注"可观测性"。
我们使用像 Firebase Performance 或自研的 APM 系统来追踪自定义指标。例如,我们会记录 INLINECODE52ae4067(滑动延迟)和 INLINECODE46817ff4(撤销成功率)。如果我们的代码变更导致滑动删除的卡顿率上升了 5%,监控系统会立即报警。结合 AI 驱动的日志分析,我们可以迅速定位是因为 notifyItemRemoved 触发了过于昂贵的布局计算,还是因为主线程被某个同步锁阻塞了。
#### 总结:技术与人性的交汇
无论技术如何演进,滑动删除并撤销这一核心交互的本质没有变:它给予用户"安全感"和"控制权"。在 2026 年,我们的目标是利用更强大的工具(ViewModel, AI, Cloud)来隐藏底层的复杂性,让开发者能更专注于这种用户体验的打磨。正如我们在文章开头所提到的,RecyclerView 依然是基石,但我们在这块基石上构建大厦的方式,已经因 AI 和现代架构理念的引入而变得更加高效和优雅。希望当你下次实现这个功能时,能不仅仅把它当作一段代码,而是作为一个精心设计的用户交互旅程来对待。