【Android 事件分发】ItemTouchHelper 实现拖动排序

举报
韩曙亮 发表于 2022/01/13 00:50:19 2022/01/13
【摘要】 Android 事件分发 系列文章目录 【Android 事件分发】事件分发源码分析 ( 驱动层通过中断传递事件 | WindowManagerService 向 View 层传递事件 ) 【And...

Android 事件分发 系列文章目录


【Android 事件分发】事件分发源码分析 ( 驱动层通过中断传递事件 | WindowManagerService 向 View 层传递事件 )
【Android 事件分发】事件分发源码分析 ( Activity 中各层级的事件传递 | Activity -> PhoneWindow -> DecorView -> ViewGroup )
【Android 事件分发】事件分发源码分析 ( ViewGroup 事件传递机制 一 )
【Android 事件分发】事件分发源码分析 ( ViewGroup 事件传递机制 二 )
【Android 事件分发】事件分发源码分析 ( ViewGroup 事件传递机制 三 )
【Android 事件分发】事件分发源码分析 ( ViewGroup 事件传递机制 四 | View 事件传递机制 )
【Android 事件分发】事件分发源码分析 ( ViewGroup 事件传递机制 五 )
【Android 事件分发】事件分发源码分析 ( ViewGroup 事件传递机制 六 )
【Android 事件分发】事件分发源码分析 ( ViewGroup 事件传递机制 七 )

【Android 事件分发】ItemTouchHelper 简介 ( 拖动/滑动事件 | ItemTouchHelper.Callback 回调 )
【Android 事件分发】ItemTouchHelper 实现侧滑删除 ( 设置滑动方向 | 启用滑动操作 | 滑动距离判定 | 滑动速度判定 | 设置动画时间 | 设置侧滑触发操作 )
【Android 事件分发】ItemTouchHelper 实现拖动排序 ( 设置滑动方向 | 启启用长按拖动功能 | 拖动距离判定 | 设置拖动触发操作 )






一、ItemTouchHelper.Callback 配置侧滑删除




1、设置移动标志 ( 拖动/滑动 )


重写 ItemTouchHelper.CallbackgetMovementFlags 方法 , 在该方法中设置滑动/拖动标志位 ;

滑动 / 拖动 标志 , 可使用 ItemTouchHelper.UP , ItemTouchHelper.DOWN , ItemTouchHelper.LEFT , ItemTouchHelper.RIGHT , 进行或操作得到 ;

        // 设置拖动方向, 此处设置上下拖动事件
        int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
        // 设置滑动方向, 此处设置左右侧滑事件
        int swipeFlags = ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;

  
 
  • 1
  • 2
  • 3
  • 4

再将 拖动标志 和 滑动标志 传入 makeMovementFlags 方法 , 得到一个移动标志位 , 作为 getMovementFlags 方法的返回值 ;

makeMovementFlags(dragFlags, swipeFlags);

  
 
  • 1

public class Callback extends ItemTouchHelper.Callback {
    /**
     * 设置上下左右动作
     * 只有在此处打开了指定方向的设置 , 才可以应用具体方向的拖动
     * @param recyclerView
     * @param viewHolder
     * @return
     */
    @Override
    public int getMovementFlags(@NonNull RecyclerView recyclerView,
                                @NonNull RecyclerView.ViewHolder viewHolder) {
        // 设置拖动方向, 此处设置上下拖动事件
        int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
        // 设置滑动方向, 此处设置左右侧滑事件
        int swipeFlags = ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;
        // 应用 拖动 和 滑动 设置
        return makeMovementFlags(dragFlags, swipeFlags);
    }
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

2、启用长按拖动功能


重写 ItemTouchHelper.CallbackisLongPressDragEnabled 方法 , 将该方法返回值设置为 true , 启用长按拖动功能 ;

public class Callback extends ItemTouchHelper.Callback {
    /**
     * 是否启用长按拖动功能
     * @return
     */
    @Override
    public boolean isLongPressDragEnabled() {
        return true;
    }
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

3、拖动距离判定设置


重写 ItemTouchHelper.CallbackgetMoveThreshold 方法 , 设置用户的拖动距离 , 组件在宽度 / 高度 上移动超过该比例 , 就认为拖动触发, 执行拖动相关操作 ;
设置的是比例值, 返回值为 0.9 , 就意味着滑动宽度/高度的 0.9 倍, 才触发拖动排序 onMove 方法 ;

public class Callback extends ItemTouchHelper.Callback {
    /**
     * 拖动幅度设置
     * 组件在宽度 / 高度 上移动超过该比例 , 就认为拖动触发, 执行拖动相关操作
     * @param viewHolder
     * @return
     */
    @Override
    public float getMoveThreshold(@NonNull RecyclerView.ViewHolder viewHolder) {
        // 该案例中, 拖动操作只能上下进行
        // 拖动超过条目组件高度超过 0.9 倍, 即可触发拖动操作
        return 0.9f;
    }
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

上面案例中设置的拖动幅度是 0.9f , 在 getMovementFlags 方法中设置的拖动方向是上下拖动 , 因此在该案例中 , 上下拖动的幅度必须要在 0.9 倍高度 , 拖动排序功能才能生效 ;

下面的操作中 , 拖动的幅度没有达到 条目组件 高度的 0.9 倍 , 拖动排序功能没有触发 ;

在这里插入图片描述

下面的操作中 , 拖动的幅度超过了 条目组件 高度的 0.9 倍 , 拖动排序功能 触发 ;

在这里插入图片描述


4、设置拖动排序触发操作


重写 ItemTouchHelper.CallbackonMove 方法 , 用户拖动操作定成功后 , 会调用该方法 , 如果拖动判定不成功 , 则不会调用该方法 ;

onMove 方法的 第 2 2 2 参数 , 是拖动的条目索引 ; 第 3 3 3 参数 , 是拖动后的的位置条目 ;
可以通过调用 RecyclerView.ViewHolder 的 getAdapterPosition 方法 , 可以获取该条目的索引值 ;


public class Callback extends ItemTouchHelper.Callback {
    /**
     * 监听滑动事件
     * 滑动分 水平 / 垂直 两个方向
     * @param recyclerView
     * @param viewHolder
     * @param target
     * @return
     */
    @Override
    public boolean onMove(@NonNull RecyclerView recyclerView,
                          @NonNull RecyclerView.ViewHolder viewHolder,
                          @NonNull RecyclerView.ViewHolder target) {
        // 拖动后交换数据, 该方法中交换 Adapter 中的数据, 并刷新界面
        Log.i(TAG, "触发拖动交换条目");
        mAdapter.changeItem(viewHolder.getAdapterPosition(), target.getAdapterPosition());
        return true;
    }
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

5、RecyclerView.Adapter 适配器中的交换排序操作


删除数据列表中的元素 , 并调用 notifyItemRemoved 触发删除动画 ;

