还是接着上一讲“进击的RecyclerView入门二(来点小装饰?)”,在上一讲中我们学到了怎么给不同的Item定制不同的外观,但貌似那个蓝色的框实在太丑了,咱还是把它干了吧。

@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
//太丑了,这段还是注释掉吧
// for (int i = 0; i < parent.getChildCount(); i++) {
// if ((i - 1) % 3 == 0) {
// View child = parent.getChildAt(i);
// RecyclerView.LayoutParams rLP = (RecyclerView.LayoutParams) child.getLayoutParams();
// int left = child.getLeft();
// int top = child.getTop();
// int right = child.getRight();
// int bottom = child.getBottom();
// c.drawRect(left, top, right, bottom, paint);
// }
// }
}

把那个碍眼的蓝色去掉以后就来认识一下我们今天的主角ItemTouchHelper,从名字上就可以看出它是与视图的触摸相关的,还是国际惯例看一下官方文档的定义:

This is a utility class to add swipe to dismiss and drag & drop support to RecyclerView.

简单讲这个类是用来帮助处理RecyclerView子View的拖拽放置和滑动删除的。

It works with a RecyclerView and a Callback class, which configures what type of interactions are enabled and also receives events when user performs these actions.

从上面这段可以知道ItemTouchHelper工作还需要RecyclerView和Callback来配合,那么这里的Callback是什么角色呢?

其实这里的Callback是ItemTouchHelper的一个子类,它的介绍如下:

This class is the contract between ItemTouchHelper and your application. It lets you control which touch behaviors are enabled per each ViewHolder and also receive callbacks when user performs these actions.

意思是说这个类是用来建立ItemTouchHelper和我们应用之间的桥梁的,它可以控制哪些触摸事件可用哪些不可用,还可以接受到这些事件的回调。

总而言之,要实现RecyclerView的拖拽放置和滑动删除需要以下三个类的配合:

  • RecyclerView
  • ItemTouchHelper
  • ItemTouchHelper.Callback

在看ItemTouchHelper.Callback的文档的时候发现它有一个子类ItemTouchHelper.SimpleCallback,似乎又啥猫腻,我们来看看:

A simple wrapper to the default Callback which you can construct with drag and swipe directions and this class will handle the flag callbacks. You should still override onMove or onSwiped depending on your use case.

它是对Callback的一个简单包装,它可以让你自由的通过不同的方向来构建拖拽放置或滑动,并处理这些回调,同时我们自需根据自身需求实现onMove或onSwiped即可。

另外文档中还给了一个小例子:

ItemTouchHelper mIth = new ItemTouchHelper(
new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN,
ItemTouchHelper.LEFT) {
public abstract boolean onMove(RecyclerView recyclerView,
ViewHolder viewHolder, ViewHolder target) {
final int fromPos = viewHolder.getAdapterPosition();
final int toPos = viewHolder.getAdapterPosition();
// move item in `fromPos` to `toPos` in adapter.
return true;// true if moved, false otherwise
}
public void onSwiped(ViewHolder viewHolder, int direction) {
// remove from adapter
}
});

那么接下来我们就来看看具体怎么码代码?

首先准备SimpleCallback:

private ItemTouchHelper.SimpleCallback simpleCallback = new ItemTouchHelper.SimpleCallback(ItemTouchHelper.LEFT
| ItemTouchHelper.DOWN
| ItemTouchHelper.RIGHT
| ItemTouchHelper.UP
, ItemTouchHelper.ACTION_STATE_IDLE)

这里构造方法两个参数的意思是,拖拽支持上下左右,滑动删除不支持。我们这里的网格布局,滑动删除不适合这里。

接着重写如下方法:

@Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
//获得两个Item的位置
int originPosition = viewHolder.getAdapterPosition();
int targetPistion = target.getAdapterPosition();
//交换Adapter中对应的位置
MyAdapter adapter = (MyAdapter) recyclerView.getAdapter();
adapter.move(originPosition, targetPistion);
return true;
}

