拖拽RecyclerView

现在有很多使用RecyclerView实现“拖拽(drag & drop)”和“滑动消失(swipe-to-dismiss)”效果的教程、库、和示例代码。虽然现在有更新的、更好的方式可以实现但是大部分的代码仍旧使用了旧的APIView.OnDragListener或者使用Roman Nurik’s SwipeToDismiss这个库中的处理方式。有一部分使用了新的API,但主要是依赖GestureDetectors 和 onInterceptTouchEvent或者实现的方式很复杂。实际上有非常简单的方式就可以把新特性添加到RecyclerView中。只需要一个类,并且它已经包含在Android Support Library之中。

ItemTouchHelper

ItemTouchHelper是一个非常强大的工具类用于处理当你添加drag&drop 和 swipe-to-dismiss特性到RecyclerView时所关心的那些问题。它是RecyclerView.ItemDecoration的子类,意味着可以向已经存在的LayoutManager 和 Adapter(!)中添加任何东西,并且也可以处理已经存在的item animations、type-restricted、drop settling animations甚至更多。
在这篇文章我将示范一下ItemTouchHelper的简单实现,稍后,在这一系列的文章中我们将延伸扩展更多特性。

忽略开头

只对代码感兴趣? Github源码: Android-ItemTouchHelper-Demo,
Apk下载:from here.

配置

首先我们需要对RecyclerView进行配置,如果没有准备好,请先更新你的build.gradle,添加RecyclerView依赖。

compile 'com.android.support:recyclerview-v7:22.2.0'

在任意RecyclerView.Adapter 和 LayoutManager都可以使用ItemTouchHelper,这篇文章代码的基本文件在这:

https://gist.github.com/iPaulPro/2216ea5e14818056cfcc

使用ItemTouchHelper 和 ItemTouchHelper.Callback

为了使用ItemTouchHelper,你必须得先创建ItemTouchHelper.Callback。这是一个接口用于监听“move” 和 “swipe”的状态。也可用于你要控制所选的view的状态和重写默认动画。如果你只是想使用基本实现你可以使用系统提供的一个帮助类SimpleCallback,但是本着学习的目的,我们还是自己来实现。

实现基本的drag & drop 和 swipe-to-dismiss必须实现以下主要的回调函数:


getMovementFlags(RecyclerView, ViewHolder)
onMove(RecyclerView, ViewHolder, ViewHolder)
onSwiped(ViewHolder, int)

使用两个帮助函数:

isLongPressDragEnabled()
isItemViewSwipeEnabled()

我们将逐个进行介绍.


@Override
public int getMovementFlags(RecyclerView recyclerView, 
        RecyclerView.ViewHolder viewHolder) {
    int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
    int swipeFlags = ItemTouchHelper.START | ItemTouchHelper.END;
    return makeMovementFlags(dragFlags, swipeFlags);
}

ItemTouchHelper允许非常简单的判断屏幕事件的走向。必须重写getMovementFlags()来说明支持哪个方向的拖拽和滑动。使用帮助类ItemTouchHelper.makeMovementFlags(int, int)来管理returned标志。这样就可以在同一个方向进行拖拽和滑动了。


@Override
public boolean isLongPressDragEnabled() {
    return true;
}

ItemTouchHelper可以只支持drag而不支持swipe(or vice versa),所以你必须明确指出你希望支持的类型。为了支持长按 RecyclerView item 对其进行拖动,isLongPressDragEnabled()函数实现应该返回true. 此外在开始拖拽时ItemTouchHelper.startDrag(RecyclerView.ViewHolder)将会被调用。这个我们稍后再讨论。


@Override
public boolean isItemViewSwipeEnabled() {
    return true;
}

如果在viwe中可以随意滑动,isItemViewSwipeEnabled()函数返回true. 此外手动拖动时ItemTouchHelper.startSwipe(RecyclerView.ViewHolder)会被调用。