    public class Adapter extends RecyclerView.Adapter<Adapter.ViewHolder> {
        /**
         * 交换条目元素
         * @param srcPosition
         * @param dstPosition
         */
        public void changeItem(int srcPosition, int dstPosition) {
            // 交换集合中两个元素位置
            Collections.swap(names, srcPosition, dstPosition);
            // 刷新界面显示
            notifyItemMoved(srcPosition, dstPosition);
        }
	}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13




三、完整代码实现




1、主界面


package kim.hsl.recyclerview;

import android.graphics.Color;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.StaggeredGridLayoutManager;

import java.util.ArrayList;
import java.util.Collections;

public class MainActivity extends AppCompatActivity {

    /**
     * 数据源
     */
    private ArrayList<String> names = new ArrayList<String>();

    /**
     * 当前的 RecyclerView 列表
     */
    private RecyclerView recycler_view;

    /**
     * 布局管理器
     */
    private LinearLayoutManager layoutManager;

    /**
     * 适配器
     */
    private Adapter adapter;

    /**
     * 添加拖动处理
     */
    private ItemTouchHelper mItemTouchHelper;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 初始化数据
        initData();

        //1 . 从布局中获取 RecyclerView
        recycler_view = findViewById(R.id.recycler_view);

        //2 . 创建并设置布局管理器
        //创建布局管理器
        layoutManager = new LinearLayoutManager(
                this,
                RecyclerView.VERTICAL,
                false);

        //设置布局管理器
        recycler_view.setLayoutManager(layoutManager);

        // 设置边距
        recycler_view.addItemDecoration(new ItemDecoration());

        //3 . 创建并设置列表适配器
        adapter = new Adapter();
        recycler_view.setAdapter(adapter);

        //4. 添加拖动事件
        Callback callback = new Callback(adapter);
        mItemTouchHelper = new ItemTouchHelper(callback);
        mItemTouchHelper.attachToRecyclerView(recycler_view);
    }

    /**
     * 初始化数据
     */
    private void initData(){
        names.add("宋江");
        names.add("卢俊义");
        names.add("吴用");
        names.add("公孙胜");
        names.add("关胜");
        names.add("林冲");
        names.add("秦明");
        names.add("呼延灼");
        names.add("花荣");
        names.add("柴进");
        names.add("李应");
        names.add("朱仝");
        names.add("鲁智深");
        names.add("武松");
        names.add("董平");
        names.add("张清");
        names.add("杨志");
        names.add("徐宁");
        names.add("索超");
    }

    /**
     * RecyclerView 适配器
     */
    public class Adapter extends RecyclerView.Adapter<Adapter.ViewHolder> {

        private RecyclerView mRecyclerView;

        @Override
        public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) {
            super.onAttachedToRecyclerView(recyclerView);
            this.mRecyclerView = recyclerView;
        }

        @Override
        public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
            View root_view = LayoutInflater.from(MainActivity.this)
                    .inflate(R.layout.item_recyclerview, parent, false);
            return new ViewHolder(root_view);
        }

        @Override
        public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
            holder.text.setText("" + names.get(position));
        }

        @Override
        public int getItemCount() {
            return names.size();
        }