上面这段代码是拖拽的回调,viewHolder是用户拖拽的Item,target用户想要放置位置的Item。接着我们获得这两个Item对应在Adapter中的位置,并通知adapter去跟新数据。如下图,是将位置5的item往位置0进行拖拽:

界面上已经有所改变了,但要真正改变这两个Item的位置需要去改变他们在adapter中的位置。

在MyAdapter中增加如下方法:


public void move(int origin, int target) {
Collections.swap(datas, origin, target);
if (origin < target) {
for (int i = origin; i < target; i++) {
Collections.swap(datas, i, i + 1);
}
}
if (origin > target) {
for (int i = origin; i > target; i--) {
Collections.swap(datas, i, i - 1);
}
}
notifyItemMoved(origin, target);
}

这里元素的位置调换需要与界面上的一致,不过多赘述。

在上面的那张动图中可以看到被拖拽的Item有放大和缩小的动画效果,这个并不是ItemTouchHelper自带的效果,需要我们自己实现,这里需要重写simpleCallback的onSelectedChanged方法:

@Override
public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
super.onSelectedChanged(viewHolder, actionState);
if(actionState == ItemTouchHelper.ACTION_STATE_DRAG){
Log.d("scott","drag");
ObjectAnimator objectAnimatorX = ObjectAnimator.ofFloat(viewHolder.itemView, "scaleX", 1.0f, 1.2f);
ObjectAnimator objectAnimatorY = ObjectAnimator.ofFloat(viewHolder.itemView, "scaleY", 1.0f, 1.2f);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.setDuration(300);
animatorSet.playTogether(objectAnimatorX, objectAnimatorY);
animatorSet.start();
view = viewHolder.itemView;
}
if(actionState == ItemTouchHelper.ACTION_STATE_IDLE){
ObjectAnimator objectAnimatorX = ObjectAnimator.ofFloat(view, "scaleX", 1.2f, 1.0f);
ObjectAnimator objectAnimatorY = ObjectAnimator.ofFloat(view, "scaleY", 1.2f, 1.0f);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.setDuration(300);
animatorSet.playTogether(objectAnimatorX, objectAnimatorY);
animatorSet.start();
}
}

以上准备工作做完后就需要将ItemTouchHelper,SimpleCallback,RecyclerView三者关联起来:

ItemTouchHelper itemTouchHelper = new ItemTouchHelper(simpleCallback);
itemTouchHelper.attachToRecyclerView(recyclerView);

OK,差不多就这么多了,更详细代码请参考:

demo完整源码地址:

https://github.com/ZhangQinglian/RecyclerViewAdvance

