类似支付宝应用管理界面——RecycleView+ItemTouchHelper实现拖拽滑动

要实现RecycleView中的拖拽滑动,在以往的经验中经常要依赖GestureDetectors、onInterceptTouchEvent等来实现,然而在RecyclerView上添加拖动特性有一个非常简单的方法它就是:ItemTouchHelper。

一、效果图

以下就是通过RecycleView+ItemTouchHelper实现拖拽滑动的效果图,看起来有没有很炫酷。其实实现起来很简单,我们接下来就开始介绍。


二、ItemTouchHelper的介绍

ItemTouchHelper是一个强大的工具,它处理好了关于在RecyclerView上添加拖动排序与滑动删除的所有事情。它是RecyclerView.ItemDecoration的子类,也就是说它可以轻易的添加到几乎所有的LayoutManager和Adapter中。它还可以和现有的item动画一起工作,提供受类型限制的拖放动画等等。

1.添加依赖
compile 'com.android.support:recyclerview-v7:25.1.0'

因为要使用RecycleView,同时ItemTouchHelper也是RecycleView中的类。

2.自定义ItemTouchHelper.Callback类

为了使用ItemTouchHelper,你需要实现ItemTouchHelper.Callback接口,通过这个接口,你可以监听“move”和 “swipe”事件,在这里你也可以控制View的选择状态和重写默认动画。

必须实现主要的回调方法:

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

具体解释这三个方法:

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)创建代表方向的Flag。这里我们同时支持drag和swipe。实现这个方法,ItemTouchHelper可以只能drag而不能swipe(反之亦然),总之根据自己的需求指定。

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

当Item移动或者滑动时,会回调这两个方法,然后可以在这两个方法内部设置回调通知更新适配器或者页面显示的数据。

我们还将使用2个帮助方法:

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

实现isLongPressDragEnabled()方法返回true去支持长按RecyclerView的item时的drag事件。或者,也可以调用ItemTouchHelper.startDrag(RecyclerView.ViewHolder) 方法来开始一个拖动。

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

实现isItemViewSwipeEnabled()方法返回true开启触摸视图时的swipe功能。另外ItemTouchHelper.startSwipe(RecyclerView.ViewHolder)也开始swipe事件。

设置给RecycleView:
实现了以上的方法后,就会监听到拖拽和滑动的手势,并会处理相关操作。
接下来需要做的就是把实现的自定义ItemTouchHelper.Callback类设置给RecycleView。

ItemDragHelperCallback callback = new ItemDragHelperCallback(mMineAdapter);
ItemTouchHelper touchHelper = new ItemTouchHelper(callback);
touchHelper.attachToRecyclerView(mNewsChannelMineRv);

以上就是关于RecycleView+ItemTouchHelper实现拖拽滑动的简单介绍,下面为实现上述效果图,具体讲解其实现过程。

三、RecycleView+ItemTouchHelper实现拖拽的实例应用

1.布局文件
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              xmlns:app="http://schemas.android.com/apk/res-auto"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:background="@color/white"
              android:clipToPadding="true"
              android:fitsSystemWindows="true"
              android:orientation="vertical">

    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        style="@style/action_bar"
        android:background="@color/colorPrimary"
        app:navigationIcon="@drawable/ic_arrow_back"
        app:theme="@style/AppTheme.PopupOverlay"
        app:title="@string/channel_manage" />

    <TextView
        style="@style/news_channel_sort_title"
        android:text="我的频道  长按并拖拽可排序" />

    <android.support.v7.widget.RecyclerView
        android:id="@+id/news_channel_mine_rv"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:overScrollMode="never"></android.support.v7.widget.RecyclerView>

    <TextView
        style="@style/news_channel_sort_title"
        android:text="@string/更多频道  点击添加" />

    <android.support.v7.widget.RecyclerView
        android:id="@+id/news_channel_more_rv"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:overScrollMode="never"></android.support.v7.widget.RecyclerView>
</LinearLayout>

一共用了两个RecyclerView来分别实现我的频道和更多频道的内容,其实可以使用一个RecyclerView通过判断类型来实现多布局类型,效果会更好,后续会尝试,暂时先这样了。感兴趣的可以参考我的另一篇文章来自行实现。

2.点击Item增删效果的实现

先来个简单的,就是点击Item后,我的频道和更多频道中,一个频道删除点击的频道,另一个频道增加该频道。

思路:设置Item的点击事件的监听

在Adaper类中,设置监听接口,并提供传入接口对象的方法让Activity调用

 //Item点击事件的监听接口
    public interface OnItemClickListener {
        void onItemClick(View view, int position);
    }
 public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
        mOnItemClickListener = onItemClickListener;
    }

当点击Item后,出发监听,产生回调

 if (mOnItemClickListener != null) {
            holder.mLayout.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {

                    if (!table.getNewsChannelFixed()) {
                        //对项目点击后增删操作的监听
                        mOnItemClickListener.onItemClick(view, holder.getLayoutPosition());
                    }
                }
            });
        }

在Activity中根据传入的数据进行操作

我的频道所对应的RecyclerView的操作

mMineAdapter.setOnItemClickListener(new NewsChannelAdapter.OnItemClickListener() {
            @Override
            public void onItemClick(View view, int position) {
                NewsChannelTable newsChannel = mMineAdapter.getAdapterData().get(position);
                mMoreAdapter.getAdapterData().add(newsChannel);
                mMoreAdapter.notifyDataSetChanged();
                mMineAdapter.getAdapterData().remove(position);
                mMineAdapter.notifyDataSetChanged();
                //进行添加或删除操作后,要更新的列表 进行存储
                mPresenter.onItemAddOrRemove((ArrayList<NewsChannelTable>) mMineAdapter.getAdapterData(), (ArrayList<NewsChannelTable>) mMoreAdapter.getAdapterData());

            }
        });

