ItemTouchHelper 实现交互动画

目录介绍

  • 01.拖拽需要实现功能
  • 02.几个重要的方法说明
  • 03.简单实现思路
  • 04.拖拽效果上优化
  • 05.完整代码展示

好消息

  • 博客笔记大汇总【16年3月到至今】,包括Java基础及深入知识点,Android技术博客,Python学习笔记等等,还包括平时开发中遇到的bug汇总,当然也在工作之余收集了大量的面试题,长期更新维护并且修正,持续完善……开源的文件是markdown格式的!同时也开源了生活博客,从12年起,积累共计N篇[近100万字,陆续搬到网上],转载请注明出处,谢谢!
  • 链接地址:https://github.com/yangchong211/YCBlogs
  • 如果觉得好,可以star一下,谢谢!当然也欢迎提出建议,万事起于忽微,量变引起质变!

01.拖拽需要实现功能

  • 需要实现拖拽的功能如下所示
    • 长按item后拖动,与其他item交换位置
    • 按住item右面的图标后拖动,与其他item交换位置
    • 左滑item变透明并缩小,超出屏幕后,其他item补上
    • 右滑item变透明并缩小,超出屏幕后,其他item补上

02.几个重要的方法说明

  • 几个重要的方法说明
    • 需要自定义类实现ItemTouchHelper.Callback类,并重写其中几个方法
    isLongPressDragEnabled                  是否可以长按拖拽排序
    isItemViewSwipeEnabled                  Item是否可以被滑动
    getMovementFlags                        当用户拖拽或者滑动Item的时候需要我们告诉系统滑动或者拖拽的方向
    onMove                                  当Item被拖拽的时候被回调
    onSwiped                                当View被滑动删除的时候
    onSelectedChanged                       当item被拖拽或侧滑时触发
    

03.简单实现思路

  • 几个方法中代码思路
    • 要想达到上面功能需求,在getMovementFlags方法中,当用户拖拽或者滑动Item的时候需要我们告诉系统滑动或者拖拽的方向,那我们知道支持拖拽和滑动删除的无非就是LinearLayoutManager和GridLayoutManager了,所以可以根据布局管理器的不同做了响应的区分。
    • 在onMove方法中处理拖拽的回调逻辑,那么什么时候被调用?当Item被拖拽排序移动到另一个Item的位置的时候被调用。在onSwiped方法[当Item被滑动删除到不见]中处理被删除后的逻辑。为了降低代码耦合度,可以通过接口listener回调的方式交给外部处理。
  • 上下拖动时与其他item进行位置交换
    • ItemTouchHelper.Callback本身不具备将两个item互换位置的功能,但RecyclerView可以,我们可以在item拖动的时候把当前item与另一个item的数据位置交换,再调用RecyclerView的notifyItemMoved()方法刷新布局,同时,因为RecyclerView自带item动画,就可以完成上面的交互效果。
  • 左右滑出屏幕时其他item补上
    • 只要在item滑出屏幕时,将对应的数据删掉,再调用RecyclerView的notifyItemRemoved()方法刷新布局即可。

04.拖拽效果上优化

  • 拖拽效果优化
    • 在item被拖拽或侧滑时修改背景色,当动作结束后将背景色恢复回来,而ItemTouchHelper.Callback中正好有对应这两个状态的方法,分别是:onSelectedChanged()、clearView()。那么优化处理其实可以放到这两个方法中处理。
    • 左右滑动使item透明度变浅且缩小该如何实现呢?让item执行了两种属性动画而已,在ItemTouchHelper.Callback中有一个方法可以拿到item被拖拽或滑动时的位移变化,那就是onChildDraw()方法,在该方法中设置item渐变和缩放属性动画。
    • 出现问题,按照上面做法会出现删除后有空白item留出来,那么为什么会出现这种情况呢?并不是多出了两条空白数据,它们是正常的数据,只是看不到了,这是因为RecyclerView条目(itemView)覆用导致的,前面在onChildDraw()方法中对itemView设置了透明和缩小,而一个列表中固定只有几个itemView而已,当那两个透明缩小的itemView被再次使用时,之前设置的透明度和高度比例已经是0,所以就出现了这种情况,解决方法也很简单,只要在item被移除后,将itemView的透明度和高度比例设置回来即可

