截图

需求

  • App 开发新的需求,要求 RecyclerView 实现的九宫格样式可以拖拽,松手以后变更位置,类似于手机桌面拖动 app 变更位置。

分析

  • 经过搜索,发现 support 中带有一个类 ItemTouchHelper,位于 android.support.v7.widget.helper.ItemTouchHelper,通过操作该类可以实现需求类似的功能
  • ItemTouchHelper 唯一构造方法源码:
    /**
* Creates an ItemTouchHelper that will work with the given Callback.
* <p>
* You can attach ItemTouchHelper to a RecyclerView via
* {@link #attachToRecyclerView(RecyclerView)}. Upon attaching, it will add an item decoration,
* an onItemTouchListener and a Child attach / detach listener to the RecyclerView.
*
* @param callback The Callback which controls the behavior of this touch helper.
*
* 大概意思是:创建 ItemTouchHelper 并传入 Callback。通过方法 attachToRecyclerView(RecyclerView) 把 ItemTouchHelper 附加到 RecyclerView 上,通过装饰器模式,把 RecyclerView 的触摸操作分发给 Callback 。Callback 是一个抽象类,通过实现该类的某些方法,完成一些手势操作。
*
* @param callback Callback 控制触摸操作
*/
public ItemTouchHelper(Callback callback) {
mCallback = callback;
}
  • 通过注释可以看到针对 RecyclerView item 的操作可以由继承 Callback 类的方法来实现。Callback 是一个静态内部抽象类 ,通过继承该类,在相应的方法中实现拖拽操作。
  • Callback 主要方法:
    • getMovementFlags(RecyclerView recyclerView,

      ViewHolder viewHolder):返回一个组合 flag ,该 flag 可以表示每个状态(idle,swiping,dragging)默认的可以移动的方向。可以使用 makeMovementFlags() 方法来代替手写复合 flag。
    • makeMovementFlags(int dragFlags, int swipeFlags):创建手势 flag 的方法,通过调用 makeFlag() 实现的。参数:通过传入参数实现 item 的拖拽,或滑动等操作
    • onMove(RecyclerView recyclerView,

      ViewHolder viewHolder, ViewHolder target):拖拽的回调方法,实现该方法实现拖拽效果
    • isLongPressDragEnabled():是否可以长按拖拽,默认返回 true ,表示支持长按拖拽;返回 false ,表示不支持长按拖拽。在针对具体业务中,有些 item 是不允许拖拽的,可以默认返回 false ,然后结合业务,在需要拖拽的地方,再操作。
    • isItemViewSwipeEnabled():是否支持滑动。
    • onSwiped(ViewHolder viewHolder, int direction):滑动回调方法,可以实现左滑和右滑等操作。
    • onSelectedChanged(ViewHolder viewHolder, int actionState):滑动或者拖拽的 item 的选中状态,必须调用 super 方法。
    • clearView(RecyclerView recyclerView, ViewHolder viewHolder)::完成滑动或者拖拽的动作以后的清理工作,必须调用 super 方法。

