美文网首页Android UIAndroid开发程序员
Android开发之 RecyclerView实现条目Item拖

Android开发之 RecyclerView实现条目Item拖

作者: zzj丶 | 来源:发表于2017-03-22 21:34 被阅读329次

    原文出处: http://blog.csdn.net/yanzhenjie1003

    需求和技术分析

    RecyclerView Item拖拽排序::长按RecyclerView的Item或者触摸Item的某个按钮。
    RecyclerView Item滑动删除:RecyclerView Item滑动删除:RecyclerView的Item滑动删除。
    实现方案与技术

    利用ItemTouchHelper绑定RecyclerView、ItemTouchHelper.Callback来实现UI更新,并且实现动态控制是否开启拖拽功能和滑动删除功能。

    实现步骤

    继承抽象类ItemTouchHelper,并在构造方法传入实现的ItemTouchHelper.Callback。
    recyclerView绑定ItemTouchHelper:itemTouchHelper.attachToRecyclerView(recyclerView)。
    自定义ItemTouchHelper.Callback的实现接口OnItemTouchCallbackListener,由外部更新RecyclerView的Item。
    几个主要的布局

    activity_main.xml

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
    
        <android.support.v7.widget.RecyclerView
            android:id="@+id/rv_main"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
    
    </LinearLayout>
    

    这个没啥好说的了吧,就是一个RecyclerView啦。

    接下来是RecyclerView的Item的布局item.xml:

    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="?android:listPreferredItemHeight"
        android:background="?selectableItemBackground">
    
        <ImageView
            android:id="@+id/iv_touch"
            style="@style/ItemStyle"
            android:layout_height="match_parent"
            android:layout_alignParentEnd="true"
            android:layout_alignParentRight="true"
            android:src="@android:drawable/alert_dark_frame" />
    
        <CheckBox
            android:id="@+id/cb_item_check"
            style="@style/ItemStyle"
            android:layout_alignParentLeft="true"
            android:layout_alignParentStart="true" />
    
        <TextView
            android:id="@+id/tv_name"
            style="@style/ItemStyle"
            android:layout_toEndOf="@id/cb_item_check"
            android:layout_toRightOf="@id/cb_item_check" />
    
        <TextView
            android:id="@+id/tv_sex"
            style="@style/ItemStyle"
            android:layout_marginLeft="@dimen/dp_10"
            android:layout_marginStart="@dimen/dp_10"
            android:layout_toEndOf="@id/tv_name"
            android:layout_toRightOf="@id/tv_name" />
    
    </RelativeLayout>
    

    这个也不用解释了,到时候下载看源码,就是普通item,展示数据而已。

    实现自己的DefaultItemTouchHelper:继承ItemTouchHelper

    public class DefaultItemTouchHelper extends ItemTouchHelper {
        public DefaultItemTouchHelper(ItemTouchHelp.Callback callback) {
            super(callback);
        }
    }
    

    好嘛,这个太简单了,基本上一行代码都不用写。但是这里需要一个ItemTouchHelp.Callback啊,所以我们还是要实现一个ItemTouchHelp.Callback,客观且看下文分解。

    实现自己的ItemTouchHelper.Callback:继承ItemTouchHelper.Callback

    这里是全文最重要的部分啦,要认真点看噢,先上代码,后解释,其他看注释和视频。

    public class DefaultItemTouchHelpCallback extends ItemTouchHelper.Callback {
    
        /**
         * Item操作的回调
         */
        private OnItemTouchCallbackListener onItemTouchCallbackListener;
    
        /**
         * 是否可以拖拽
         */
        private boolean isCanDrag = false;
        /**
         * 是否可以被滑动
         */
        private boolean isCanSwipe = false;
    
        public DefaultItemTouchHelpCallback(OnItemTouchCallbackListener onItemTouchCallbackListener) {
            this.onItemTouchCallbackListener = onItemTouchCallbackListener;
        }
    
        /**
         * 设置Item操作的回调,去更新UI和数据源
         *
         * @param onItemTouchCallbackListener
         */
        public void setOnItemTouchCallbackListener(OnItemTouchCallbackListener onItemTouchCallbackListener) {
            this.onItemTouchCallbackListener = onItemTouchCallbackListener;
        }
    
        /**
         * 设置是否可以被拖拽
         *
         * @param canDrag 是true,否false
         */
        public void setDragEnable(boolean canDrag) {
            isCanDrag = canDrag;
        }
    
        /**
         * 设置是否可以被滑动
         *
         * @param canSwipe 是true,否false
         */
        public void setSwipeEnable(boolean canSwipe) {
            isCanSwipe = canSwipe;
        }
    
        /**
         * 当Item被长按的时候是否可以被拖拽
         *
         * @return
         */
        @Override
        public boolean isLongPressDragEnabled() {
            return isCanDrag;
        }
    
        /**
         * Item是否可以被滑动(H:左右滑动,V:上下滑动)
         *
         * @return
         */
        @Override
        public boolean isItemViewSwipeEnabled() {
            return isCanSwipe;
        }
    
        /**
         * 当用户拖拽或者滑动Item的时候需要我们告诉系统滑动或者拖拽的方向
         *
         * @param recyclerView
         * @param viewHolder
         * @return
         */
        @Override
        public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
            RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
            if (layoutManager instanceof GridLayoutManager) {// GridLayoutManager
                // flag如果值是0,相当于这个功能被关闭
                int dragFlag = ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT | ItemTouchHelper.UP | ItemTouchHelper.DOWN;
                int swipeFlag = 0;
                // create make
                return makeMovementFlags(dragFlag, swipeFlag);
            } else if (layoutManager instanceof LinearLayoutManager) {// linearLayoutManager
                LinearLayoutManager linearLayoutManager = (LinearLayoutManager) layoutManager;
                int orientation = linearLayoutManager.getOrientation();
    
                int dragFlag = 0;
                int swipeFlag = 0;
    
                // 为了方便理解,相当于分为横着的ListView和竖着的ListView
                if (orientation == LinearLayoutManager.HORIZONTAL) {// 如果是横向的布局
                    swipeFlag = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
                    dragFlag = ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;
                } else if (orientation == LinearLayoutManager.VERTICAL) {// 如果是竖向的布局,相当于ListView
                    dragFlag = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
                    swipeFlag = ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;
                }
                return makeMovementFlags(dragFlag, swipeFlag);
            }
            return 0;
        }
    
        /**
         * 当Item被拖拽的时候被回调
         *
         * @param recyclerView     recyclerView
         * @param srcViewHolder    拖拽的ViewHolder
         * @param targetViewHolder 目的地的viewHolder
         * @return
         */
        @Override
        public boolean onMove(RecyclerView recyclerView, ViewHolder srcViewHolder, ViewHolder targetViewHolder) {
            if (onItemTouchCallbackListener != null) {
                return onItemTouchCallbackListener.onMove(srcViewHolder.getAdapterPosition(), targetViewHolder.getAdapterPosition());
            }
            return false;
        }
    
        @Override
        public void onSwiped(ViewHolder viewHolder, int direction) {
            if (onItemTouchCallbackListener != null) {
                onItemTouchCallbackListener.onSwiped(viewHolder.getAdapterPosition());
            }
        }
    
        public interface OnItemTouchCallbackListener {
            /**
             * 当某个Item被滑动删除的时候
             *
             * @param adapterPosition item的position
             */
            void onSwiped(int adapterPosition);
    
            /**
             * 当两个Item位置互换的时候被回调
             *
             * @param srcPosition    拖拽的item的position
             * @param targetPosition 目的地的Item的position
             * @return 开发者处理了操作应该返回true,开发者没有处理就返回false
             */
            boolean onMove(int srcPosition, int targetPosition);
        }
    }
    

    好,其实上面最重要的就是五个方法:

    /**
     * 是否可以长按拖拽排序。
     */
    @Override
    public boolean isLongPressDragEnabled() {}
    /**
     * Item是否可以被滑动(H:左右滑动,V:上下滑动)
     */
    @Override
    public boolean isItemViewSwipeEnabled() {}
    /**
     * 当用户拖拽或者滑动Item的时候需要我们告诉系统滑动或者拖拽的方向
     */
    @Override
    public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {}
    /**
     * 当Item被拖拽的时候被回调
     */
    @Override
    public boolean onMove(RecyclerView r, ViewHolder rholer, ViewHolder tholder) {}
    
    /**
     * 当View被滑动删除的时候
     */
    @Override
    public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {}
    

    isItemViewSwipeEnabled()返回值是否可以拖拽排序,true可以,false不可以,isItemViewSwipeEnabled()是否可以滑动删除,true可以,false不可以;这两个方法都是配置是否可以操作的。我们上面的代码中返回了一个成员变量值,并且这个值通过外部可以修改,所以提供了外部控制的方法。

    onMove()当Item被拖拽排序移动到另一个Item的位置的时候被回调,onSwiped()当Item被滑动删除到不见;这两个方法是当用户操作了,来回调我们,我们就该去更新UI了。这里我们提供了一个Listener去通知外部,并且返回出去了必要的值,来降低代码耦合度。

    getMovementFlags()说明一:是当用户拖拽或者滑动Item的时候需要我们告诉系统滑动或者拖拽的方向,那我们又知道支持拖拽和滑动删除的无非就是LinearLayoutManager和GridLayoutManager了,相当于我们老早的时候用的ListView和GridView了。所以我们根据布局管理器的不同做了响应的区分。

    getMovementFlags()说明二:其他都好理解,就是这里的return makeMovementFlags(dragFlag, swipeFlag);这句话是最终的返回值,也就是它决定了我们的拖拽或者滑动的方法。第一个参数是拖拽flag,第二个是滑动的flag。

    重新定义DefaultItemTouchHelper

    我们记得上面定义了一个DefaultItemTouchHelper,它的构造中需要传一个ItemTouchHelper.Callback,既然我们实现礼了,我们再把DefaultItemTouchHelper做个封装,使使用者更傻瓜式的调用。

    public class DefaultItemTouchHelper extends YolandaItemTouchHelper {
    
        private DefaultItemTouchHelpCallback itemTouchHelpCallback;
    
        public DefaultItemTouchHelper(DefaultItemTouchHelpCallback.OnItemTouchCallbackListener onItemTouchCallbackListener) {
            super(new DefaultItemTouchHelpCallback(onItemTouchCallbackListener));
            itemTouchHelpCallback = (DefaultItemTouchHelpCallback) getCallback();
        }
    
        /**
         * 设置是否可以被拖拽
         *
         * @param canDrag 是true,否false
         */
        public void setDragEnable(boolean canDrag) {
            itemTouchHelpCallback.setDragEnable(canDrag);
        }
    
        /**
         * 设置是否可以被滑动
         *
         * @param canSwipe 是true,否false
         */
        public void setSwipeEnable(boolean canSwipe) {
            itemTouchHelpCallback.setSwipeEnable(canSwipe);
        }
    }
    

    现在我们看到已经不需要传ItemTouchHelper.Callback给ItemTouchHelper了,只需要传我们在DefaultItemTouchHelpCallback中定义好的OnItemTouchCallbackListener就好了,而且提供了设置是否可以滑动和是否可以拖拽的方法,而OnItemTouchCallbackListener只是通知外部滑动了、删除了,你去更新UI吧。

    这里可以有的同学会有疑问,上面原来不是继承ItemTouchHelper吗?这里咋就变成了YolandaItemTouchHelper了呢?因为我们看到这里多了一句itemTouchHelpCallback = getCallback();,这个getCallback();这个方法是没有的,是我们在YolandaItemTouchHelper中自定义的,因为我们想在DefaultItemTouchHelper中提供外部设置是否可以拖拽和滑动删除的方法,就得拿到这个Callback,所以我看了下源码,我们在ItemTouchHelper构造中把Callback穿进去,它保存的时候一个package级别的成员变量,所以我在Android.support.v7.widget.helper包下新建了一个YolandaItemTouchHelper类:

    public class YolandaItemTouchHelper extends ItemTouchHelper {
        public YolandaItemTouchHelper(Callback callback) {
            super(callback);
        }
    
        public Callback getCallback() {
            return mCallback;
        }
    }
    

    如何投入使用

    好扯淡也扯完了,封装也封装完了,那么接下来就来在Activity中使用下咯:

    recyclerView绑定ItemTouchHelper

    没啥好说的用itemTouchHelper.attachToRecyclerView(recyclerView)绑定recyclerView和ItemTouchHelper,并且只是允许拖拽和滑动删除Item:

    DefaultItemTouchHelper itemTouchHelper = new DefaultItemTouchHelper(onItemTouchCallbackListener);
    itemTouchHelper.attachToRecyclerView(recyclerView);
    itemTouchHelper.setDragEnable(true);
    itemTouchHelper.setSwipeEnable(true);
    

    看到上面还缺少一个onItemTouchCallbackListener吧,这个也比较重要。

    使用Callback自定义的OnItemTouchCallbackListener刷新UI

    我们在自定义Callback的时候不是在onMove()和onSwiped()方法中回调OnItemTouchCallbackListener去更新UI吗?这里就是OnItemTouchCallbackListener如何更新UI的操作了,完成这个操作,那么我们的目的就达到了:

    private DefaultItemTouchHelpCallback.OnItemTouchCallbackListener onItemTouchCallbackListener = new DefaultItemTouchHelpCallback.OnItemTouchCallbackListener() {
        @Override
        public void onSwiped(int adapterPosition) {
            // 滑动删除的时候,从数据源移除,并刷新这个Item。
            if (userInfoList != null) {
                userInfoList.remove(adapterPosition);
                mainAdapter.notifyItemRemoved(adapterPosition);
            }
        }
    
        @Override
        public boolean onMove(int srcPosition, int targetPosition) {
            if (userInfoList != null) {
                // 更换数据源中的数据Item的位置
                Collections.swap(userInfoList, srcPosition, targetPosition);
                // 更新UI中的Item的位置,主要是给用户看到交互效果
                mainAdapter.notifyItemMoved(srcPosition, targetPosition);
                return true;
            }
            return false;
        }
    };
    

    版权声明:转载请注明本文转自严振杰的博客: http://blog.csdn.net/yanzhenjie1003

    相关文章

      网友评论

        本文标题:Android开发之 RecyclerView实现条目Item拖

        本文链接:https://www.haomeiwen.com/subject/wwdanttx.html