onMove()onSwiped()负责主要数据的更新。故首先我们需要创建一个允许传递事件回调的接口。


public interface ItemTouchHelperAdapter {

    void onItemMove(int fromPosition, int toPosition);

    void onItemDismiss(int position);
}

ItemTouchHelperAdapter.java Gist

最简单的方式就是下面这样,让我们的RecyclerListAdapter实现ItemTouchHelperAdapter。


public class RecyclerListAdapter extends 
        RecyclerView.Adapter<ItemViewHolder> 
        implements ItemTouchHelperAdapter {
// ... code from [gist]()


@Override
public void onItemDismiss(int position) {
    mItems.remove(position);
    notifyItemRemoved(position);
}

@Override
public void onItemMove(int from, int to) {
    Collections.swap(mItems, from, to);
    notifyItemMoved(from, to);
}

notifyItemRemoved()notifyItemMoved()的调用是非常重要,因此Adapter是可以感受到这些变化的,同样重要的是要注意当我们改变item的Position的时候view的index也时刻在改变,并不是在“drop”事件结束后再改变

现在我们回过头来创建我们自己的SimpleItemTouchHelperCallback并且必须override onMove() 和 onSwiped()。首先添加一个构造函数和Adapter变量。


private final ItemTouchHelperAdapter mAdapter;

public SimpleItemTouchHelperCallback(
        ItemTouchHelperAdapter adapter) {
    mAdapter = adapter;
}

之后 override the remaining events and notify the adapter:


@Override
public boolean onMove(RecyclerView recyclerView, 
        RecyclerView.ViewHolder viewHolder, 
        RecyclerView.ViewHolder target) {

mAdapter.onItemMove(viewHolder.getAdapterPosition(), 
            target.getAdapterPosition());
    return true;
}

@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, 
        int direction) {
    mAdapter.onItemDismiss(viewHolder.getAdapterPosition());
}

写完之后回调类应该像下面这样:


public class SimpleItemTouchHelperCallback extends ItemTouchHelper.Callback {

    private final ItemTouchHelperAdapter mAdapter;

    public SimpleItemTouchHelperCallback(ItemTouchHelperAdapter adapter) {
        mAdapter = adapter;
    }

    @Override
    public boolean isLongPressDragEnabled() {
        return true;
    }

    @Override
    public boolean isItemViewSwipeEnabled() {
        return true;
    }

    @Override
    public int getMovementFlags(RecyclerView recyclerView, ViewHolder viewHolder) {
        int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
        int swipeFlags = ItemTouchHelper.START | ItemTouchHelper.END;
        return makeMovementFlags(dragFlags, swipeFlags);
    }

    @Override
    public boolean onMove(RecyclerView recyclerView, ViewHolder viewHolder, 
            ViewHolder target) {
        mAdapter.onItemMove(viewHolder.getAdapterPosition(), target.getAdapterPosition());
        return true;
    }

    @Override
    public void onSwiped(ViewHolder viewHolder, int direction) {
        mAdapter.onItemDismiss(viewHolder.getAdapterPosition());
    }

}

随着我们Callback的完成,再创建ItemTouchHelper并且调用attachToRecyclerView(RecyclerView) (e.g. in MainFragment.java):


ItemTouchHelper.Callback callback = 
    new SimpleItemTouchHelperCallback(adapter);
ItemTouchHelper touchHelper = new ItemTouchHelper(callback);
touchHelper.attachToRecyclerView(recyclerView);

程序运行结果:

结束语

这是一种极其简单的对ItemTouchHelper的实现写法。但是应该清楚的知道,使用RecyclerView实现基本的拖拽和滑动消失效果是不需要引入三方library的。在下一篇中我们将对拖拽进行更加丰富的控制实现。

源代码

Android-ItemTouchHelper-Demo.

关注我: Google+ and Twitter

© 2015 Paul Burke

移动端常见崩溃指标