实现

  • 主要代码:
        itemTouchHelper = new ItemTouchHelper(new Callback() {
/**
* 设置是否滑动,拖拽方向,需要判断布局结构。GridLayoutManger 上下拖动, LineayLayoutManager 上下左右都可以拖动
*/
@Override
public int getMovementFlags(RecyclerView recyclerView, ViewHolder viewHolder) {
if (recyclerView.getLayoutManager() instanceof GridLayoutManager) {
int dragFlags =
ItemTouchHelper.UP | ItemTouchHelper.DOWN | ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;
int swipeFlags = 0;
return makeMovementFlags(dragFlags, swipeFlags);
} else if (recyclerView.getLayoutManager() instanceof LinearLayoutManager) {
int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
int swipeFlags = ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;
return makeMovementFlags(dragFlags, swipeFlags);
}
return 0;
} /**
* 拖动的时候回调的方法,在这里需要将正在拖拽的 item 和集合的 Item 进行交换数据,然后通知 adapter 更新数据
*/
@Override
public boolean onMove(RecyclerView recyclerView, ViewHolder viewHolder, ViewHolder target) {
int fromPosition = viewHolder.getAdapterPosition();
int targetPosition = target.getAdapterPosition();
DragBean dragBean = mList.get(fromPosition);
mList.remove(fromPosition);
mList.add(targetPosition, dragBean);
dragAdapter.notifyItemMoved(fromPosition, targetPosition);
return true;
} /**
* 滑动调用的方法,
*/
@Override
public void onSwiped(ViewHolder viewHolder, int direction) {
int position = viewHolder.getAdapterPosition();
mList.remove(position);
dragAdapter.notifyItemRemoved(position);
} /**
* 长按的时候选中的 item, 给当前 item 设置一个高亮背景色
*/
@Override
public void onSelectedChanged(ViewHolder viewHolder, int actionState) {
if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) {
viewHolder.itemView.setBackgroundColor(Color.LTGRAY);
}
super.onSelectedChanged(viewHolder, actionState); } /**
* 松手以后,去掉高亮背景色
*/
@Override
public void clearView(RecyclerView recyclerView, ViewHolder viewHolder) {
super.clearView(recyclerView, viewHolder);
viewHolder.itemView.setBackgroundColor(0);
} /**
* 默认为 true ,表示长按可用, false 表示长按不可用
*/
@Override
public boolean isLongPressDragEnabled() {
return true;
} /**
* 默认为 true ,表示滑动可用,false 表示滑动不可用。
*/
@Override
public boolean isItemViewSwipeEnabled() {
return true;
}
});
itemTouchHelper.attachToRecyclerView(mRecyclerView);

新的需求

  • 分类显示 item,第一类显示十个 item ,其他类显示剩余的 item ,
  • 所有的 item 可以拖动变换位置,意思是第一类的 item 可以拖动到其他类里面,其他类的 item 也可以拖动到第一类里面。
  • 第一个 item 的位置显示标题首页 item,第十个 item 的位置显示其他 item

分析

  • 第一个 item 和第十个 item 的位置,显示标题,其他 item 按照 GridLayout 显示。可以通过设置android.support.v7.widget.GridLayoutManager#setSpanSizeLookup 方法实现不同位置显示不同的效果
        // 在第0个位置和第9个位置,一个 item 需要占用4个位置,默认为占用1个位置
mLayoutManager.setSpanSizeLookup(new SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
if (position == 0 || position == 9) {
return 4;
}
return 1;
}
});
  • 第一个 item 和第十个 item 是标题,不可拖拽。首先需要默认不可长按拖拽,针对可以长按拖拽的 item 实现点击事件,然后在长按事件中触发拖拽。第一个 item 和第十个 item 的viewholder 不实现点击事件。

实现

  • 主要代码
        // 设置了长按点击的 item 才可以拖拽
