Android 开发都要会的侧滑删除:AndroidSwipeLayout 实战
侧滑删除是 Android 列表交互的经典模式——微信、QQ、系统短信、邮件 App 都在用:手指左滑列表项,露出"删除/置顶"按钮。但手写一个健壮的侧滑并不简单,要处理多 item 互斥(一次只开一个)、嵌套水平滑动冲突、展开收起动画、RecyclerView 复用状态等一堆细节。而 daimajia 的 AndroidSwipeLayout 是开源界最经典、最强大的方案,号称 "The Most Powerful Swipe Layout"。本文从 surface/bottom 模型到 RecyclerView 侧滑删除实战,带你掌握它。参考 GitHub 仓库。
1、概述:AndroidSwipeLayout 与侧滑模型
AndroidSwipeLayout 的核心思想是把每个列表项拆成两层:Surface View(表面)和 Bottom View(底层)。Surface 是默认显示的内容,Bottom 是藏在后面、滑动后才露出的操作区(如删除按钮)。它继承自 FrameLayout,底层用 ViewDragHelper 处理拖拽,支持上下左右四个方向、多种显示模式。
SwipeLayout (FrameLayout) 叠放结构
┌─────────────────────────────────┐
│ Surface View ← 默认显示(内容) │ z-index 高,盖在上面
├─────────────────────────────────┤
│ Bottom View ← 滑动后露出(删除)│ z-index 低,藏在后面
└─────────────────────────────────┘
手指拖动 Surface,Bottom 露出,即为侧滑菜单
核心实现(为什么用它):
1. 开箱即用:XML 配几个属性就有侧滑效果,不用自己写 ViewDragHelper
2. 四向拖拽(Left/Right/Top/Bottom)、两种显示模式灵活组合
3. 自带 "一次只开一个"、复用重置、按钮显露动画等列表常用能力
4. 与 ListView/RecyclerView 都能集成,是侧滑类需求的工业级方案
2、依赖引入
在模块的 build.gradle 添加依赖。注意官方 1.2.0 是 support 库时代的产物,AndroidX 项目需开启 jetifier 或改用社区的 androidx 移植分支。
// build.gradle (Module)
dependencies {
implementation 'com.daimajia.swipelayout:library:1.2.0@aar'
// 1.2.0 依赖旧版 support 库;
// AndroidX 项目可在 gradle.properties 开启 android.useAndroidX + android.enableJetifier=true
// 或使用社区的 androidx 移植版本替代
} 核心实现:
1. 1.2.0@aar 是官方稳定版,托管在 mavenCentral
2. 它依赖 recyclerview-v7 等 support 库,纯 AndroidX 工程需做兼容处理
3. 引入后即可在 XML 里直接使用 com.daimajia.swipe.SwipeLayout
3、XML 布局:写一个侧滑 item
用 SwipeLayout 作为 item 的根布局,Bottom View 写在前面(用 tag 标记),Surface View 写在后面;通过 app:drag_edge 指定拖拽方向,app:show_mode 指定显示模式。
<!-- res/layout/item_swipe.xml -->
<com.daimajia.swipe.SwipeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/swipe"
android:layout_width="match_parent"
android:layout_height="72dp"
app:drag_edge="right" <!-- 从右往左滑,露出右侧按钮 -->
app:show_mode="pull_out"> <!-- Surface 被拉出,Bottom 固定 -->
<!-- ① Bottom View:写在前面,用 tag="Bottom1" 标记,滑出后显示 -->
<LinearLayout
android:id="@+id/bottom_wrapper"
android:tag="Bottom1"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="#FFFF4444">
<TextView
android:id="@+id/btn_delete"
android:layout_width="90dp"
android:layout_height="match_parent"
android:gravity="center"
android:textColor="#FFFFFF"
android:text="删除" />
</LinearLayout>
<!-- ② Surface View:默认显示的内容,写在后面 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#FFFFFF"
android:gravity="center_vertical">
<TextView
android:id="@+id/tv_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="16dp"
android:text="向左滑动显示删除按钮" />
</LinearLayout>
</com.daimajia.swipe.SwipeLayout>
核心实现(两个关键属性):
1. drag_edge:拖拽方向,决定从哪边滑——right 表示"从右往左滑"露出右侧按钮(最常见的删除场景)
2. show_mode:pull_out(Surface 被拉出,Bottom 固定)/ laying_down(Surface 固定,Bottom 从下层滑上来盖住)
3. Bottom 必须写在 Surface 前面,并用 android:tag 标记,库据此识别哪层是底层
4. drag_edge 四选一:left / right / top / bottom,配合 show_mode 两选一,能组合出各种侧滑样式
4、代码控制:开关、禁用与监听
SwipeLayout 提供了完整的开/关 API 和事件监听,业务里常用来"打开前先关别的""点击外部关闭"等:
SwipeLayout swipeLayout = findViewById(R.id.swipe);
// 开关(true 带动画)
swipeLayout.open(true); // 展开 Bottom
swipeLayout.close(true); // 收起
swipeLayout.toggle(true); // 切换
// 禁用侧滑(某些 item 不允许滑)
swipeLayout.setSwipeEnabled(false);
// 点击 Surface 自动关闭
swipeLayout.setClickToClose(true);
// 监听状态变化
swipeLayout.addSwipeListener(new SwipeLayout.SwipeListener() {
@Override public void onStartOpen(SwipeLayout layout) { } // 即将打开
@Override public void onOpen(SwipeLayout layout) { } // 已完全打开
@Override public void onStartClose(SwipeLayout layout) { } // 即将关闭
@Override public void onClose(SwipeLayout layout) { } // 已完全关闭
@Override public void onUpdate(SwipeLayout layout, int left, int top) { } // 拖动中
@Override public void onHandRelease(SwipeLayout layout, float xvel, float yvel) { } // 松手
}); 核心实现:
1. open/close/toggle 控制展开收起,boolean 参数表示是否带动画
2. setSwipeEnabled(false) 可按 item 禁用侧滑,setClickToClose 让点内容即收起
3. SwipeListener 有 6 个回调,覆盖"开始打开/打开/开始关闭/关闭/拖动中/松手"全生命周期
4. 其中 onStartOpen / onOpen 最常用——用来实现"打开前关掉别的 item"
5、RecyclerView 侧滑删除实战
这是侧滑删除的核心场景。两个关键工程细节:① 用一个成员变量记录当前打开的 SwipeLayout,实现"一次只开一个";② 复用时 close 重置,避免滚动后状态错乱。另外监听器要在 onCreateViewHolder 注册一次,不要在 onBind 里重复 add(会累积)。
public class SwipeAdapter extends RecyclerView.Adapter<SwipeAdapter.VH> {
private final List<String> mData;
private SwipeLayout mOpenLayout; // 当前展开的 item,实现"一次只开一个"
public SwipeAdapter(List<String> data) { mData = data; }
@Override
public VH onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_swipe, parent, false);
final VH h = new VH(v);
// 监听器在创建时注册一次,避免 onBind 重复累积
h.swipeLayout.setShowMode(SwipeLayout.ShowMode.LayingDown);
h.swipeLayout.addSwipeListener(new SwipeLayout.SwipeListener() {
@Override public void onStartOpen(SwipeLayout layout) {
// 打开前先关掉别的,保证同一时刻只有一个 item 展开
if (mOpenLayout != null && mOpenLayout != layout) {
mOpenLayout.close();
}
}
@Override public void onOpen(SwipeLayout layout) { mOpenLayout = layout; }
@Override public void onClose(SwipeLayout layout) {
if (mOpenLayout == layout) mOpenLayout = null;
}
@Override public void onStartClose(SwipeLayout l) { }
@Override public void onUpdate(SwipeLayout l, int left, int top) { }
@Override public void onHandRelease(SwipeLayout l, float x, float y) { }
});
return h;
}
@Override
public void onBindViewHolder(VH h, int position) {
// ① 复用时先收起,防止滚动后其他 item 残留展开状态
h.swipeLayout.close(false);
h.tvContent.setText(mData.get(position));
// ② 点击删除:移除数据并刷新
h.btnDelete.setOnClickListener(v -> {
int pos = h.getAdapterPosition();
if (pos != RecyclerView.NO_POSITION) {
mData.remove(pos);
notifyItemRemoved(pos);
mOpenLayout = null;
}
});
}
@Override public int getItemCount() { return mData.size(); }
static class VH extends RecyclerView.ViewHolder {
SwipeLayout swipeLayout;
TextView tvContent, btnDelete;
VH(View v) {
super(v);
swipeLayout = v.findViewById(R.id.swipe);
tvContent = v.findViewById(R.id.tv_content);
btnDelete = v.findViewById(R.id.btn_delete);
}
}
} 核心实现(三个工程要点):
1. "一次只开一个":用 mOpenLayout 记录当前展开项,onStartOpen 时若它不是自己就 close()
2. 复用重置:onBindViewHolder 开头 close(false),否则列表滚动后复用的 item 会"莫名是打开的"
3. 监听器注册一次:在 onCreateViewHolder 里 addSwipeListener,不要放 onBind,否则每次绑定都累加一个监听器
4. 删除用 getAdapterPosition 拿实时位置(不能用 position,删除后会错位),配合 notifyItemRemoved
6、进阶:按钮动画与官方 Adapter
侧滑露出的按钮,可以用 addRevealListener 做显露动画(如删除图标随展开旋转),让交互更精致。此外,库自带了封装好"一次只开一个"的 SwipeRecyclerAdapter,可省掉第 5 节的手动互斥逻辑。
// ① addRevealListener:Bottom 显露过程中做按钮动画
swipeLayout.addRevealListener(R.id.bottom_wrapper,
(layout, child, edge, fraction, distance) -> {
// fraction 0~1 表示显露进度,可给删除图标做旋转/缩放
ViewCompat.setRotation(deleteIcon, fraction * 90f);
});
// ② 用官方 SwipeRecyclerAdapter,setMode 自动实现"一次只开一个"
// 继承 SwipeRecyclerAdapter,重写 getSwipeLayoutResourceId / generateView / onFillValues
// adapter.setMode(Attributes.Mode.Single); // Single:同时只允许打开一个
// adapter.setMode(Attributes.Mode.Multiple); // Multiple:可同时打开多个 核心实现:
1. addRevealListener 监听 Bottom 的显露进度 fraction(0~1),驱动按钮做旋转/缩放等动画
2. 用官方 SwipeRecyclerAdapter + setMode(Attributes.Mode.Single) 可自动实现"一次只开一个",省去手写 mOpenLayout
3. 重写 getSwipeLayoutResourceId 返回 item 中 SwipeLayout 的 id,库会自动管理开合
4. 进阶还能配合 ViewPropertyAnimator、daimajia 的 AndroidViewAnimations 做更丰富的按钮动效
7、注意事项与常见坑
实际接入时,这几个坑最容易踩:
① 版本与 AndroidX。官方 1.2.0 依赖旧 support 库,纯 AndroidX 工程要么开 enableJetifier,要么换社区 androidx 移植分支,否则编译报错。
② 复用状态残留。RecyclerView 复用 item 时,如果不 close 重置,A 打开后滚出屏幕再滚回来,复用它的 B 会"凭空是打开的"——务必在 onBindViewHolder 调 close(false)。
③ 监听器重复累积。addSwipeListener 放在 onBind 里会随绑定次数累加,导致回调被触发多次;应在 onCreateViewHolder 注册一次。
④ 删除用 getAdapterPosition。notifyItemRemoved 后 position 失效,必须用 holder.getAdapterPosition() 拿实时下标,否则越界或删错。
⑤ 同向滑动冲突。SwipeLayout 与外层水平 ViewPager 同向时可能抢事件,需按业务在 onInterceptTouchEvent 或 requestDisallowInterceptTouchEvent 协调(库已对常见场景做了处理)。
8、总结
侧滑删除的内核是 "Surface + Bottom 两层叠放 + 拖拽露出 Bottom",AndroidSwipeLayout 把这套机制封装得干净强大,再配合"一次只开一个""复用重置"两个工程细节,就能做出微信级的列表侧滑体验。
关键要点:
- 模型:Surface(内容,盖在上)+ Bottom(按钮,藏后),Bottom 写前、tag 标记
- 属性:drag_edge 定方向(left/right/top/bottom),show_mode 定模式(pull_out/laying_down)
- 控制:open/close/toggle + SwipeListener 六回调 + setSwipeEnabled/setClickToClose
- 列表三要点:一次只开一个(mOpenLayout)、复用 close 重置、监听器注册一次
- 进阶:addRevealListener 做按钮动画;SwipeRecyclerAdapter + Mode.Single 自动互斥
- 坑:AndroidX 兼容、复用残留、监听累积、getAdapterPosition、同向滑动冲突
对于做消息列表、待办、文件管理、购物车等需要"滑动操作"场景的开发者而言,掌握 AndroidSwipeLayout 的"两层模型 + 一次只开一个 + 复用重置"是必要的——它能让你用一个成熟轮子,避开手写 ViewDragHelper、动画、嵌套冲突的深坑,把精力留给业务本身。把它接进 RecyclerView 的那套 Adapter 模板记牢,遇到侧滑需求几乎可以秒级落地。