目录介绍
- 01.拖拽需要实现功能
- 02.几个重要的方法说明
- 03.简单实现思路
- 04.拖拽效果上优化
- 05.完整代码展示
好消息
- 博客笔记大汇总【16年3月到至今】,包括Java基础及深入知识点,Android技术博客,Python学习笔记等等,还包括平时开发中遇到的bug汇总,当然也在工作之余收集了大量的面试题,长期更新维护并且修正,持续完善……开源的文件是markdown格式的!同时也开源了生活博客,从12年起,积累共计N篇[近100万字,陆续搬到网上],转载请注明出处,谢谢!
- 链接地址:https://github.com/yangchong2...
- 如果觉得好,可以star一下,谢谢!当然也欢迎提出建议,万事起于忽微,量变引起质变!
01.拖拽需要实现功能
-
需要实现拖拽的功能如下所示
- 长按item后拖动,与其他item交换位置
- 按住item右面的图标后拖动,与其他item交换位置
- 左滑item变透明并缩小,超出屏幕后,其他item补上
- 右滑item变透明并缩小,超出屏幕后,其他item补上
02.几个重要的方法说明
-
几个重要的方法说明
- 需要自定义类实现ItemTouchHelper.Callback类,并重写其中几个方法
isLongPressDragEnabled 是否可以长按拖拽排序 isItemViewSwipeEnabled Item是否可以被滑动 getMovementFlags 当用户拖拽或者滑动Item的时候需要我们告诉系统滑动或者拖拽的方向 onMove 当Item被拖拽的时候被回调 onSwiped 当View被滑动删除的时候 onSelectedChanged 当item被拖拽或侧滑时触发
03.简单实现思路
-
几个方法中代码思路
- 要想达到上面功能需求,在getMovementFlags方法中,当用户拖拽或者滑动Item的时候需要我们告诉系统滑动或者拖拽的方向,那我们知道支持拖拽和滑动删除的无非就是LinearLayoutManager和GridLayoutManager了,所以可以根据布局管理器的不同做了响应的区分。
- 在onMove方法中处理拖拽的回调逻辑,那么什么时候被调用?当Item被拖拽排序移动到另一个Item的位置的时候被调用。在onSwiped方法[当Item被滑动删除到不见]中处理被删除后的逻辑。为了降低代码耦合度,可以通过接口listener回调的方式交给外部处理。
-
上下拖动时与其他item进行位置交换
- ItemTouchHelper.Callback本身不具备将两个item互换位置的功能,但RecyclerView可以,我们可以在item拖动的时候把当前item与另一个item的数据位置交换,再调用RecyclerView的notifyItemMoved()方法刷新布局,同时,因为RecyclerView自带item动画,就可以完成上面的交互效果。
-
左右滑出屏幕时其他item补上
- 只要在item滑出屏幕时,将对应的数据删掉,再调用RecyclerView的notifyItemRemoved()方法刷新布局即可。
04.拖拽效果上优化
-
拖拽效果优化
- 在item被拖拽或侧滑时修改背景色,当动作结束后将背景色恢复回来,而ItemTouchHelper.Callback中正好有对应这两个状态的方法,分别是:onSelectedChanged()、clearView()。那么优化处理其实可以放到这两个方法中处理。
- 左右滑动使item透明度变浅且缩小该如何实现呢?让item执行了两种属性动画而已,在ItemTouchHelper.Callback中有一个方法可以拿到item被拖拽或滑动时的位移变化,那就是onChildDraw()方法,在该方法中设置item渐变和缩放属性动画。
- 出现问题,按照上面做法会出现删除后有空白item留出来,那么为什么会出现这种情况呢?并不是多出了两条空白数据,它们是正常的数据,只是看不到了,这是因为RecyclerView条目(itemView)覆用导致的,前面在onChildDraw()方法中对itemView设置了透明和缩小,而一个列表中固定只有几个itemView而已,当那两个透明缩小的itemView被再次使用时,之前设置的透明度和高度比例已经是0,所以就出现了这种情况,解决方法也很简单,只要在item被移除后,将itemView的透明度和高度比例设置回来即可
05.完整代码展示
- 代码的GitHub地址:https://github.com/yangchong2...
-
完整代码如下所示
/** *
* @author 杨充 * blog : https://github.com/yangchong211 * time : 2017/5/2 * desc : 自定义ItemTouchHelper * revise: 参考严正杰大神博客:https://blog.csdn.net/yanzhenjie1003/article/details/51935982
*/
public class ItemTouchHelpCallback extends ItemTouchHelper.Callback {
/**
* Item操作的回调,去更新UI和数据源
*/
private OnItemTouchCallbackListener onItemTouchCallbackListener;
/**
* 是否可以拖拽
*/
private boolean isCanDrag = false;
/**
* 是否可以被滑动
*/
private boolean isCanSwipe = false;
/**
* 按住拖动item的颜色
*/
private int color = 0;
public ItemTouchHelpCallback(OnItemTouchCallbackListener onItemTouchCallbackListener) {
this.onItemTouchCallbackListener = onItemTouchCallbackListener;
}
/**
* 设置是否可以被拖拽
*
* @param canDrag 是true,否false
*/
public void setDragEnable(boolean canDrag) {
isCanDrag = canDrag;
}
/**
* 设置是否可以被滑动
*
* @param canSwipe 是true,否false
*/
public void setSwipeEnable(boolean canSwipe) {
isCanSwipe = canSwipe;
}
/**
* 设置按住拖动item的颜色
* @param color 颜色
*/
public void setColor(@ColorInt int color){
this.color = color;
}
/**
* 当Item被长按的时候是否可以被拖拽
*
* @return true
*/
@Override
public boolean isLongPressDragEnabled() {
return isCanDrag;
}
/**
* Item是否可以被滑动(H:左右滑动,V:上下滑动)
* isItemViewSwipeEnabled()返回值是否可以拖拽排序,true可以,false不可以
* @return true
*/
@Override
public boolean isItemViewSwipeEnabled() {
return isCanSwipe;
}
/**
* 当用户拖拽或者滑动Item的时候需要我们告诉系统滑动或者拖拽的方向
* 动作标识分:dragFlags和swipeFlags
* dragFlags:列表滚动方向的动作标识(如竖直列表就是上和下,水平列表就是左和右)
* wipeFlags:与列表滚动方向垂直的动作标识(如竖直列表就是左和右,水平列表就是上和下)
*
* 思路:如果你不想上下拖动,可以将 dragFlags = 0
* 如果你不想左右滑动,可以将 swipeFlags = 0
* 最终的动作标识(flags)必须要用makeMovementFlags()方法生成
*/
@Override
public int getMovementFlags(@NonNull RecyclerView recyclerView,
@NonNull RecyclerView.ViewHolder viewHolder) {
RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
if (layoutManager instanceof GridLayoutManager) {
// flag如果值是0,相当于这个功能被关闭
int dragFlag = ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT
| ItemTouchHelper.UP | ItemTouchHelper.DOWN;
int swipeFlag = 0;
// create make
return makeMovementFlags(dragFlag, swipeFlag);
} else if (layoutManager instanceof LinearLayoutManager) {
LinearLayoutManager linearLayoutManager = (LinearLayoutManager) layoutManager;
int orientation = linearLayoutManager.getOrientation();
int dragFlag = 0;
int swipeFlag = 0;
// 为了方便理解,相当于分为横着的ListView和竖着的ListView
// 如果是横向的布局
if (orientation == LinearLayoutManager.HORIZONTAL) {
swipeFlag = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
dragFlag = ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;
} else if (orientation == LinearLayoutManager.VERTICAL) {
// 如果是竖向的布局,相当于ListView
dragFlag = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
swipeFlag = ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;
}
//第一个参数是拖拽flag,第二个是滑动的flag
return makeMovementFlags(dragFlag, swipeFlag);
}
return 0;
}
/**
* 当Item被拖拽的时候被回调
*
* @param recyclerView recyclerView
* @param srcViewHolder 当前被拖拽的item的viewHolder
* @param targetViewHolder 当前被拖拽的item下方的另一个item的viewHolder
* @return 是否被移动
*/
@Override
public boolean onMove(@NonNull RecyclerView recyclerView,
@NonNull RecyclerView.ViewHolder srcViewHolder,
@NonNull RecyclerView.ViewHolder targetViewHolder) {
if (onItemTouchCallbackListener != null) {
int srcPosition = srcViewHolder.getAdapterPosition();
int targetPosition = targetViewHolder.getAdapterPosition();
return onItemTouchCallbackListener.onMove(srcPosition, targetPosition);
}
return false;
}
/**
* 当item侧滑出去时触发(竖直列表是侧滑,水平列表是竖滑)
*
* @param viewHolder viewHolder
* @param direction 滑动的方向
*/
@Override
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
if (onItemTouchCallbackListener != null) {
onItemTouchCallbackListener.onSwiped(viewHolder.getAdapterPosition());
}
}
/**
* 当item被拖拽或侧滑时触发
*
* @param viewHolder viewHolder
* @param actionState 当前item的状态
*/
@Override
public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
super.onSelectedChanged(viewHolder, actionState);
//不管是拖拽或是侧滑,背景色都要变化
if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) {
if (color==0){
viewHolder.itemView.setBackgroundColor(viewHolder.itemView.getContext()
.getResources().getColor(android.R.color.darker_gray));
}else {
viewHolder.itemView.setBackgroundColor(color);
}
}
}
/**
* 当item的交互动画结束时触发
*
* @param recyclerView recyclerView
* @param viewHolder viewHolder
*/
@Override
public void clearView(@NonNull RecyclerView recyclerView,
@NonNull RecyclerView.ViewHolder viewHolder) {
super.clearView(recyclerView, viewHolder);
viewHolder.itemView.setBackgroundColor(viewHolder.itemView.getContext().getResources()
.getColor(android.R.color.white));
viewHolder.itemView.setAlpha(1);
viewHolder.itemView.setScaleY(1);
}
@Override
public void onChildDraw(@NonNull Canvas c, @NonNull RecyclerView recyclerView,
@NonNull RecyclerView.ViewHolder viewHolder,
float dX, float dY, int actionState, boolean isCurrentlyActive) {
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
float value = 1 - Math.abs(dX) / viewHolder.itemView.getWidth();
viewHolder.itemView.setAlpha(value);
viewHolder.itemView.setScaleY(value);
}
}
public interface OnItemTouchCallbackListener {
/**
* 当某个Item被滑动删除的时候
*
* @param adapterPosition item的position
*/
void onSwiped(int adapterPosition);
/**
* 当两个Item位置互换的时候被回调
*
* @param srcPosition 拖拽的item的position
* @param targetPosition 目的地的Item的position
* @return 开发者处理了操作应该返回true,开发者没有处理就返回false
*/
boolean onMove(int srcPosition, int targetPosition);
}
}
```
-
如何使用,代码如下所示
ItemTouchHelpCallback callback = new ItemTouchHelpCallback((srcPosition, targetPosition) -> { if (imageBeans != null) { try { // 更换数据源中的数据Item的位置。更改list中开始和结尾position的位置 Collections.swap(imageBeans, srcPosition, targetPosition); // 更新UI中的Item的位置,主要是给用户看到交互效果 mAdapter.notifyItemMoved(srcPosition, targetPosition); } catch (Exception e){ e.printStackTrace(); } return true; } return true; }); callback.setDragEnable(true); callback.setSwipeEnable(true); callback.setColor(this.getResources().getColor(R.color.base_background_block)); //创建helper对象,callback监听recyclerView item 的各种状态 ItemTouchHelper itemTouchHelper = new ItemTouchHelper(callback); try{ //关联recyclerView,一个helper对象只能对应一个recyclerView itemTouchHelper.attachToRecyclerView(recyclerView); }catch (Exception e){ e.printStackTrace(); }
开源库地址[融合大多数recyclerView使用案例,可以直接下载demo]:https://github.com/yangchong2...
-
- 几乎融合了该系列博客中绝大部分的知识点,欢迎一遍看博客一遍实践,一步步从简单实现功能强大的库
-
- RecycleView的结构,RecyclerView简单用法介绍
-
- RecyclerView.Adapter扮演的角色,一般常用的重写方法说明,数据变更通知之观察者模式,查看.notifyChanged();源码
-
- ViewHolder的作用,如何理解对于ViewHolder对象的数量“够用”之后就停止调用onCreateViewHolder方法,ViewHolder简单封装
-
- LayoutManager作用是什么?setLayoutManager源码分析
-
- SnapHelper作用,什么是Fling操作 ,SnapHelper类重要的方法,
- 06.ItemTouchHelper
-
- SpanSizeLookup如何使用,同时包含列表,2列的网格,3列的网格如何优雅实现?
-
- ItemDecoration的用途,addItemDecoration()源码分析
-
- RecyclerViewPool用于多个RecyclerView之间共享View。
-
- 添加recyclerView的滑动事件,上拉加载分页数据,设置上拉加载的底部footer布局,显示和隐藏footer布局
-
- RecyclerView做性能优化要说复杂也复杂,比如说布局优化,缓存,预加载,复用池,刷新数据等等
-
- SnapHelper旨在支持RecyclerView的对齐方式,也就是通过计算对齐RecyclerView中TargetView 的指定点或者容器中的任何像素点。
-
- 自定义SnapHelper
-
- 需要自定义类实现RecyclerView.ItemDecoration类,并选择重写合适方法
-
- getLayoutPosition()和getAdapterPosition()的区别
-
- 01.如何判断RecyclerView控件滑动到顶部和底部
- 02.RecyclerView嵌套RecyclerView 条目自动上滚的Bug
- 03.ScrollView嵌套RecyclerView滑动冲突
- 04.ViewPager嵌套水平RecyclerView横向滑动到底后不滑动ViewPager
- 05.RecyclerView嵌套RecyclerView的滑动冲突问题
- 06.RecyclerView使用Glide加载图片导致图片错乱问题解决
-
- 要实现在NestedScrollView中嵌入一个或多个RecyclerView,会出现滑动冲突,焦点抢占,显示不全等。如何处理?
其他介绍
01.关于博客汇总链接
02.关于我的博客
- 我的个人站点:www.yczbj.org,www.ycbjie.cn
- github:https://github.com/yangchong211
- 知乎:https://www.zhihu.com/people/...
- 简书:http://www.jianshu.com/u/b7b2...
- csdn:http://my.csdn.net/m0_37700275
- 喜马拉雅听书:http://www.ximalaya.com/zhubo...
- 开源中国:https://my.oschina.net/zbj161...
- 泡在网上的日子:http://www.jcodecraeer.com/me...
- 邮箱:[email protected]
- 阿里云博客:https://yq.aliyun.com/users/a... 239.headeruserinfo.3.dT4bcV
- segmentfault头条:https://segmentfault.com/u/xi...
- 掘金:https://juejin.im/user/593943...