final DragAdapter.OnItemClickListener mlistener = new DragAdapter.OnItemClickListener() {
@Override
public void onClickEvent(View v, int position, ViewHolder vh) { } @Override
public void onLongClickEvent(View v, int position, ViewHolder vh) {
itemTouchHelper.startDrag(vh);
}
}; dragAdapter = new DragAdapter(MainActivity.this, mList);
// 实现 adapter 内部 item 的点击事件
dragAdapter.setAdapterClickLisener(mlistener);
mRecyclerView = findViewById(R.id.recyclerview);
mLayoutManager = new GridLayoutManager(MainActivity.this, 4);
// 设置 GridLayout 一行显示4个 item ,在 item 为0 或者9 的时候,一个 item 占用4个位置
mLayoutManager.setSpanSizeLookup(new SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
if (position == 0 || position == 9) {
return 4;
}
return 1;
}
}); mRecyclerView.setItemAnimator(new DefaultItemAnimator());
mRecyclerView.setHasFixedSize(true);
mRecyclerView.setLayoutManager(mLayoutManager);
mRecyclerView.setAdapter(dragAdapter);
itemTouchHelper = new ItemTouchHelper(new Callback() {
/**
* 设置是否滑动,拖拽方向,需要判断布局结构。GridLayoutManger 上下拖动, LineayLayoutManager 上下左右都可以拖动
*/
@Override
public int getMovementFlags(RecyclerView recyclerView, ViewHolder viewHolder) {
Log.e(TAG, "getMovementFlags()");
if (recyclerView.getLayoutManager() instanceof GridLayoutManager) {
int dragFlags =
ItemTouchHelper.UP | ItemTouchHelper.DOWN | ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;
int swipeFlags = 0;
return makeMovementFlags(dragFlags, swipeFlags);
} else if (recyclerView.getLayoutManager() instanceof LinearLayoutManager) {
int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
int swipeFlags = ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;
return makeMovementFlags(dragFlags, swipeFlags);
}
return 0;
} /**
* 拖动的时候回调的方法,在这里需要将正在拖拽的 item 和集合的 Item 进行交换数据,然后通知 adapter 更新数据
* 针对第0个和第9个位置做判断
*/
@Override
public boolean onMove(RecyclerView recyclerView, ViewHolder viewHolder, ViewHolder target) {
Log.e(TAG, "onMove()");
int fromPosition = viewHolder.getAdapterPosition();
int targetPosition = target.getAdapterPosition();
Log.e(TAG, "fromPosition:" + fromPosition + ",targetPosition:" + targetPosition);
if (targetPosition == 0 || targetPosition == 9) {
targetPosition = fromPosition;
}
int actualFromPosition;
int actualTargetPosition;
if (fromPosition < 9) {
actualFromPosition = fromPosition - 1;
} else if (fromPosition > 9) {
actualFromPosition = fromPosition - 2;
} else {
actualFromPosition = fromPosition;
} if (targetPosition < 9) {
actualTargetPosition = targetPosition - 1;
} else if (targetPosition > 9) {
actualTargetPosition = targetPosition - 2;
} else {
actualTargetPosition = targetPosition;
} if (targetPosition != 0 || targetPosition != 9) {
DragBean dragBean = mList.get(actualFromPosition);
mList.remove(actualFromPosition);
mList.add(actualTargetPosition, dragBean);
dragAdapter.notifyItemMoved(fromPosition, targetPosition);
}
return true; } /**
* 滑动调用的方法,
*/
@Override
public void onSwiped(ViewHolder viewHolder, int direction) {
Log.e(TAG, "onSwiped()");
int position = viewHolder.getAdapterPosition();
mList.remove(position);
dragAdapter.notifyItemRemoved(position);
} /**
* 长按的时候选中的 item, 给当前 item 设置一个背景色
*/
@Override
public void onSelectedChanged(ViewHolder viewHolder, int actionState) {
Log.e(TAG, "onSelectedChanged()");
if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) {
viewHolder.itemView.setBackgroundColor(Color.LTGRAY);
}
super.onSelectedChanged(viewHolder, actionState); } /**
* 松手以后,去掉背景色
*/
@Override
public void clearView(RecyclerView recyclerView, ViewHolder viewHolder) {
Log.e(TAG, "clearView()");
super.clearView(recyclerView, viewHolder);
viewHolder.itemView.setBackgroundColor(0);
} /**
* 默认为 true ,表示长按可用, false 表示长按不可用
* 由 adapter 里可以长按拖拽的 item 来激活长按事件
*/
@Override
public boolean isLongPressDragEnabled() {
Log.e(TAG, "isLongPressDragEnabled()");
return false;
} /**
* 默认为 true ,表示滑动可用,false 表示滑动不可用。
*/
@Override
public boolean isItemViewSwipeEnabled() {
Log.e(TAG, "isItemViewSwipeEnabled()");
return false;
}
});
itemTouchHelper.attachToRecyclerView(mRecyclerView); // adapter 中可点击 item 的点击事件
dragViewHolder.dragLayout.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (mListener != null) {
mListener.onClickEvent(v, actualPosition, holder);
}
}
});
dragViewHolder.dragLayout.setOnLongClickListener(new OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
if (mListener != null) {
mListener.onLongClickEvent(v, actualPosition, holder);
}
return true;
}
});

注意

  • 目前还存在 bug

    • 从其他 item 拖拽到首页 item 的时候,其他item 的标题会消失然后又出现。目前还没有想到解决办法,正在看系统源码中。
  • 详见 Github