05.完整代码展示

  • 代码的GitHub地址:https://github.com/yangchong211/YCRefreshView
  • 完整代码如下所示
    /**
     * <pre>
     *     @author 杨充
     *     blog  : https://github.com/yangchong211
     *     time  : 2017/5/2
     *     desc  : 自定义ItemTouchHelper
     *     revise: 参考严正杰大神博客:https://blog.csdn.net/yanzhenjie1003/article/details/51935982
     * </pre>
     */
    public class ItemTouchHelpCallback extends ItemTouchHelper.Callback {
    
        /**
         * Item操作的回调,去更新UI和数据源
         */
        private OnItemTouchCallbackListener onItemTouchCallbackListener;
        /**
         * 是否可以拖拽
         */
        private boolean isCanDrag = false;
        /**
         * 是否可以被滑动
         */
        private boolean isCanSwipe = false;
        /**
         * 按住拖动item的颜色
         */
        private int color = 0;
    
        public ItemTouchHelpCallback(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的颜色
         * @param color     颜色
         */
        public void setColor(@ColorInt int color){
            this.color = color;
        }
    
        /**
         * 当Item被长按的时候是否可以被拖拽
         *
         * @return                      true
         */
        @Override
        public boolean isLongPressDragEnabled() {
            return isCanDrag;
        }
    
        /**
         * Item是否可以被滑动(H:左右滑动,V:上下滑动)
         * isItemViewSwipeEnabled()返回值是否可以拖拽排序,true可以,false不可以
         * @return                      true
         */
        @Override
        public boolean isItemViewSwipeEnabled() {
            return isCanSwipe;
        }
    
        /**
         * 当用户拖拽或者滑动Item的时候需要我们告诉系统滑动或者拖拽的方向
         * 动作标识分:dragFlags和swipeFlags
         * dragFlags:列表滚动方向的动作标识(如竖直列表就是上和下,水平列表就是左和右)
         * wipeFlags:与列表滚动方向垂直的动作标识(如竖直列表就是左和右,水平列表就是上和下)
         *
         * 思路:如果你不想上下拖动,可以将 dragFlags = 0
         *      如果你不想左右滑动,可以将 swipeFlags = 0
         *      最终的动作标识(flags)必须要用makeMovementFlags()方法生成
         */
        @Override
        public int getMovementFlags(@NonNull RecyclerView recyclerView,
                                    @NonNull RecyclerView.ViewHolder viewHolder) {
            RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
            if (layoutManager instanceof 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) 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;
                }
                //第一个参数是拖拽flag,第二个是滑动的flag
                return makeMovementFlags(dragFlag, swipeFlag);
            }
            return 0;
        }
    
    
        /**
         * 当Item被拖拽的时候被回调
         *
         * @param recyclerView          recyclerView
         * @param srcViewHolder         当前被拖拽的item的viewHolder
         * @param targetViewHolder      当前被拖拽的item下方的另一个item的viewHolder
         * @return                      是否被移动
         */
        @Override
        public boolean onMove(@NonNull RecyclerView recyclerView,
                              @NonNull RecyclerView.ViewHolder srcViewHolder,
                              @NonNull RecyclerView.ViewHolder targetViewHolder) {
            if (onItemTouchCallbackListener != null) {
                int srcPosition = srcViewHolder.getAdapterPosition();
                int targetPosition = targetViewHolder.getAdapterPosition();
                return onItemTouchCallbackListener.onMove(srcPosition, targetPosition);
            }
            return false;
        }
    
    
        /**
         * 当item侧滑出去时触发(竖直列表是侧滑,水平列表是竖滑)
         *
         * @param viewHolder            viewHolder
         * @param direction             滑动的方向
         */
        @Override
        public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
            if (onItemTouchCallbackListener != null) {
                onItemTouchCallbackListener.onSwiped(viewHolder.getAdapterPosition());
            }
        }
    
        /**
         * 当item被拖拽或侧滑时触发
         *
         * @param viewHolder            viewHolder
         * @param actionState           当前item的状态
         */
        @Override
        public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
            super.onSelectedChanged(viewHolder, actionState);
            //不管是拖拽或是侧滑,背景色都要变化
            if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) {
                if (color==0){
                    viewHolder.itemView.setBackgroundColor(viewHolder.itemView.getContext()
                            .getResources().getColor(android.R.color.darker_gray));
                }else {
                    viewHolder.itemView.setBackgroundColor(color);
                }
            }
        }
    
    
        /**
         * 当item的交互动画结束时触发
         *
         * @param recyclerView          recyclerView
         * @param viewHolder            viewHolder
         */
        @Override
        public void clearView(@NonNull RecyclerView recyclerView,
                              @NonNull RecyclerView.ViewHolder viewHolder) {
            super.clearView(recyclerView, viewHolder);
            viewHolder.itemView.setBackgroundColor(viewHolder.itemView.getContext().getResources()
                    .getColor(android.R.color.white));
            viewHolder.itemView.setAlpha(1);
            viewHolder.itemView.setScaleY(1);
        }
    
    
        @Override
        public void onChildDraw(@NonNull Canvas c, @NonNull RecyclerView recyclerView,
                                @NonNull RecyclerView.ViewHolder viewHolder,
                                float dX, float dY, int actionState, boolean isCurrentlyActive) {
            super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
            if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
                float value = 1 - Math.abs(dX) / viewHolder.itemView.getWidth();
                viewHolder.itemView.setAlpha(value);
                viewHolder.itemView.setScaleY(value);
            }
        }
    
    
        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);
        }
    }
    
  • 如何使用,代码如下所示
    ItemTouchHelpCallback callback = new ItemTouchHelpCallback((srcPosition, targetPosition) -> {
            if (imageBeans != null) {
                try {
                    // 更换数据源中的数据Item的位置。更改list中开始和结尾position的位置
                    Collections.swap(imageBeans, srcPosition, targetPosition);
                    // 更新UI中的Item的位置,主要是给用户看到交互效果
                    mAdapter.notifyItemMoved(srcPosition, targetPosition);
                } catch (Exception e){
                    e.printStackTrace();
                }
                return true;
            }
            return true;
        });
    callback.setDragEnable(true);
    callback.setSwipeEnable(true);
    callback.setColor(this.getResources().getColor(R.color.base_background_block));
    //创建helper对象,callback监听recyclerView item 的各种状态
    ItemTouchHelper itemTouchHelper = new ItemTouchHelper(callback);
    try{
        //关联recyclerView,一个helper对象只能对应一个recyclerView
        itemTouchHelper.attachToRecyclerView(recyclerView);
    }catch (Exception e){
        e.printStackTrace();
    }
    

