当前位置:   article > 正文

Android 可长按拖拽的RecyclerView_android recycleview 按住按钮拖拽

android recycleview 按住按钮拖拽

近期项目遇到需要拖拽的RecyclerView来实现需求,首先考虑了ItemTouchHelper 这个类,但是后续使用发现无法把item视图拖出RecyclerView的视图范围,于是打算自定义RecyclerView来实现长按item可在屏幕内随意拖拽,在此简单记录一下。

实现效果

主要思路

  1. 继承RecylerView,重写dispatchTouchEvent
  2. 根据findChildViewUnder和getChildAdapterPosition方法获取到手指所在的View和索引
  3. 利用WindowManager添加获取到的视图到屏幕窗口
  4. 根据手指滑动的位置索引改变添加的视图位置,实现滑动

实现代码

  1. package com.pgc.dragrecyclerviewdemo;
  2. import android.annotation.SuppressLint;
  3. import android.app.Activity;
  4. import android.content.Context;
  5. import android.graphics.Bitmap;
  6. import android.graphics.PixelFormat;
  7. import android.graphics.Rect;
  8. import android.os.Handler;
  9. import android.os.Vibrator;
  10. import android.util.AttributeSet;
  11. import android.view.Gravity;
  12. import android.view.MotionEvent;
  13. import android.view.View;
  14. import android.view.WindowManager;
  15. import android.widget.ImageView;
  16. import androidx.recyclerview.widget.RecyclerView;
  17. import java.util.Objects;
  18. /**
  19. * @explain 长按移动的recyclerView
  20. * @author Created by PengGuiChu on 2022/4/11 11:58
  21. */
  22. public class DragRecyclerView extends RecyclerView {
  23. //拖拽响应的时间 默认为1s
  24. private long mDragResponseMs = 1000;
  25. //是否支持拖拽,默认不支持
  26. private boolean isDrag = false;
  27. //振动器,用于提示替换
  28. private final Vibrator mVibrator;
  29. //拖拽的item的position
  30. private int mDragPosition;
  31. //拖拽的item对应的View
  32. private View mDragView;
  33. //窗口管理器,用于为Activity上添加拖拽的View
  34. private final WindowManager mWindowManager;
  35. //item镜像的布局参数
  36. private WindowManager.LayoutParams mLayoutParams;
  37. //item镜像的 显示镜像,这里用ImageView显示
  38. private ImageView mDragMirrorView;
  39. //item镜像的bitmap
  40. private Bitmap mDragBitmap;
  41. //按下的点到所在item的左边缘距离
  42. private int mPoint2ItemLeft;
  43. private int mPoint2ItemTop;
  44. //DragView到上边缘的距离
  45. private int mOffset2Top;
  46. private int mOffset2Left;
  47. //按下时x,y
  48. private int mDownX;
  49. private int mDownY;
  50. //移动的时x.y
  51. private int mMoveX;
  52. private int mMoveY;
  53. //状态栏高度
  54. private final int mStatusHeight;
  55. //item发生变化的回调接口
  56. private OnItemMoveListener itemMoveListener;
  57. private final Handler mHandler;
  58. /**
  59. * 长按的Runnable
  60. */
  61. private final Runnable mLongClickRunnable = new Runnable() {
  62. @Override
  63. public void run() {
  64. isDrag = true;
  65. mVibrator.vibrate(200);
  66. //隐藏该item
  67. mDragView.setVisibility(INVISIBLE);
  68. //在点击的地方创建并显示item镜像
  69. createDragView(mDragBitmap, mDownX, mDownY);
  70. }
  71. };
  72. public DragRecyclerView(Context context) {
  73. this(context, null);
  74. }
  75. public DragRecyclerView(Context context, AttributeSet attrs) {
  76. this(context, attrs, 0);
  77. }
  78. public DragRecyclerView(Context context, AttributeSet attrs, int defStyleAttr) {
  79. super(context, attrs, defStyleAttr);
  80. mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
  81. mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
  82. mHandler = new Handler();
  83. mStatusHeight = getStatusHeight(context);
  84. }
  85. @Override
  86. public boolean dispatchTouchEvent(MotionEvent ev) {
  87. switch (ev.getAction()) {
  88. case MotionEvent.ACTION_DOWN:
  89. mDownX = (int) ev.getX();
  90. mDownY = (int) ev.getY();
  91. mDragView=findChildViewUnder(mDownX,mDownY);
  92. if (mDragView == null) {
  93. return super.dispatchTouchEvent(ev);
  94. }
  95. //获取按下的position
  96. mDragPosition =getChildAdapterPosition(mDragView);
  97. if (mDragPosition == NO_POSITION) { //无效就返回
  98. return super.dispatchTouchEvent(ev);
  99. }
  100. //延时长按执行mLongClickRunable
  101. mHandler.postDelayed(mLongClickRunnable, mDragResponseMs);
  102. //获取按下的item对应的View 由于存在复用机制,所以需要 处理FirstVisiblePosition
  103. //计算按下的点到所在item的left top 距离
  104. mPoint2ItemLeft = mDownX - mDragView.getLeft();
  105. mPoint2ItemTop = mDownY - mDragView.getTop();
  106. //计算RecyclerView的left top 偏移量:原始距离 - 相对距离就是偏移量
  107. mOffset2Left = (int) ev.getRawX() - mDownX;
  108. mOffset2Top = (int) ev.getRawY() - mDownY;
  109. //开启视图缓存
  110. mDragView.setDrawingCacheEnabled(true);
  111. //获取缓存的中的bitmap镜像 包含了item中的ImageView和TextView
  112. mDragBitmap = Bitmap.createBitmap(mDragView.getDrawingCache());
  113. //释放视图缓存 避免出现重复的镜像
  114. mDragView.destroyDrawingCache();
  115. break;
  116. case MotionEvent.ACTION_MOVE:
  117. mMoveX = (int) ev.getX();
  118. mMoveY = (int) ev.getY();
  119. //如果只在按下的item上移动,未超过边界,就不移除mLongClickRunable
  120. if (!isTouchInItem(mDragView, mMoveX, mMoveY)) {
  121. mHandler.removeCallbacks(mLongClickRunnable);
  122. }
  123. break;
  124. case MotionEvent.ACTION_UP:
  125. mHandler.removeCallbacks(mLongClickRunnable);
  126. //判断是否是点击
  127. if (!isDrag&&mDragMirrorView==null&&itemMoveListener!=null&&Math.abs(mMoveX-mDownX)<20&&Math.abs(mMoveY-mDownY)<20){
  128. itemMoveListener.onItemClick(mDragPosition);
  129. }
  130. break;
  131. default:
  132. break;
  133. }
  134. return super.dispatchTouchEvent(ev);
  135. }
  136. @SuppressLint("ClickableViewAccessibility")
  137. @Override
  138. public boolean onTouchEvent(MotionEvent ev) {
  139. if (isDrag && mDragMirrorView != null) {
  140. switch (ev.getAction()) {
  141. case MotionEvent.ACTION_MOVE:
  142. mMoveX = (int) ev.getX();
  143. mMoveY = (int) ev.getY();
  144. onDragItem(mMoveX, mMoveY);
  145. break;
  146. case MotionEvent.ACTION_UP:
  147. onStopDrag();
  148. isDrag = false;
  149. break;
  150. default:
  151. break;
  152. }
  153. return true;
  154. }
  155. return super.onTouchEvent(ev);
  156. }
  157. /************************对外提供的接口***************************************/
  158. public boolean isDrag() {
  159. return isDrag;
  160. }
  161. public void setDrag(boolean drag) {
  162. isDrag = drag;
  163. }
  164. public long getDragResponseMs() {
  165. return mDragResponseMs;
  166. }
  167. public void setDragResponseMs(long mDragResponseMs) {
  168. this.mDragResponseMs = mDragResponseMs;
  169. }
  170. public void setOnItemMoveListener(OnItemMoveListener itemMoveListener) {
  171. this.itemMoveListener = itemMoveListener;
  172. }
  173. /******************************************************************************/
  174. /**
  175. * 点是否在该View上面
  176. *
  177. * @param view
  178. * @param x
  179. * @param y
  180. * @return
  181. */
  182. private boolean isTouchInItem(View view, int x, int y) {
  183. if (view == null) {
  184. return false;
  185. }
  186. if (view.getLeft() < x && x < view.getRight()
  187. && view.getTop() < y && y < view.getBottom()) {
  188. return true;
  189. } else {
  190. return false;
  191. }
  192. }
  193. /**
  194. * 获取状态栏的高度
  195. *
  196. * @param context
  197. * @return
  198. */
  199. @SuppressLint("PrivateApi")
  200. private static int getStatusHeight(Context context) {
  201. int statusHeight;
  202. Rect localRect = new Rect();
  203. ((Activity) context).getWindow().getDecorView().getWindowVisibleDisplayFrame(localRect);
  204. statusHeight = localRect.top;
  205. if (0 == statusHeight) {
  206. Class<?> localClass;
  207. try {
  208. localClass = Class.forName("com.android.internal.R$dimen");
  209. Object localObject = localClass.newInstance();
  210. int height = Integer.parseInt(Objects.requireNonNull(localClass.getField("status_bar_height").get(localObject)).toString());
  211. statusHeight = context.getResources().getDimensionPixelSize(height);
  212. } catch (Exception e) {
  213. e.printStackTrace();
  214. }
  215. }
  216. return statusHeight;
  217. }
  218. /**
  219. * 停止拖动
  220. */
  221. private void onStopDrag() {
  222. if (mDragView != null) {
  223. mDragView.setVisibility(VISIBLE);
  224. }
  225. removeDragImage();
  226. if (itemMoveListener!=null){
  227. itemMoveListener.onUp(mDragPosition);
  228. }
  229. }
  230. /**
  231. * WindowManager 移除镜像
  232. */
  233. private void removeDragImage() {
  234. if (mDragMirrorView != null) {
  235. mWindowManager.removeView(mDragMirrorView);
  236. mDragMirrorView = null;
  237. }
  238. }
  239. /**
  240. * 拖动item到指定位置
  241. *
  242. * @param x
  243. * @param y
  244. */
  245. private void onDragItem(int x, int y) {
  246. mLayoutParams.x = x - mPoint2ItemLeft + mOffset2Left;
  247. mLayoutParams.y = y - mPoint2ItemTop + mOffset2Top - mStatusHeight;
  248. //更新镜像位置
  249. mWindowManager.updateViewLayout(mDragMirrorView, mLayoutParams);
  250. int[] location = new int[2];
  251. getLocationOnScreen(location);
  252. int pY = location[1];
  253. if (itemMoveListener!=null){
  254. itemMoveListener.onMove(x,y+pY);
  255. }
  256. }
  257. /**
  258. * 创建拖动的镜像
  259. *
  260. * @param bitmap
  261. * @param downX
  262. * @param downY
  263. */
  264. @SuppressLint("RtlHardcoded")
  265. private void createDragView(Bitmap bitmap, int downX, int downY) {
  266. mLayoutParams = new WindowManager.LayoutParams();
  267. mLayoutParams.format = PixelFormat.TRANSLUCENT; //图片之外其他地方透明
  268. mLayoutParams.gravity = Gravity.TOP | Gravity.LEFT; //左 上
  269. //指定位置 其实就是 该 item 对应的 rawX rawY 因为Window 添加View是需要知道 raw x ,y的
  270. mLayoutParams.x = mOffset2Left + (downX - mPoint2ItemLeft);
  271. mLayoutParams.y = mOffset2Top + (downY - mPoint2ItemTop) + mStatusHeight;
  272. //指定布局大小
  273. mLayoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
  274. mLayoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
  275. //透明度
  276. mLayoutParams.alpha = 0.5f;
  277. //指定标志 不能获取焦点和触摸,允许拖动到窗口外
  278. mLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
  279. | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE|WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
  280. mDragMirrorView = new ImageView(getContext());
  281. mDragMirrorView.setImageBitmap(bitmap);
  282. //添加View到窗口中
  283. mWindowManager.addView(mDragMirrorView, mLayoutParams);
  284. }
  285. /**
  286. * item 交换时的回调接口
  287. */
  288. public interface OnItemMoveListener {
  289. void onMove(int x, int y);
  290. void onUp(int position);
  291. void onItemClick(int position);
  292. }
  293. }