RecyclerView 与 ItemTouchHelper 实现拖拽效果的更多相关文章

  1. RecyclerViewItemTouchHelperDemo【使用ItemTouchHelper进行拖拽排序功能】

    版权声明:本文为HaiyuKing原创文章,转载请注明出处! 前言 记录使用ItemTouchHelper对Recyclerview进行拖拽排序功能的实现. 效果图 代码分析 ItemTouchHel ...

  2. jQuery的DOM操作实例(2)——拖拽效果&&拓展插件

    一.原生JavaScript编写拖拽效果 二.jQuery编写的拖拽效果 三.在jQuery中拓展一个拖拽插件

  3. React.js实现原生js拖拽效果及思考

    一.起因&思路 不知不觉,已经好几天没写博客了...近来除了研究React,还做了公司官网... 一直想写一个原生js拖拽效果,又加上近来学react学得比较嗨.所以就用react来实现这个拖 ...

  4. js拖拽效果

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  5. WinForm支持拖拽效果

    有一个MSDN客户提问在WinForm中如何实现拖拽效果——比如在WinForm中有一个Button,我要实现的效果是拖拽这个Button到目标位置后生成一个该控件的副本. 其实这个操作主要分成三步走 ...

  6. js div浮动层拖拽效果代码

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  7. JS实现漂亮的窗口拖拽效果(可改变大小、最大化、最小化、关闭)

    转自<JS实现漂亮的窗口拖拽效果(可改变大小.最大化.最小化.关闭)>:http://www.jb51.net/article/73157.htm   这篇文章主要介绍了JS实现漂亮的窗口 ...

  8. JQ实现3D拖拽效果

    <!DOCTYPE HTML> <html onselectstart='return false'> <head> <meta http-equiv=&qu ...

  9. 用JS实现版面拖拽效果

    类似于这样的一个版面,点击标题栏,实现拖拽效果. 添加onmousedown事件 通过获取鼠标的坐标(clientX,clientY)来改变面板的位置 注意:面板使用绝对定位方式,是以左上角为参考点, ...

随机推荐

  1. Webwork【03】核心类 ServletDispatcher 的初始化

    1. Webwork 与 Xwork 搭建环境需要的的jar 为:webwork-core-1.0.jar,xwork-1.0.jar,搭建webwork 需要xwork 的jar呢?原因是这样的,W ...

  2. sqlserver 日志查看

    sqlserve的ErrorLog文件有时候会碰到文件很大的情况,可能通过命令xp_readerrorlog 或 sp_readerrorlog 执行,可以加搜索文本或起止时间 -- 日志查看 --e ...

  3. Linux下找不到动态链接库(转)

    1.生成静态库 生成静态库使用ar工具,其实ar是archive的意思 $ar cqs libhello.a hello.o 2.生成动态库 用gcc来完成,由于可能存在多个版本,因此通常指定版本号: ...

  4. Java AtomicBoolean (Java代码实战-008)

    值得一提的是,Java的AtomXXX类并不是使用了锁的方式进行同步,而是采用了一种新的理念,叫做CAS(Compare And Swap)CAS是一组CPU原语指令,用来实现多线程下的变量同步(原子 ...

  5. python之模块contextlib 加强with语句而存在

    # -*- coding: utf-8 -*- #python 27 #xiaodeng #python之模块contextlib,为加强with语句而存在 #特别注意:python3和python2 ...

  6. vmware中的 CentOS7 虚机磁盘动态扩容

    0.在vmware的配置项中,将虚机的磁盘大小调大,步骤简单,此处略 查看当前状态 文件系统状态 df -h 磁盘状态 lsblkfdisk   -l  1.首先要再创建一个物理分区 (使用fdisk ...

  7. 如何在eclipse中创建.properties文件

    打开file--new--other 选择general--file--next 选择要建在哪个文件名下,然后在底部的file name后输入properities文件名,finish即可

  8. Path画直线与弧线

    代码地址如下:http://www.demodashi.com/demo/14754.html 前言 之前讲过Paint和Canvas的基本使用,今天来介绍下Path的使用 涉及内容有: Path画直 ...

  9. Ubuntu系统下添加程序启动器

    Ubuntu系统上安装的软件,有的会自动创建快捷方式,在程序中可以搜索到,而有的安装后不会在应用程序中出现,如Eclipse.Spring Tool Suite或是绿色软件等,那么怎么手动创建快捷方式 ...

  10. [转]GFS架构分析

    Google文件系统(Google File System,GFS)是构建在廉价的服务器之上的大型分布式系统.它将服务器故障视为正常现象,通过软件的方式自动容错,在保证系统可靠性和可用性的同时,大大减 ...