开源库地址[融合大多数recyclerView使用案例,可以直接下载demo]:https://github.com/yangchong211/YCRefreshView

  • 00.RecyclerView复杂封装库
    • 几乎融合了该系列博客中绝大部分的知识点,欢迎一遍看博客一遍实践,一步步从简单实现功能强大的库
  • 01.RecyclerView
    • RecycleView的结构,RecyclerView简单用法介绍
  • 02.Adapter
    • RecyclerView.Adapter扮演的角色,一般常用的重写方法说明,数据变更通知之观察者模式,查看.notifyChanged();源码
  • 03.ViewHolder
    • ViewHolder的作用,如何理解对于ViewHolder对象的数量“够用”之后就停止调用onCreateViewHolder方法,ViewHolder简单封装
  • 04.LayoutManager
    • LayoutManager作用是什么?setLayoutManager源码分析
  • 05.SnapHelper
    • SnapHelper作用,什么是Fling操作 ,SnapHelper类重要的方法,
  • 06.ItemTouchHelper
  • 07.SpanSizeLookup
    • SpanSizeLookup如何使用,同时包含列表,2列的网格,3列的网格如何优雅实现?
  • 08.ItemDecoration
    • ItemDecoration的用途,addItemDecoration()源码分析
  • 09.RecycledViewPool
    • RecyclerViewPool用于多个RecyclerView之间共享View。
  • 11.RecyclerView上拉加载
    • 添加recyclerView的滑动事件,上拉加载分页数据,设置上拉加载的底部footer布局,显示和隐藏footer布局
  • 12.RecyclerView缓存原理
    • RecyclerView做性能优化要说复杂也复杂,比如说布局优化,缓存,预加载,复用池,刷新数据等等
  • 13.SnapHelper源码分析
    • SnapHelper旨在支持RecyclerView的对齐方式,也就是通过计算对齐RecyclerView中TargetView 的指定点或者容器中的任何像素点。
  • 16.自定义SnapHelper
    • 自定义SnapHelper
  • 19.自定义ItemDecoration分割线
    • 需要自定义类实现RecyclerView.ItemDecoration类,并选择重写合适方法
  • 22.RecyclerView问题汇总
    • getLayoutPosition()和getAdapterPosition()的区别
  • 23.RecyclerView滑动冲突
    • 01.如何判断RecyclerView控件滑动到顶部和底部
    • 02.RecyclerView嵌套RecyclerView 条目自动上滚的Bug
    • 03.ScrollView嵌套RecyclerView滑动冲突
    • 04.ViewPager嵌套水平RecyclerView横向滑动到底后不滑动ViewPager
    • 05.RecyclerView嵌套RecyclerView的滑动冲突问题
    • 06.RecyclerView使用Glide加载图片导致图片错乱问题解决
  • 24.ScrollView嵌套RecyclerView问题
    • 要实现在NestedScrollView中嵌入一个或多个RecyclerView,会出现滑动冲突,焦点抢占,显示不全等。如何处理?

其他介绍

01.关于博客汇总链接

02.关于我的博客

代码案例:https://github.com/yangchong211/YCRefreshView

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

推荐阅读更多精彩内容