还是接着上一讲“进击的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. Redis集群搭建问题汇总

    环境 centos7+redis3.2.12 redis requires Ruby version >= 2.2.2. redis官方提供了redis-trib.rb工具,但是在使用之前 需要 ...

  2. 使用kubernetes创建容器一直处于ContainerCreating状态的原因查找与解决

    运行容器的时候,发现一直处于ContainerCreating状态,悲了个催,刚入手就遇到了点麻烦,下面来讲讲如何查找问题及解决的 运行容器命令: [root@master- ~]# kubectl ...

  3. QSettings 使用实例 当需要在程序关闭时保存”状态“信息

    用户对应用程序经常有这样的要求:要求它能记住它的settings,比如窗口大小,位置,一些别的设置,还有一个经常用的,就是recent files,等等这些都可以通过Qsettings来实现. 我们知 ...

  4. atitit.研发管理--标准化流程总结---java开发环境与项目部署环境的搭建工具包总结

    atitit.研发管理--标准化流程总结---java开发环境与项目部署环境的搭建工具包总结 1. ide系列(只开发环境需要,但部署环境也做好放上,很有用) 1 2. web服务器+sdk+网站程序 ...

  5. 彻底清除Linux centos minerd木马 实战  跟redis的设置有关

    top -c把cpu占用最多的进程找出来: Tasks: total, running, sleeping, stopped, zombie Cpu(s): 72.2%us, 5.9%sy, 0.0% ...

  6. 处理图片(updated)

    高像素的图片,比如分辨率为 7712x4352 的照片,当加载到一个 bitmap 中时会占用相当大的内存. 每个像素会占用 4个字节的内存,所以当没有被压缩时,全部的图片会占用 12800万字节(约 ...

  7. python学习笔记(9)--Python UnicodeEncodeError: 'gbk' codec can't encode character 解决方法

    Python UnicodeEncodeError: 'gbk' codec can't encode character 解决方法 这篇文章主要介绍了Python UnicodeEncodeErro ...

  8. C51寄存器详解(Reg51.h)

    Reg51.h 这个头文件将C程序中能用到的寄存器名或寄存器中某位的名称与硬件地址值做了对应,在程序中直接写出这些名称,集成开发环境就能识别,并最终转换成机器代码,实现对单片机各硬件资源的准确操控. ...

  9. URLDecoder: Incomplete trailing escape (%) pattern问题处理

    http://blog.csdn.net/yangbobo1992/article/details/10076335 _________________________________________ ...

  10. PHP 汉字转成拼音

    <?php class ZH{ /** * 将字符串转化为拼音 */ function Pinyin($_String, $_Code='gb2312') { $_DataKey =" ...