崩溃分析,是将 Android 和 iOS 平台常见的 APP 崩溃问题进行归类分析,帮助企业根据崩溃指标快速发现、定位问题。

发布于:2天以前  |  44次阅读  |  详细内容 »

Android死锁初探

说到死锁,大家可能都不陌生,每次遇到死锁,总会让计算机产生比较严重的后果,比如资源耗尽,界面无响应等。

发布于:7天以前  |  89次阅读  |  详细内容 »

Android AES加密(Kotlin)

halo~最近工作上写的东西比较简单,感觉分享不出来,最近刚好看到数据加密这一块,感觉挺不错的,也挺好用的,所以下面分享给大家!

发布于:12天以前  |  79次阅读  |  详细内容 »

Android 持续滑动布局 ConsecutiveScrollerLayout 的使用

在开发项目的时候,有时候会遇到一些比较复杂的页面,需要多个不同的列表或者滑动布局、甚至是WebView,组成一个完整的页面。要实现这样一个复杂的页面,在以前我们可能会通过布局嵌套的方式,在一个大的ScrollView下嵌套多个RecyclerView、WebView、ScrollView来实现。但是这种嵌套的方式不仅会严重影响布局的性能,而且处理滑动事件的冲突也是一件头疼的事,处理不好会严重影响用户操作的体验。

发布于:16天以前  |  101次阅读  |  详细内容 »

Android 升级适配爬坑历程

最近接手了一个公司项目,项目比较老了,从Android 5.0之后就再也没有适配过了,然而重写时间又来不及,然后我的爬坑之旅便开始了。(以下适配方案是按照项目需求顺序来的)

发布于:18天以前  |  181次阅读  |  详细内容 »

2020年,一文点破跨平台开发框架现状

多年来,跨平台移动开发已经获得了最流行软件开发趋势之一的声誉。这并不令人意外,因为采用跨平台开发技术使得软件工程师使用同一代码就能为不同平台构建应用程序,从而节省时间、金钱以及不必要的工作。

发布于:1月以前  |  201次阅读  |  详细内容 »

unicode编码在Android中的应用

Unicode(统一码、万国码、单一码)是计算机科学领域里的一项业界标准,包括字符集、编码方案等。Unicode 是为了解决传统的字符编码方案的局限而产生的,它为每种语言中的每个字符设定了统一并且唯一的二进制编码,以满足跨语言、跨平台进行文本转换、处理的要求。1990年开始研发,1994年正式公布。

发布于:2月以前  |  328次阅读  |  详细内容 »

Android动态权限详解

很多App在首次启动时一通弹窗,申请各式各样的权限。后来苹果为改善用户体验,在App Store审核时要求App必须在使用前一刻才能申请权限,有效改善了此类问题。比如一款直播App,当你启动App时并不需要相机、录音权限,等到你开播时才需要申请这两个权限。这一场景,其实就类似今天要提到的Android动态授权。

发布于:2月以前  |  363次阅读  |  详细内容 »

最多阅读

简化Android的UI开发 7月以前  |  192659次阅读
Android设计与开发工作流 7月以前  |  2295次阅读
Google Enjarify:可代替dex2jar的dex反编译 1年以前  |  2290次阅读
Android多渠道打包工具:apptools 1年以前  |  1922次阅读
Android权限 - 第一篇 1年以前  |  1893次阅读
Google Java编程风格规范(中文版) 1年以前  |  1871次阅读
Stetho 1年以前  |  1809次阅读
Android UI基本技术点 1年以前  |  1802次阅读
30分钟搭建一个android的私有Maven仓库 1年以前  |  1765次阅读
2015 Google IO带来的新 Android 开发工具 1年以前  |  1704次阅读
你应该知道的布局和属性 1年以前  |  1652次阅读
听FackBook工程师讲*Custom ViewGroups* 1年以前  |  1649次阅读
MVP在Android平台上的应用 1年以前  |  1632次阅读
Gradle小知识#3:任务的顺序 1年以前  |  1629次阅读