进击的RecyclerView入门三(要是能拖动就好了)的更多相关文章

  1. 进击的RecyclerView入门二(来点小装饰?)

    接着上一讲,我们看到我们的Demo可以正常的运行,并且能自动加载网络图片,那么为了后面观察的方便,我们取消这种自动加载的功能,使用两个按钮来代替,分别用来增加一个数据和减少一个数据.截图如下: 正在我 ...

  2. 进击的RecyclerView入门一(简单上手)

    虽然RecyclerView面世有一段时间了,但由于它的学习成本相对较高,很多码友只是粗略的认识了一下而没有细致的品味RecyclerView的真谛. 那么从现在开始我将带你装逼带你飞,一起领略Goo ...

  3. DevExpress XtraReports 入门三 创建 Master-Detail(主/从) 报表

    原文:DevExpress XtraReports 入门三 创建 Master-Detail(主/从) 报表 本文只是为了帮助初次接触或是需要DevExpress XtraReports报表的人群使用 ...

  4. 【原创】NIO框架入门(三):iOS与MINA2、Netty4的跨平台UDP双向通信实战

    前言 本文将演示一个iOS客户端程序,通过UDP协议与两个典型的NIO框架服务端,实现跨平台双向通信的完整Demo.服务端将分别用MINA2和Netty4进行实现,而通信时服务端你只需选其一就行了.同 ...

  5. Jqgrid入门-Jqgrid列数据拖动(七)

    上一章提到在Jqgrid中如何设置二级表头,这一章节主要探讨Jqgrid表格里面的数据如果实现拖动功能,比如你想把第一行的数据拖到当前页的最后一行,或者其他位置.     Jqgrid表格插件自己没有 ...

  6. 2015版Force Touch Mac Book激活三个手指拖动窗口

    新买的2015版的Mac Book Pro,一切都好,就是原来一直很的很习惯的三个手指拖动窗口的手势,突然找不到地方设置了,很是让我失望了一把,在想苹果怎么会把这么有用的手势去掉了呢.还好有万能的Go ...

  7. Swift语法基础入门三(函数, 闭包)

    Swift语法基础入门三(函数, 闭包) 函数: 函数是用来完成特定任务的独立的代码块.你给一个函数起一个合适的名字,用来标识函数做什么,并且当函数需要执行的时候,这个名字会被用于“调用”函数 格式: ...

  8. Thinkphp入门三—框架模板、变量(47)

    原文:Thinkphp入门三-框架模板.变量(47) [在控制器调用模板] display()   调用当前操作名称的模板 display(‘名字’)  调用指定名字的模板文件 控制器调用模板四种方式 ...

  9. 微服务(入门三):netcore ocelot api网关结合consul服务发现

    简介 api网关是提供给外部调用的统一入口,类似于dns,所有的请求统一先到api网关,由api网关进行指定内网链接. ocelot是基于netcore开发的开源API网关项目,功能强大,使用方便,它 ...

随机推荐

  1. struts2设置加载非默认路径的struts.xml文件解决方案

    方案一:   首先我们要明白struts2加载配置文件都是从它自己的jar包和\WEB-INF\classes两个默认的位置加载的,如果你想改变strusts2中的文件的默认加载路径,可以在web项目 ...

  2. location.href 不响应的解决方法

    在某些时候修改元素的值的时候过快会导致失效 1.window.open("","_blank") 代替  不过也是打开新窗口 2.延迟调用location.hr ...

  3. nginx日志自动切割

    1.日志配置 Nginx 日志分 access.log 记录哪些用户,哪些页面以及用户浏览器,IP等访问信息: error.log 记录服务器错误的日志 配置日志存储路径 location / { a ...

  4. STM32F1_常见外设资源汇总

    前言 STM32F1系列芯片算是在STM32中最早的一系列,在实际生活中应用的比较广泛.因此,汇总一下STM32F1系列芯片常见片内资源,每一篇文章把重点提出来讲解,并提供软件源代码工程. 汇总常见资 ...

  5. mysql数据库 详解

    一.学习目录 1.认识数据库和mysql 2.mysql连接 3.入门语句 4.详解列类型 5.增删改查 INSERT INTO  表名(列1,……  列n)  VALUES(值 1,……  值 n) ...

  6. 常用的easyui使用方法之二

    -------datagrid 1.获取某行的行号(row)tdg.datagrid('getRowIndex',rows)2.通过行号移除该行tdg.datagrid('deleteRow',ind ...

  7. Nginx服务器防止被压力测试

    最近经常用人帮忙做压力测试,用webbech或者ab等一些工具模拟并发压服务器,若服务器没有限制连接数或带宽,服务器很容易被压跨.cat nginx.conf…http {…limit_conn_zo ...

  8. VMware ESXi vsan.v00卡 停

    重装VMware ESXi 6.5,结果在loading vsan.v00文件的时候卡住了,过了几个小时都停留在这个界面.无意间重启使用UEFI 启动,提示某文件校验出错,MD5对不上,重新写入安装镜 ...

  9. android之SQLite数据库insert操作

    原型: long Android.database.sqlite.SQLiteDatabase.insert(String table, String nullColumnHack, ContentV ...

  10. 使用AngularJS实现的前后端分离的数据交互过程

    一. AngularJS简介 AngularJS是什么 AngularJS是一个开源Web应用程序框架.最初是由MISKO Hevery和Adam Abrons于2009年开发,现在是由谷歌维护. A ...