更多频道所对应的RecyclerView的操作

mMoreAdapter.setOnItemClickListener(new NewsChannelAdapter.OnItemClickListener() {
            @Override
            public void onItemClick(View view, int position) {
                if(mMineAdapter.getAdapterData().size()==7){
                    ToastUitl.showShort("最多只能添加7个");
                }else{
                NewsChannelTable newsChannel = mMoreAdapter.getAdapterData().get(position);
                mMoreAdapter.getAdapterData().remove(position);
                mMoreAdapter.notifyDataSetChanged();
                mMineAdapter.getAdapterData().add(newsChannel);
                mMineAdapter.notifyDataSetChanged();
                List<NewsChannelTable> data = mMineAdapter.getAdapterData();
                for (NewsChannelTable table : data) {
                    System.out.println(table);
                }
                //进行添加或删除操作后,要更新的列表 进行存储
                mPresenter.onItemAddOrRemove((ArrayList<NewsChannelTable>) mMineAdapter.getAdapterData(), (ArrayList<NewsChannelTable>) mMoreAdapter.getAdapterData());
            }

主要实现两个内容:
第一:RecyclerView中内容数据的修改更新,呈现点击后增删的效果。
第二:调用方法通知进行缓存处理,记录修改后的效果。同时通知新闻首页中频道的更新显示。关于第二部分的内容不详细介绍了,可以看最下方的源码地址。

3.自定义ItemTouchHelper.Callback类实现拖拽效果
@Override
    public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
        //根据recyclerView的布局,进行设置拖拽的方向
        int dragFlags = setDragFlags(recyclerView);
        //不允许进行滑动
        int swipeFlags = 0;
        return makeMovementFlags(dragFlags, swipeFlags);
    }

    private int setDragFlags(RecyclerView recyclerView) {
        int dragFlags;
        RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
        if (layoutManager instanceof GridLayoutManager || layoutManager instanceof StaggeredGridLayoutManager) {
            dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN | ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;
        } else {
            dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
        }
        return dragFlags;
    }

先根据布局判断支持的拖拽的方向,如果是GridLayoutManager 和StaggeredGridLayoutManager支持上下左右拖拽,如果是LinearLayoutManager支持上下拖拽,本例中不支持滑动操作。

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

    }

监听移动事件,其中mOnItemMoveListener.onItemMove是对移动的监听的回调,判断是否可以移动并通知Adapter类数据更新。

 @Override
    public boolean onItemMove(int fromPosition, int toPosition) {
        if (isChannelFixed(fromPosition, toPosition)) {
            return false;
        }
        //在我的频道中进行子频道的移动
        Collections.swap(getAdapterData(), fromPosition, toPosition);
        notifyItemMoved(fromPosition, toPosition);
        //通知顺序变换,存储,设置频道顺序,以及显示的顺序
        System.out.println("发送移动的消息");
        EventBus.getDefault().post(new ChannelBean(getAdapterData()));
        return true;
    }
//不能移动头条
    private boolean isChannelFixed(int fromPosition, int toPosition) {
        return fromPosition == 0 || toPosition == 0;
    }

两种情况:
第一:如果移动的是“头条”频道或者移动到“头条”频道,返回false,则不能进行移动。
第二:不是上面的情况。更新我的频道栏目中频道的显示顺序,同时通知数据缓存并通知新闻首页频道顺序的更新。关于这部分的详细内容,可以看最下方的源码。

 //返回true 允许拖拽
    @Override
    public boolean isLongPressDragEnabled() {
        return mIsLongPressEnabled;
    }

是否允许拖拽,通过外部传入来开启。

public void setLongPressEnabled(boolean longPressEnabled) {
        mIsLongPressEnabled = longPressEnabled;
    }

在Adapter类中,根据触摸的Item的类型来判断是否开启长按拖拽。

if (mItemDragHelperCallback != null) {
            holder.mLayout.setOnTouchListener(new View.OnTouchListener() {
                @Override
                public boolean onTouch(View v, MotionEvent event) {
                    mItemDragHelperCallback.setLongPressEnabled(table.getNewsChannelIndex() == 0 ? false : true);
                    return false;
                }
            });
        }

如果触摸的对象是“头条”频道,则不开启拖拽,其他情况就会开启长按。

4.设置给RecycleView
       //Adapter类实现了OnItemMoveListener的接口,将其传入ItemDragHelperCallback方便接口回调
        ItemDragHelperCallback callback = new ItemDragHelperCallback(mMineAdapter);
       //将自定义的ItemDragHelperCallback类传给ItemTouchHelper
        ItemTouchHelper touchHelper = new ItemTouchHelper(callback);
       //将ItemTouchHelper设置给RecyclerView
        touchHelper.attachToRecyclerView(mNewsChannelMineRv);

通过以上步骤就可以实现效果图中的效果,真实效果还是不错的。源码地址,感兴趣的看一下,给个Star支持下,看项目中的NewsChannelActivity部分即可。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,390评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,821评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,632评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,170评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,033评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,098评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,511评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,204评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,479评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,572评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,341评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,893评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,171评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,486评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,676评论 2 335

推荐阅读更多精彩内容