使用

  1. <com.pgc.dragrecyclerviewdemo.DragRecyclerView
  2. android:id="@+id/recycler_view"
  3. android:layout_width="match_parent"
  4. android:layout_height="300dp"
  5. app:layout_constraintBottom_toTopOf="@id/view_5"
  6. />
  1. recyclerView.setOnItemMoveListener(new DragRecyclerView.OnItemMoveListener() {
  2. @Override
  3. public void onMove(int x, int y) {
  4. if (x > viewX1 && x < viewX1 + viewWidth1 && y > viewY1 && y < viewY1 + viewHeight1) {
  5. moveType = 1;
  6. } else if (x > viewX2 && x < viewX2 + viewWidth2 && y > viewY2 && y < viewY2 + viewHeight2) {
  7. moveType = 2;
  8. } else if (x > viewX3 && x < viewX3 + viewWidth3 && y > viewY3 && y < viewY3 + viewHeight3) {
  9. moveType = 3;
  10. } else if (x > viewX4 && x < viewX4 + viewWidth4 && y > viewY4 && y < viewY4 + viewHeight4) {
  11. moveType = 4;
  12. } else if (x > viewX5 && x < viewX5 + viewWidth5 && y > viewY5 && y < viewY5 + viewHeight5) {
  13. moveType = 5;
  14. } else {
  15. moveType = -1;
  16. }
  17. if (moveType != -1)
  18. Toast.makeText(getApplicationContext(), "移动到VIEW" + moveType, Toast.LENGTH_LONG).show();
  19. }
  20. @Override
  21. public void onUp(int position) {
  22. Log.d("哦豁",moveType+"----"+position);
  23. }
  24. @Override
  25. public void onItemClick(int position) {
  26. Log.d("进入","点击了="+position);
  27. }
  28. });

下载链接

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/凡人多烦事01/article/detail/286742
推荐阅读
相关标签
  

闽ICP备14008679号