        public class ViewHolder extends RecyclerView.ViewHolder {
            TextView text;
            public ViewHolder(@NonNull View itemView) {
                super(itemView);
                text = itemView.findViewById(R.id.text);
            }
        }

        /**
         * 删除元素调用的方法
         * @param position
         */
        public void deleteItem(int position) {
            names.remove(position);
            notifyItemRemoved(position);
        }

        /**
         * 交换条目元素
         * @param srcPosition
         * @param dstPosition
         */
        public void changeItem(int srcPosition, int dstPosition) {
            // 交换集合中两个元素位置
            Collections.swap(names, srcPosition, dstPosition);
            // 刷新界面显示
            notifyItemMoved(srcPosition, dstPosition);
        }
    }

}


  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168

2、ItemTouchHelper.Callback 回调类


package kim.hsl.recyclerview;

import android.util.Log;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.RecyclerView;

public class Callback extends ItemTouchHelper.Callback {

    private static final String TAG = "Callback";

    private MainActivity.Adapter mAdapter;

    public Callback(MainActivity.Adapter mAdapter) {
        this.mAdapter = mAdapter;
    }

    /**
     * 设置上下左右动作
     * 只有在此处打开了指定方向的设置 , 才可以应用具体方向的拖动
     * @param recyclerView
     * @param viewHolder
     * @return
     */
    @Override
    public int getMovementFlags(@NonNull RecyclerView recyclerView,
                                @NonNull RecyclerView.ViewHolder viewHolder) {
        // 设置拖动方向, 此处设置上下拖动事件
        int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
        // 设置滑动方向, 此处设置左右侧滑事件
        int swipeFlags = ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;
        // 应用 拖动 和 滑动 设置
        return makeMovementFlags(dragFlags, swipeFlags);
    }

    /*
        以下是拖动相关方法
     */

    /**
     * 是否启用长按拖动功能
     * @return
     */
    @Override
    public boolean isLongPressDragEnabled() {
        return true;
    }

    /**
     * 拖动幅度设置
     * 组件在宽度 / 高度 上移动超过该比例 , 就认为拖动触发, 执行拖动相关操作
     * @param viewHolder
     * @return
     */
    @Override
    public float getMoveThreshold(@NonNull RecyclerView.ViewHolder viewHolder) {
        // 该案例中, 拖动操作只能上下进行
        // 拖动超过条目组件高度超过 0.9 倍, 即可触发拖动操作
        return 0.9f;
    }

    /**
     * 监听滑动事件
     * 滑动分 水平 / 垂直 两个方向
     * @param recyclerView
     * @param viewHolder
     * @param target
     * @return
     */
    @Override
    public boolean onMove(@NonNull RecyclerView recyclerView,
                          @NonNull RecyclerView.ViewHolder viewHolder,
                          @NonNull RecyclerView.ViewHolder target) {
        // 拖动后交换数据, 该方法中交换 Adapter 中的数据, 并刷新界面
        Log.i(TAG, "触发拖动交换条目");
        mAdapter.changeItem(viewHolder.getAdapterPosition(), target.getAdapterPosition());
        return true;
    }

    /*
        以下是滑动相关方法
     */

    /**
     * 是否启用滑动操作
     * @return 是否启用 true 启用, false 不启用
     */
    @Override
    public boolean isItemViewSwipeEnabled() {
        return true;
    }

    /**
     * 用户滑动距离, 设置的是比例值, 返回值为 0.5 , 就意味着滑动宽度/高度的一半, 才触发侧滑 onSwiped 方法
     * @param viewHolder
     * @return
     */
    @Override
    public float getSwipeThreshold(@NonNull RecyclerView.ViewHolder viewHolder) {
        return 0.5f;
    }

    /**
     * 滑动判定速度, 每秒移动的像素个数, 达到该速度后, 才可以被判定为滑动
     * @param defaultValue
     * @return
     */
    @Override
    public float getSwipeEscapeVelocity(float defaultValue) {
        return 5000f;
    }

    /**
     * 手指离开后的动画持续时间
     * @param recyclerView
     * @param animationType
     * @param animateDx
     * @param animateDy
     * @return
     */
    @Override
    public long getAnimationDuration(@NonNull RecyclerView recyclerView,
                                     int animationType,
                                     float animateDx, float animateDy) {
        return 200L;
    }

    /**
     * 滑动时的回调操作
     * @param viewHolder
     * @param direction
     */
    @Override
    public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
        Log.i(TAG, "触发侧滑删除条目");
        // 滑动指定的距离, 达到一定幅度后, 就会触发该方法回调
        // 这里做的是滑动删除功能, 直接删除滑动项
        // 该方法中删除指定条目, 并刷新界面
        mAdapter.deleteItem(viewHolder.getAdapterPosition());
    }
}


  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143

3、执行效果


在这里插入图片描述





三、博客资源



博客资源 :

文章来源: hanshuliang.blog.csdn.net,作者:韩曙亮,版权归原作者所有,如需转载,请联系作者。

原文链接:hanshuliang.blog.csdn.net/article/details/118725533

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。