当前位置:   article > 正文

一个实现item可手动拖拽的ListView和GridView_安卓listview内部item可随意拖动放置

安卓listview内部item可随意拖动放置

 

上篇博客分享了一个实现ListView中item交换动画的控件(戳这里查看),但是有些情况下我们的需求比这种效果要复杂。比如说需要手动拖拽item来完成item交换的交互。像这样:

还有这样:

 

这次分享的控件就实现这样的功能,下面开始正文。

先说实现item拖拽功能的DragListView。上代码:

  1. public class DragListView extends ListView {
  2. /**
  3. * 速度模板,影响视图移动时的速度变化
  4. * <p>
  5. * MODE_LINEAR // 线性变化模式
  6. * MODE_ACCELERATE // 加速模式
  7. * MODE_DECELERATE // 减速模式
  8. * MODE_ACCELERATE_DECELERATE // 先加速后加速模式
  9. */
  10. public static final int MODE_LINEAR = 0x001;
  11. public static final int MODE_ACCELERATE = 0x002;
  12. public static final int MODE_DECELERATE = 0x003;
  13. public static final int MODE_ACCELERATE_DECELERATE = 0x004;
  14. private Context context;
  15. // 拖动时的视图
  16. private View dragView;
  17. private WindowManager windowManager;
  18. private WindowManager.LayoutParams windowLayoutParams;
  19. private BaseDragAdapter adapter;
  20. /**
  21. * 可设置选项
  22. */
  23. // 移动动画储持续时间,单位毫秒
  24. private long duration = 300;
  25. // 速度模板
  26. private int speedMode = MODE_ACCELERATE_DECELERATE;
  27. // 自动滚动的速度
  28. private int scrollSpeed = 50;
  29. /**
  30. * 运行参数
  31. */
  32. // 拖动块的原始坐标
  33. private int originalPosition = -1;
  34. // 拖动块当前所在坐标
  35. private int currentPosition = -1;
  36. // 用于记录上次点击事件MotionEvent.getX();
  37. private int lastX;
  38. // 用于记录上次点击事件MotionEvent.getY();
  39. private int lastY;
  40. // 用于记录上次点击事件MotionEvent.getRawX();
  41. private int lastRawX;
  42. // 用于记录上次点击事件MotionEvent.getRawY();
  43. private int lastRawY;
  44. // 拖动块中心点x坐标,用于判断拖动块所处的列表位置
  45. private int dragCenterX;
  46. // 拖动块中心点y坐标,用于判断拖动块所处的列表位置
  47. private int dragCenterY;
  48. // 滑动上边界,拖动块中心超过该边界时列表自动向下滑动
  49. private int upScrollBorder;
  50. // 滑动下边界,拖动块中心超过该边界时列表自动向上滑动
  51. private int downScrollBorder;
  52. // 状态栏高度
  53. private int statusHeight;
  54. // 拖动时的列表刷新标识符
  55. private boolean dragRefresh;
  56. // 拖动锁定标记,为false时选中块可被拖动
  57. private boolean dragLock = true;
  58. // 动画列表,存放当前屏幕上正在播放的所有滑动动画的动画对象
  59. private ArrayList<Animator> animatorList;
  60. // 视图列表,存放当前屏幕上正在播放的所有滑动动画的视图对象
  61. private ArrayList<View> dragViews;
  62. /**
  63. * 可监听接口
  64. */
  65. // 拖动块视图对象生成器,可通过设置该接口自定义一个拖动视图的样式,不设置时会有默认实现
  66. private DragViewCreator dragViewCreator;
  67. // 拖动监听接口,拖动开始和结束时会在该接口回调
  68. private OnDragingListener dragingListener;
  69. // 当前拖动目标位置改变时,每次改变都会在该接口回调
  70. private OnDragTargetChangedListener targetChangedListener;
  71. // 内部接口,动画观察者,滑动动画结束是回调
  72. private AnimatorObserver animatorObserver;
  73. private Handler handler = new Handler();
  74. // 列表自动滚动线程
  75. private Runnable scrollRunnable = new Runnable() {
  76. @Override
  77. public void run() {
  78. int scrollY;
  79. // 滚动到顶或到底时停止滚动
  80. if (getFirstVisiblePosition() == 0 || getLastVisiblePosition() == getCount() - 1) {
  81. handler.removeCallbacks(scrollRunnable);
  82. }
  83. // 触控点y坐标超过上边界时,出发列表自动向下滚动
  84. if (lastY > upScrollBorder) {
  85. scrollY = scrollSpeed;
  86. handler.postDelayed(scrollRunnable, 25);
  87. }
  88. // 触控点y坐标超过下边界时,出发列表自动向上滚动
  89. else if (lastY < downScrollBorder) {
  90. scrollY = -scrollSpeed;
  91. handler.postDelayed(scrollRunnable, 25);
  92. }
  93. // // 触控点y坐标处于上下边界之间时,停止滚动
  94. else {
  95. scrollY = 0;
  96. handler.removeCallbacks(scrollRunnable);
  97. }
  98. smoothScrollBy(scrollY, 10);
  99. }
  100. };
  101. public DragListView(Context context) {
  102. super(context);
  103. init(context);
  104. }
  105. public DragListView(Context context, AttributeSet attrs) {
  106. super(context, attrs);
  107. init(context);
  108. }
  109. public DragListView(Context context, AttributeSet attrs, int defStyleAttr) {
  110. super(context, attrs, defStyleAttr);
  111. init(context);
  112. }
  113. /**
  114. * 初始化方法
  115. *
  116. * @param context
  117. */
  118. private void init(Context context) {
  119. this.context = context;
  120. statusHeight = getStatusHeight();
  121. windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
  122. animatorList = new ArrayList<>();
  123. dragViews = new ArrayList<>();
  124. // 拖动块视图对象生成器的默认实现,返回一个与被拖动项外观一致的ImageView
  125. dragViewCreator = new DragViewCreator() {
  126. @Override
  127. public View createDragView(int width, int height, Bitmap viewCache) {
  128. ImageView imageView = new ImageView(DragListView.this.context);
  129. imageView.setImageBitmap(viewCache);
  130. return imageView;
  131. }
  132. };
  133. }
  134. @Override
  135. public boolean dispatchTouchEvent(MotionEvent motionEvent) {
  136. switch (motionEvent.getAction()) {
  137. case MotionEvent.ACTION_DOWN:
  138. downScrollBorder = getHeight() / 5;
  139. upScrollBorder = getHeight() * 4 / 5;
  140. // 手指按下时记录相关坐标
  141. lastX = (int) motionEvent.getX();
  142. lastY = (int) motionEvent.getY();
  143. lastRawX = (int) motionEvent.getRawX();
  144. lastRawY = (int) motionEvent.getRawY();
  145. currentPosition = pointToPosition(lastRawX, lastRawY);
  146. if (currentPosition == AdapterView.INVALID_POSITION || !adapter.isDragAvailable(currentPosition)) {
  147. return true;
  148. }
  149. originalPosition = currentPosition;
  150. break;
  151. }
  152. return super.dispatchTouchEvent(motionEvent);
  153. }
  154. @Override
  155. public boolean onTouchEvent(MotionEvent motionEvent) {
  156. switch (motionEvent.getAction()) {
  157. case MotionEvent.ACTION_MOVE:
  158. if (!dragLock) {
  159. int currentRawX = (int) motionEvent.getRawX();
  160. int currentRawY = (int) motionEvent.getRawY();
  161. if (dragView == null) {
  162. createDragImageView(getChildAt(pointToPosition(lastRawX, lastRawY) - getFirstVisiblePosition()));
  163. getChildAt(originalPosition - getFirstVisiblePosition()).setVisibility(View.INVISIBLE);
  164. if (dragingListener != null) {
  165. dragingListener.onStart(originalPosition);
  166. }
  167. }
  168. drag(currentRawY - lastRawY);
  169. if (dragingListener != null) {
  170. dragingListener.onDraging((int) motionEvent.getX(), (int) motionEvent.getY(), currentRawX, currentRawY);
  171. }
  172. int position = pointToPosition(dragCenterX, dragCenterY);
  173. // 满足交换条件时让目标位置的原有视图上滑或下滑
  174. if (position != AdapterView.INVALID_POSITION && currentPosition != position && adapter.isDragAvailable(position)) {
  175. translation(position, currentPosition);
  176. currentPosition = position;
  177. if (targetChangedListener != null) {
  178. targetChangedListener.onTargetChanged(currentPosition);
  179. }
  180. }
  181. // 更新点击位置
  182. lastX = (int) motionEvent.getX();
  183. lastY = (int) motionEvent.getY();
  184. lastRawX = currentRawX;
  185. lastRawY = currentRawY;
  186. // 返回true消耗掉这次点击事件,防止ListView本身接收到这次点击事件后触发滚动
  187. return true;
  188. }
  189. break;
  190. case MotionEvent.ACTION_UP:
  191. // 手指抬起时,如果所有滑动动画都已播放完毕,则直接执行拖动完成逻辑
  192. if (animatorList.size() == 0) {
  193. resetDataAndView();
  194. if (dragingListener != null) {
  195. dragingListener.onFinish(currentPosition);
  196. }
  197. }
  198. // 如果还有未播放完成的滑动动画,则注册观察者,延时执行拖动完成逻辑
  199. else {
  200. animatorObserver = new AnimatorObserver() {
  201. @Override
  202. public void onAllAnimatorFinish() {
  203. resetDataAndView();
  204. if (dragingListener != null) {
  205. dragingListener.onFinish(currentPosition);
  206. }
  207. }
  208. };
  209. }
  210. break;
  211. }
  212. return super.onTouchEvent(motionEvent);
  213. }
  214. /**
  215. * 创建拖动块视图方法
  216. *
  217. * @param view 被拖动位置的视图对象
  218. */
  219. private void createDragImageView(View view) {
  220. if (view == null) {
  221. return;
  222. }
  223. removeDragImageView();
  224. int[] location = new int[2];
  225. view.getLocationOnScreen(location);
  226. view.setDrawingCacheEnabled(true);
  227. Bitmap bitmap = Bitmap.createBitmap(view.getDrawingCache());
  228. view.destroyDrawingCache();
  229. windowLayoutParams = new WindowManager.LayoutParams();
  230. windowLayoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
  231. windowLayoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT;
  232. windowLayoutParams.format = PixelFormat.TRANSPARENT;
  233. windowLayoutParams.gravity = Gravity.TOP | Gravity.LEFT;
  234. windowLayoutParams.x = location[0];
  235. windowLayoutParams.y = location[1] - statusHeight;
  236. windowLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
  237. dragCenterX = windowLayoutParams.x + view.getWidth() / 2;
  238. dragCenterY = windowLayoutParams.y + view.getHeight() / 2;
  239. dragView = dragViewCreator.createDragView(view.getWidth(), view.getHeight(), bitmap);
  240. if (dragView == null) {
  241. throw new NullPointerException("dragView can not be null");
  242. } else {
  243. windowManager.addView(dragView, windowLayoutParams);
  244. }
  245. }
  246. /**
  247. * 移除拖动视图方法
  248. */
  249. private void removeDragImageView() {
  250. if (dragView != null && windowManager != null) {
  251. windowManager.removeView(dragView);
  252. dragView = null;
  253. windowLayoutParams = null;
  254. }
  255. }
  256. /**
  257. * 拖动方法
  258. *
  259. * @param dy
  260. */
  261. private void drag(int dy) {
  262. dragCenterY += dy;
  263. windowLayoutParams.y += dy;
  264. windowManager.updateViewLayout(dragView, windowLayoutParams);
  265. handler.post(scrollRunnable);
  266. }
  267. /**
  268. * 移动指定位置视图方法
  269. *
  270. * @param fromPosition 移动起始位置
  271. * @param toPosition 移动目标位置
  272. */
  273. private void translation(int fromPosition, int toPosition) {
  274. View fromView = getChildAt(fromPosition - getFirstVisiblePosition());
  275. View toView = getChildAt(toPosition - getFirstVisiblePosition());
  276. if (fromView == null || toView == null) {
  277. return;
  278. }
  279. float distance = (toView.getY() - toView.getTranslationY()) - (fromView.getY() - fromView.getTranslationY());
  280. ObjectAnimator animator = ObjectAnimator.ofFloat(fromView, "translationY", 0, distance);
  281. animator.setDuration(duration);
  282. animator.setInterpolator(getAnimatorInterpolator());
  283. animator.addListener(new AnimatorListenerAdapter() {
  284. @Override
  285. public void onAnimationEnd(Animator animation) {
  286. animatorList.remove(animation);
  287. // 所有滑动动画都播放结束时,执行相关操作
  288. if (animatorList.size() == 0) {
  289. // 重置所有滑动过的视图的translateY,避免列表刷新后视图重叠
  290. resetTranslate(dragViews);
  291. dragViews.clear();
  292. adapter.exchangeData(originalPosition, currentPosition);
  293. addOnLayoutChangeListener(new OnLayoutChangeListener() {
  294. @Override
  295. public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
  296. if (dragRefresh) {
  297. removeOnLayoutChangeListener(this);
  298. resetChildVisibility();
  299. getChildAt(currentPosition - getFirstVisiblePosition()).setVisibility(View.INVISIBLE);
  300. originalPosition = currentPosition;
  301. dragRefresh = false;
  302. if (animatorObserver != null) {
  303. animatorObserver.onAllAnimatorFinish();
  304. animatorObserver = null;
  305. }
  306. }
  307. }
  308. });
  309. dragRefresh = true;
  310. adapter.notifyDataSetChanged();
  311. }
  312. }
  313. });
  314. animatorList.add(animator);
  315. dragViews.add(fromView);
  316. animator.start();
  317. }
  318. /**
  319. * 重置列表所有项的可见性方法
  320. */
  321. private void resetChildVisibility() {
  322. for (int i = 0; i < getChildCount(); i++) {
  323. View child = getChildAt(i);
  324. if (child != null) {
  325. child.setVisibility(VISIBLE);
  326. }
  327. }
  328. }
  329. /**
  330. * 重置指定视图的translateY属性方法
  331. *
  332. * @param list
  333. */
  334. private void resetTranslate(ArrayList<View> list) {
  335. for (int i = 0; i < list.size(); i++) {
  336. if (list.get(i) != null) {
  337. list.get(i).setTranslationY(0);
  338. }
  339. }
  340. }
  341. /**
  342. * 重置数据和视图相关数据方法
  343. */
  344. private void resetDataAndView() {
  345. if (currentPosition == -1) {
  346. return;
  347. }
  348. getChildAt(currentPosition - getFirstVisiblePosition()).setVisibility(View.VISIBLE);
  349. originalPosition = -1;
  350. currentPosition = -1;
  351. handler.removeCallbacks(scrollRunnable);
  352. removeDragImageView();
  353. }
  354. @Override
  355. public void setAdapter(ListAdapter adapter) {
  356. if (adapter instanceof BaseDragAdapter) {
  357. this.adapter = (BaseDragAdapter) adapter;
  358. super.setAdapter(adapter);
  359. } else {
  360. throw new IllegalStateException("the adapter must extends BaseDragAdapter");
  361. }
  362. }
  363. /**
  364. * 根据速度模板创建动画迭代器
  365. *
  366. * @return
  367. */
  368. private Interpolator getAnimatorInterpolator() {
  369. switch (speedMode) {
  370. case MODE_LINEAR:
  371. return new LinearInterpolator();
  372. case MODE_ACCELERATE:
  373. return new AccelerateInterpolator();
  374. case MODE_DECELERATE:
  375. return new DecelerateInterpolator();
  376. case MODE_ACCELERATE_DECELERATE:
  377. return new AccelerateDecelerateInterpolator();
  378. default:
  379. return null;
  380. }
  381. }
  382. /**
  383. * 拖动解锁方法,调用者需手动调用该方法后才能开启列表拖动功能
  384. */
  385. public void unlockDrag() {
  386. dragLock = false;
  387. }
  388. /**
  389. * 拖动锁定方法,调用者调用该方法后关闭列表拖动功能
  390. */
  391. public void lockDrag() {
  392. dragLock = true;
  393. }
  394. /**
  395. * 设置移动动画持续时间
  396. *
  397. * @param duration 时间,单位毫秒
  398. */
  399. public void setDuration(long duration) {
  400. this.duration = duration;
  401. }
  402. /**
  403. * 设置速度模式,可选项:
  404. * MODE_LINEAR 线性变化模式
  405. * MODE_ACCELERATE 加速模式
  406. * MODE_DECELERATE 减速模式
  407. * MODE_ACCELERATE_DECELERATE 先加速后加速模式
  408. *
  409. * @param speedMode
  410. */
  411. public void setSpeedMode(int speedMode) {
  412. this.speedMode = speedMode;
  413. }
  414. /**
  415. * 设置自动滚动速度
  416. *
  417. * @param scrollSpeed 速度,单位:dp/10ms
  418. */
  419. public void setScrollSpeed(int scrollSpeed) {
  420. this.scrollSpeed = scrollSpeed;
  421. }
  422. /**
  423. * 设置拖动块视图对象生成器方法
  424. *
  425. * @param creator
  426. */
  427. public void setDragViewCreator(DragViewCreator creator) {
  428. if (creator == null) {
  429. return;
  430. }
  431. this.dragViewCreator = creator;
  432. }
  433. /**
  434. * 设置拖动监听接口
  435. *
  436. * @param dragingListener
  437. */
  438. public void setOnDragingListener(OnDragingListener dragingListener) {
  439. this.dragingListener = dragingListener;
  440. }
  441. /**
  442. * 设置拖动目标位置改变监听接口
  443. *
  444. * @param targetChangedListener
  445. */
  446. public void setOnDragTargetChangedListener(OnDragTargetChangedListener targetChangedListener) {
  447. this.targetChangedListener = targetChangedListener;
  448. }
  449. private int getStatusHeight() {
  450. int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
  451. if (resourceId > 0) {
  452. return context.getResources().getDimensionPixelSize(resourceId);
  453. }
  454. return 0;
  455. }
  456. /**
  457. * 动画观察者
  458. */
  459. private interface AnimatorObserver {
  460. /**
  461. * 滑动动画播放结束时回调
  462. */
  463. void onAllAnimatorFinish();
  464. }
  465. /**
  466. * 拖动块视图对象生成器
  467. */
  468. public interface DragViewCreator {
  469. /**
  470. * 创建拖动块视图对象方法,可通过实现该方法自定义拖动块样式
  471. */
  472. View createDragView(int width, int height, Bitmap viewCache);
  473. }
  474. /**
  475. * 拖动监听接口
  476. */
  477. public interface OnDragingListener {
  478. /**
  479. * 拖动开始时回调
  480. *
  481. * @param startPosition 拖动起始坐标
  482. */
  483. void onStart(int startPosition);
  484. /**
  485. * 拖动过程中回调
  486. *
  487. * @param x 触控点相对ListView的x坐标
  488. * @param y 触控点相对ListView的y坐标
  489. * @param rawX 触控点相对屏幕的x坐标
  490. * @param rawY 触控点相对屏幕的y坐标
  491. */
  492. void onDraging(int x, int y, int rawX, int rawY);
  493. /**
  494. * 拖动结束时回调
  495. *
  496. * @param finalPosition 拖动终点坐标
  497. */
  498. void onFinish(int finalPosition);
  499. }
  500. /**
  501. * 拖动目标位置改变监听接口
  502. */
  503. public interface OnDragTargetChangedListener {
  504. /**
  505. * 拖动过程中,每次目标位置改变,会在该方法回调
  506. *
  507. * @param targetPosition 拖动目标位置坐标
  508. */
  509. void onTargetChanged(int targetPosition);
  510. }
  511. }

简单讲一下实现原理。手指按下时通过ListView的getChildAt方法获得按下位置的item并获取其视图缓存,也就是这句话:

  1. view.setDrawingCacheEnabled(true);
  2. Bitmap bitmap = Bitmap.createBitmap(view.getDrawingCache());
  3. view.destroyDrawingCache();

然后新建一个View把这个缓存塞进去并置于屏幕之上,并隐藏原来的item,让人看起来就好像是item被“拽”了下来,也就是这句话:

windowManager.addView(dragView, windowLayoutParams);

手指移动时,改变这个View的LayoutParams的y坐标值,让它跟随手指移动,也就是这两句话:

  1. windowLayoutParams.y += dy;
  2. windowManager.updateViewLayout(dragView, windowLayoutParams);

拖拽过程中,当判定交换行为发生时,用一个属性动画不断改变目标item的translationY属性来实现交换效果,也就是这句话:

ObjectAnimator animator = ObjectAnimator.ofFloat(fromView, "translationY", 0, distance);

具体代码大家可以看注释,应该写得比较清楚了。

要特别说明的是,DragListView的setAdapter方法被重写了,只接收BaseDragAdapter的继承类,BaseDragAdapter长这样:
 

  1. public abstract class BaseDragAdapter extends BaseAdapter {
  2. /**
  3. * 调用者需实现该方法,返回列表的所有数据集合
  4. *
  5. * @return
  6. */
  7. public abstract List getDataList();
  8. /**
  9. * 调用者可实现该方法自定义某一项是否可被拖动
  10. *
  11. * @param position
  12. * @return
  13. */
  14. public abstract boolean isDragAvailable(int position);
  15. /**
  16. * 实现数据交换方法
  17. *
  18. * @param oldPosition
  19. * @param newPosition
  20. */
  21. public void exchangeData(int oldPosition, int newPosition) {
  22. List list = getDataList();
  23. if (list == null) {
  24. return;
  25. }
  26. Object temp = list.get(oldPosition);
  27. if (oldPosition < newPosition) {
  28. for (int i = oldPosition; i < newPosition; i++) {
  29. Collections.swap(list, i, i + 1);
  30. }
  31. } else if (oldPosition > newPosition) {
  32. for (int i = oldPosition; i > newPosition; i--) {
  33. Collections.swap(list, i, i - 1);
  34. }
  35. }
  36. list.set(newPosition, temp);
  37. }
  38. }

BaseDragAdapter的目的是替调用者封装一些必要的操作,它给普通的BaseAdapter增加了两个需要实现的抽象方法:getDataList()和isDragAvailable()。getDataList()返回ListView 的数据列表即可,isDragAvailable()用来让调用者决定某个item是否可被拖拽,比如说需求是列表的第一项不可被拖拽,只需要实现isDragAvailable方法,在position=0时返回false即可。

然后就可以使用了。先写一个item的布局:
 

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. android:id="@+id/item_layout"
  4. android:layout_width="match_parent"
  5. android:layout_height="wrap_content"
  6. android:background="#FFFAFA"
  7. android:orientation="vertical">
  8. <TextView
  9. android:id="@+id/content_textview"
  10. android:layout_width="wrap_content"
  11. android:layout_height="wrap_content"
  12. android:paddingLeft="15dp"
  13. android:paddingTop="20dp"
  14. android:paddingRight="15dp"
  15. android:paddingBottom="20dp"
  16. android:text="我是内容"
  17. android:textSize="20sp" />
  18. <View
  19. android:id="@+id/divider_line"
  20. android:layout_width="match_parent"
  21. android:layout_height="1dp"
  22. android:layout_marginLeft="15dp"
  23. android:background="#eeeeee" />
  24. </LinearLayout>

再简单写一个适配器TestListViewAdapter继承自BaseDragAdapter:

  1. public class TestListViewAdapter extends BaseDragAdapter {
  2. private Context context;
  3. private int resourceId;
  4. private ArrayList<String> list;
  5. private Vibrator vibrator;
  6. private OnItemLongClickListener listener;
  7. public TestListViewAdapter(Context context, int resourceId, ArrayList<String> list) {
  8. this.context = context;
  9. this.resourceId = resourceId;
  10. this.list = list;
  11. this.vibrator = (Vibrator) context.getSystemService(context.VIBRATOR_SERVICE);
  12. }
  13. @Override
  14. public List getDataList() {
  15. return list;
  16. }
  17. @Override
  18. public boolean isDragAvailable(int position) {
  19. return true;
  20. }
  21. @Override
  22. public int getCount() {
  23. return list.size();
  24. }
  25. @Override
  26. public Object getItem(int position) {
  27. return list.get(position);
  28. }
  29. @Override
  30. public long getItemId(int position) {
  31. return position;
  32. }
  33. @Override
  34. public View getView(final int position, View convertView, ViewGroup parent) {
  35. View view;
  36. ViewHolder viewHolder;
  37. if (convertView == null) {
  38. view = LayoutInflater.from(context).inflate(resourceId, null);
  39. viewHolder = new ViewHolder();
  40. viewHolder.itemLayout = view.findViewById(R.id.item_layout);
  41. viewHolder.contentTextView = view.findViewById(R.id.content_textview);
  42. viewHolder.dividerLine = view.findViewById(R.id.divider_line);
  43. view.setTag(viewHolder);
  44. } else {
  45. view = convertView;
  46. viewHolder = (ViewHolder) view.getTag();
  47. }
  48. viewHolder.contentTextView.setText(list.get(position));
  49. viewHolder.dividerLine.setVisibility(position != list.size() - 1 ? View.VISIBLE : View.INVISIBLE);
  50. viewHolder.itemLayout.setOnLongClickListener(new View.OnLongClickListener() {
  51. @Override
  52. public boolean onLongClick(View v) {
  53. vibrator.vibrate(100);
  54. if (listener != null) {
  55. listener.onItemLongClick(position);
  56. }
  57. return false;
  58. }
  59. });
  60. return view;
  61. }
  62. public void setOnItemLongClickListener(OnItemLongClickListener listener) {
  63. this.listener = listener;
  64. }
  65. public interface OnItemLongClickListener {
  66. void onItemLongClick(int position);
  67. }
  68. class ViewHolder {
  69. LinearLayout itemLayout;
  70. TextView contentTextView;
  71. View dividerLine;
  72. }
  73. }

代码很简单,就不多说了。

最后就可以使用了,Activity里这样写:
 

  1. private DragListView dragListView;
  2. private TestListViewAdapter adapter;
  3. private ArrayList<String> list;
  4. @Override
  5. protected void onCreate(Bundle savedInstanceState) {
  6. super.onCreate(savedInstanceState);
  7. setContentView(R.layout.activity_drag_listview_test);
  8. initData();
  9. initView();
  10. }
  11. private void initData() {
  12. list = new ArrayList<>();
  13. for (int i = 1; i <= 40; i++) {
  14. list.add("我是第" + i + "条数据");
  15. }
  16. }
  17. private void initView() {
  18. dragListView = findViewById(R.id.drag_listview);
  19. dragListView.setOnDragingListener(new DragListView.OnDragingListener() {
  20. @Override
  21. public void onStart(int startPosition) {
  22. }
  23. @Override
  24. public void onDraging(int x, int y, int rawX, int rawY) {
  25. }
  26. @Override
  27. public void onFinish(int finalPosition) {
  28. dragListView.lockDrag();
  29. }
  30. });
  31. adapter = new TestListViewAdapter(this, R.layout.item_test_listview, list);
  32. adapter.setOnItemLongClickListener(new TestListViewAdapter.OnItemLongClickListener() {
  33. @Override
  34. public void onItemLongClick(int position) {
  35. dragListView.unlockDrag();
  36. }
  37. });
  38. dragListView.setAdapter(adapter);
  39. }

用法和普通的ListView一样,通过调用unlockDrag()来解锁拖动(示例代码中通过长按操作来解锁),通过调用lockDrag()方法来锁定拖动。之后还可以通过设置OnDragingListener来监听拖拽过程。开启和锁定拖动操作的条件视项目需求而定,比如长安开启,或者按编辑按钮开启等等。

最后运行一下就可以看到开头的效果了。

控件支持自定义拖拽View的样式。可以通过setDragViewCreator()方法来实现。比如说我想给拖拽的View加一个高亮效果,就可以这样写:

  1. dragListView.setDragViewCreator(new DragListView.DragViewCreator() {
  2. @Override
  3. public View createDragView(int width, int height, Bitmap viewCache) {
  4. RelativeLayout layout = new RelativeLayout(DragListViewTestActivity.this);
  5. ImageView imageView = new ImageView(DragListViewTestActivity.this);
  6. imageView.setImageBitmap(viewCache);
  7. layout.addView(imageView, new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, height));
  8. View view = new View(DragListViewTestActivity.this);
  9. view.setBackground(getDrawable(R.drawable.edging_red));
  10. layout.addView(view, new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, height));
  11. return layout;
  12. }
  13. });

其中高亮的资源edging_red.xml长这样:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <shape xmlns:android="http://schemas.android.com/apk/res/android">
  3. <stroke
  4. android:width="1dp"
  5. android:color="#FF6347" />
  6. <solid android:color="#70FFC0CB" />
  7. </shape>

代码很简单,就是新建一个Layout,里面放一张图片,再在之上加一层高亮遮罩,并将这个layout返回给DragViewCreator接口即可。运行一下看一下效果:

 

同样的原理再写一个支持item拖拽的GridView,上源码:

  1. public class DragGridView extends GridView {
  2. /**
  3. * 速度模板,影响视图移动时的速度变化
  4. * <p>
  5. * MODE_LINEAR // 线性变化模式
  6. * MODE_ACCELERATE // 加速模式
  7. * MODE_DECELERATE // 减速模式
  8. * MODE_ACCELERATE_DECELERATE // 先加速后加速模式
  9. */
  10. public static final int MODE_LINEAR = 0x001;
  11. public static final int MODE_ACCELERATE = 0x002;
  12. public static final int MODE_DECELERATE = 0x003;
  13. public static final int MODE_ACCELERATE_DECELERATE = 0x004;
  14. private Context context;
  15. // 拖动时的视图
  16. private View dragView;
  17. private WindowManager windowManager;
  18. private WindowManager.LayoutParams windowLayoutParams;
  19. private BaseDragAdapter adapter;
  20. /**
  21. * 可设置选项
  22. */
  23. // 移动动画储持续时间,单位毫秒
  24. private long duration = 300;
  25. // 速度模板
  26. private int speedMode = MODE_ACCELERATE_DECELERATE;
  27. // 自动滚动的速度
  28. private int scrollSpeed = 50;
  29. /**
  30. * 运行参数
  31. */
  32. // 拖动块的原始坐标
  33. private int originalPosition = -1;
  34. // 拖动块当前所在坐标
  35. private int currentPosition = -1;
  36. // 用于记录上次点击事件MotionEvent.getX();
  37. private int lastX;
  38. // 用于记录上次点击事件MotionEvent.getY();
  39. private int lastY;
  40. // 用于记录上次点击事件MotionEvent.getRawX();
  41. private int lastRawX;
  42. // 用于记录上次点击事件MotionEvent.getRawY();
  43. private int lastRawY;
  44. // 拖动块中心点x坐标,用于判断拖动块所处的列表位置
  45. private int dragCenterX;
  46. // 拖动块中心点y坐标,用于判断拖动块所处的列表位置
  47. private int dragCenterY;
  48. // 滑动上边界,拖动块中心超过该边界时列表自动向下滑动
  49. private int upScrollBorder;
  50. // 滑动下边界,拖动块中心超过该边界时列表自动向上滑动
  51. private int downScrollBorder;
  52. // 状态栏高度
  53. private int statusHeight;
  54. // 拖动时的列表刷新标识符
  55. private boolean dragRefresh;
  56. // 拖动锁定标记,为false时选中块可被拖动
  57. private boolean dragLock = true;
  58. // 动画列表,存放当前屏幕上正在播放的所有滑动动画的动画对象
  59. private ArrayList<Animator> animatorList;
  60. // 视图列表,存放当前屏幕上正在播放的所有滑动动画的视图对象
  61. private ArrayList<View> dragViews;
  62. /**
  63. * 可监听接口
  64. */
  65. // 拖动块视图对象生成器,可通过设置该接口自定义一个拖动视图的样式,不设置时会有默认实现
  66. private DragViewCreator dragViewCreator;
  67. // 拖动监听接口,拖动开始和结束时会在该接口回调
  68. private OnDragingListener dragingListener;
  69. // 当前拖动目标位置改变时,每次改变都会在该接口回调
  70. private OnDragTargetChangedListener targetChangedListener;
  71. // 内部接口,动画观察者,滑动动画结束是回调
  72. private AnimatorObserver animatorObserver;
  73. private Handler handler = new Handler();
  74. // 列表自动滚动线程
  75. private Runnable scrollRunnable = new Runnable() {
  76. @Override
  77. public void run() {
  78. int scrollY;
  79. // 滚动到顶或到底时停止滚动
  80. if (getFirstVisiblePosition() == 0 || getLastVisiblePosition() == getCount() - 1) {
  81. handler.removeCallbacks(scrollRunnable);
  82. }
  83. // 触控点y坐标超过上边界时,出发列表自动向下滚动
  84. if (lastY > upScrollBorder) {
  85. scrollY = scrollSpeed;
  86. handler.postDelayed(scrollRunnable, 25);
  87. }
  88. // 触控点y坐标超过下边界时,出发列表自动向上滚动
  89. else if (lastY < downScrollBorder) {
  90. scrollY = -scrollSpeed;
  91. handler.postDelayed(scrollRunnable, 25);
  92. }
  93. // // 触控点y坐标处于上下边界之间时,停止滚动
  94. else {
  95. scrollY = 0;
  96. handler.removeCallbacks(scrollRunnable);
  97. }
  98. smoothScrollBy(scrollY, 10);
  99. }
  100. };
  101. public DragGridView(Context context) {
  102. super(context);
  103. init(context);
  104. }
  105. public DragGridView(Context context, AttributeSet attrs) {
  106. super(context, attrs);
  107. init(context);
  108. }
  109. public DragGridView(Context context, AttributeSet attrs, int defStyleAttr) {
  110. super(context, attrs, defStyleAttr);
  111. init(context);
  112. }
  113. /**
  114. * 初始化方法
  115. *
  116. * @param context
  117. */
  118. private void init(Context context) {
  119. this.context = context;
  120. statusHeight = getStatusHeight();
  121. windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
  122. animatorList = new ArrayList<>();
  123. dragViews = new ArrayList<>();
  124. // 拖动块视图对象生成器的默认实现,返回一个与被拖动项外观一致的ImageView
  125. dragViewCreator = new DragGridView.DragViewCreator() {
  126. @Override
  127. public View createDragView(int width, int height, Bitmap viewCache) {
  128. ImageView imageView = new ImageView(DragGridView.this.context);
  129. imageView.setImageBitmap(viewCache);
  130. return imageView;
  131. }
  132. };
  133. }
  134. @Override
  135. public boolean dispatchTouchEvent(MotionEvent motionEvent) {
  136. switch (motionEvent.getAction()) {
  137. case MotionEvent.ACTION_DOWN:
  138. downScrollBorder = getHeight() / 5;
  139. upScrollBorder = getHeight() * 4 / 5;
  140. // 手指按下时记录相关坐标
  141. lastX = (int) motionEvent.getX();
  142. lastY = (int) motionEvent.getY();
  143. lastRawX = (int) motionEvent.getRawX();
  144. lastRawY = (int) motionEvent.getRawY();
  145. currentPosition = pointToPosition(lastRawX, lastRawY);
  146. if (currentPosition == AdapterView.INVALID_POSITION || !adapter.isDragAvailable(currentPosition)) {
  147. return true;
  148. }
  149. originalPosition = currentPosition;
  150. break;
  151. }
  152. return super.dispatchTouchEvent(motionEvent);
  153. }
  154. @Override
  155. public boolean onTouchEvent(MotionEvent motionEvent) {
  156. switch (motionEvent.getAction()) {
  157. case MotionEvent.ACTION_MOVE:
  158. if (!dragLock) {
  159. int currentRawX = (int) motionEvent.getRawX();
  160. int currentRawY = (int) motionEvent.getRawY();
  161. if (dragView == null) {
  162. createDragImageView(getChildAt(pointToPosition(lastRawX, lastRawY) - getFirstVisiblePosition()));
  163. getChildAt(originalPosition - getFirstVisiblePosition()).setVisibility(View.INVISIBLE);
  164. if (dragingListener != null) {
  165. dragingListener.onStart(originalPosition);
  166. }
  167. }
  168. drag(currentRawX - lastRawX, currentRawY - lastRawY);
  169. if (dragingListener != null) {
  170. dragingListener.onDraging((int) motionEvent.getX(), (int) motionEvent.getY(), currentRawX, currentRawY);
  171. }
  172. int position = pointToPosition(dragCenterX, dragCenterY);
  173. if (position != AdapterView.INVALID_POSITION
  174. && currentPosition != position
  175. && adapter.isDragAvailable(position)
  176. && animatorList.size() == 0) {
  177. translation(position, currentPosition);
  178. currentPosition = position;
  179. if (targetChangedListener != null) {
  180. targetChangedListener.onTargetChanged(currentPosition);
  181. }
  182. }
  183. // 更新点击位置
  184. lastX = (int) motionEvent.getX();
  185. lastY = (int) motionEvent.getY();
  186. lastRawX = currentRawX;
  187. lastRawY = currentRawY;
  188. // 返回true消耗掉这次点击事件,防止ListView本身接收到这次点击事件后触发滚动
  189. return true;
  190. }
  191. break;
  192. case MotionEvent.ACTION_UP:
  193. // 手指抬起时,如果所有滑动动画都已播放完毕,则直接执行拖动完成逻辑
  194. if (animatorList.size() == 0) {
  195. resetDataAndView();
  196. if (dragingListener != null) {
  197. dragingListener.onFinish(currentPosition);
  198. }
  199. }
  200. // 如果还有未播放完成的滑动动画,则注册观察者,延时执行拖动完成逻辑
  201. else {
  202. animatorObserver = new AnimatorObserver() {
  203. @Override
  204. public void onAllAnimatorFinish() {
  205. resetDataAndView();
  206. if (dragingListener != null) {
  207. dragingListener.onFinish(currentPosition);
  208. }
  209. }
  210. };
  211. }
  212. break;
  213. }
  214. return super.onTouchEvent(motionEvent);
  215. }
  216. /**
  217. * 创建拖动块视图方法
  218. *
  219. * @param view 被拖动位置的视图对象
  220. */
  221. private void createDragImageView(View view) {
  222. if (view == null) {
  223. return;
  224. }
  225. removeDragImageView();
  226. int[] location = new int[2];
  227. view.getLocationOnScreen(location);
  228. view.setDrawingCacheEnabled(true);
  229. Bitmap bitmap = Bitmap.createBitmap(view.getDrawingCache());
  230. view.destroyDrawingCache();
  231. windowLayoutParams = new WindowManager.LayoutParams();
  232. windowLayoutParams.width = ViewGroup.LayoutParams.WRAP_CONTENT;
  233. windowLayoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT;
  234. windowLayoutParams.format = PixelFormat.TRANSPARENT;
  235. windowLayoutParams.gravity = Gravity.TOP | Gravity.LEFT;
  236. windowLayoutParams.x = location[0];
  237. windowLayoutParams.y = location[1] - statusHeight;
  238. windowLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
  239. dragCenterX = windowLayoutParams.x + view.getWidth() / 2;
  240. dragCenterY = windowLayoutParams.y + view.getHeight() / 2;
  241. dragView = dragViewCreator.createDragView(view.getWidth(), view.getHeight(), bitmap);
  242. if (dragView == null) {
  243. throw new NullPointerException("dragView can not be null");
  244. } else {
  245. windowManager.addView(dragView, windowLayoutParams);
  246. }
  247. }
  248. /**
  249. * 移除拖动视图方法
  250. */
  251. private void removeDragImageView() {
  252. if (dragView != null && windowManager != null) {
  253. windowManager.removeView(dragView);
  254. dragView = null;
  255. windowLayoutParams = null;
  256. }
  257. }
  258. /**
  259. * 拖动方法
  260. *
  261. * @param dx
  262. * @param dy
  263. */
  264. private void drag(int dx, int dy) {
  265. dragCenterX += dx;
  266. dragCenterY += dy;
  267. windowLayoutParams.x += dx;
  268. windowLayoutParams.y += dy;
  269. windowManager.updateViewLayout(dragView, windowLayoutParams);
  270. handler.post(scrollRunnable);
  271. }
  272. /**
  273. * 移动指定位置视图方法
  274. *
  275. * @param fromPosition 移动起始位置
  276. * @param toPosition 移动目标位置
  277. */
  278. private void translation(int fromPosition, int toPosition) {
  279. ArrayList<Animator> list = new ArrayList<>();
  280. if (toPosition > fromPosition) {
  281. for (int position = fromPosition; position < toPosition; position++) {
  282. View view = getChildAt(position - getFirstVisiblePosition());
  283. dragViews.add(view);
  284. if ((position + 1) % getNumColumns() == 0) {
  285. list.add(createTranslationAnimations(view,
  286. 0,
  287. -(view.getWidth() + getVerticalSpacing()) * (getNumColumns() - 1),
  288. 0,
  289. view.getHeight() + getHorizontalSpacing()));
  290. } else {
  291. list.add(createTranslationAnimations(view,
  292. 0,
  293. view.getWidth() + getVerticalSpacing(),
  294. 0,
  295. 0));
  296. }
  297. }
  298. } else {
  299. for (int position = fromPosition; position > toPosition; position--) {
  300. View view = getChildAt(position - getFirstVisiblePosition());
  301. dragViews.add(view);
  302. if (position % getNumColumns() == 0) {
  303. list.add(createTranslationAnimations(view,
  304. 0,
  305. (view.getWidth() + getVerticalSpacing()) * (getNumColumns() - 1),
  306. 0,
  307. -(view.getHeight() + getHorizontalSpacing())));
  308. } else {
  309. list.add(createTranslationAnimations(view,
  310. 0,
  311. -(view.getWidth() + getVerticalSpacing()),
  312. 0,
  313. 0));
  314. }
  315. }
  316. }
  317. AnimatorSet animatorSet = new AnimatorSet();
  318. animatorSet.playTogether(list);
  319. animatorSet.setDuration(duration);
  320. animatorSet.setInterpolator(getAnimatorInterpolator());
  321. animatorSet.addListener(new AnimatorListenerAdapter() {
  322. @Override
  323. public void onAnimationEnd(Animator animation) {
  324. animatorList.remove(animation);
  325. // 所有滑动动画都播放结束时,执行相关操作
  326. if (animatorList.size() == 0) {
  327. // 重置所有滑动过的视图的translateY,避免列表刷新后视图重叠
  328. resetTranslate(dragViews);
  329. dragViews.clear();
  330. adapter.exchangeData(originalPosition, currentPosition);
  331. addOnLayoutChangeListener(new OnLayoutChangeListener() {
  332. @Override
  333. public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
  334. if (dragRefresh) {
  335. removeOnLayoutChangeListener(this);
  336. resetChildVisibility();
  337. getChildAt(currentPosition - getFirstVisiblePosition()).setVisibility(View.INVISIBLE);
  338. originalPosition = currentPosition;
  339. dragRefresh = false;
  340. if (animatorObserver != null) {
  341. animatorObserver.onAllAnimatorFinish();
  342. animatorObserver = null;
  343. }
  344. }
  345. }
  346. });
  347. dragRefresh = true;
  348. adapter.notifyDataSetChanged();
  349. }
  350. }
  351. });
  352. animatorList.add(animatorSet);
  353. animatorSet.start();
  354. }
  355. /**
  356. * 生成移动动画方法
  357. *
  358. * @param view 需要移动的视图
  359. * @param startX 移动起始x坐标
  360. * @param endX 移动终点x坐标
  361. * @param startY 移动起始y坐标
  362. * @param endY 移动终点y坐标
  363. * @return
  364. */
  365. private Animator createTranslationAnimations(View view, float startX, float endX, float startY, float endY) {
  366. ObjectAnimator animatorX = ObjectAnimator.ofFloat(view, "translationX", startX, endX);
  367. ObjectAnimator animatorY = ObjectAnimator.ofFloat(view, "translationY", startY, endY);
  368. AnimatorSet animatorSet = new AnimatorSet();
  369. animatorSet.playTogether(animatorX, animatorY);
  370. return animatorSet;
  371. }
  372. /**
  373. * 重置列表所有项的可见性方法
  374. */
  375. private void resetChildVisibility() {
  376. for (int i = 0; i < getChildCount(); i++) {
  377. View child = getChildAt(i);
  378. if (child != null) {
  379. child.setVisibility(VISIBLE);
  380. }
  381. }
  382. }
  383. /**
  384. * 重置指定视图的translateY属性方法
  385. *
  386. * @param list
  387. */
  388. private void resetTranslate(ArrayList<View> list) {
  389. for (int i = 0; i < list.size(); i++) {
  390. if (list.get(i) != null) {
  391. list.get(i).setTranslationX(0);
  392. list.get(i).setTranslationY(0);
  393. }
  394. }
  395. }
  396. /**
  397. * 重置数据和视图相关数据方法
  398. */
  399. private void resetDataAndView() {
  400. if (currentPosition == -1) {
  401. return;
  402. }
  403. getChildAt(currentPosition - getFirstVisiblePosition()).setVisibility(View.VISIBLE);
  404. originalPosition = -1;
  405. currentPosition = -1;
  406. dragLock = true;
  407. handler.removeCallbacks(scrollRunnable);
  408. removeDragImageView();
  409. }
  410. @Override
  411. public void setAdapter(ListAdapter adapter) {
  412. if (adapter instanceof BaseDragAdapter) {
  413. this.adapter = (BaseDragAdapter) adapter;
  414. super.setAdapter(adapter);
  415. } else {
  416. throw new IllegalStateException("the adapter must extends BaseDragAdapter");
  417. }
  418. }
  419. /**
  420. * 根据速度模板创建动画迭代器
  421. *
  422. * @return
  423. */
  424. private Interpolator getAnimatorInterpolator() {
  425. switch (speedMode) {
  426. case MODE_LINEAR:
  427. return new LinearInterpolator();
  428. case MODE_ACCELERATE:
  429. return new AccelerateInterpolator();
  430. case MODE_DECELERATE:
  431. return new DecelerateInterpolator();
  432. case MODE_ACCELERATE_DECELERATE:
  433. return new AccelerateDecelerateInterpolator();
  434. default:
  435. return null;
  436. }
  437. }
  438. /**
  439. * 拖动解锁方法,调用者需手动调用该方法后才能开启列表拖动功能
  440. */
  441. public void unlockDrag() {
  442. dragLock = false;
  443. }
  444. /**
  445. * 拖动锁定方法,调用者调用该方法后关闭列表拖动功能
  446. */
  447. public void lockDrag() {
  448. dragLock = true;
  449. }
  450. /**
  451. * 设置移动动画持续时间
  452. *
  453. * @param duration 时间,单位毫秒
  454. */
  455. public void setDuration(long duration) {
  456. this.duration = duration;
  457. }
  458. /**
  459. * 设置速度模式,可选项:
  460. * MODE_LINEAR 线性变化模式
  461. * MODE_ACCELERATE 加速模式
  462. * MODE_DECELERATE 减速模式
  463. * MODE_ACCELERATE_DECELERATE 先加速后加速模式
  464. *
  465. * @param speedMode
  466. */
  467. public void setSpeedMode(int speedMode) {
  468. this.speedMode = speedMode;
  469. }
  470. /**
  471. * 设置自动滚动速度
  472. *
  473. * @param scrollSpeed 速度,单位:dp/10ms
  474. */
  475. public void setScrollSpeed(int scrollSpeed) {
  476. this.scrollSpeed = scrollSpeed;
  477. }
  478. /**
  479. * 设置拖动块视图对象生成器方法
  480. *
  481. * @param creator
  482. */
  483. public void setDragViewCreator(DragViewCreator creator) {
  484. if (creator == null) {
  485. return;
  486. }
  487. this.dragViewCreator = creator;
  488. }
  489. /**
  490. * 设置拖动监听接口
  491. *
  492. * @param dragingListener
  493. */
  494. public void setOnDragingListener(OnDragingListener dragingListener) {
  495. this.dragingListener = dragingListener;
  496. }
  497. /**
  498. * 设置拖动目标位置改变监听接口
  499. *
  500. * @param targetChangedListener
  501. */
  502. public void setOnDragTargetChangedListener(OnDragTargetChangedListener targetChangedListener) {
  503. this.targetChangedListener = targetChangedListener;
  504. }
  505. private int getStatusHeight() {
  506. int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
  507. if (resourceId > 0) {
  508. return context.getResources().getDimensionPixelSize(resourceId);
  509. }
  510. return 0;
  511. }
  512. /**
  513. * 动画观察者
  514. */
  515. private interface AnimatorObserver {
  516. /**
  517. * 滑动动画播放结束时回调
  518. */
  519. void onAllAnimatorFinish();
  520. }
  521. /**
  522. * 拖动块视图对象生成器
  523. */
  524. public interface DragViewCreator {
  525. /**
  526. * 创建拖动块视图对象方法,可通过实现该方法自定义拖动块样式
  527. */
  528. View createDragView(int width, int height, Bitmap viewCache);
  529. }
  530. /**
  531. * 拖动监听接口
  532. */
  533. public interface OnDragingListener {
  534. /**
  535. * 拖动开始时回调
  536. *
  537. * @param startPosition 拖动起始坐标
  538. */
  539. void onStart(int startPosition);
  540. /**
  541. * 拖动过程中回调
  542. *
  543. * @param x 触控点相对ListView的x坐标
  544. * @param y 触控点相对ListView的y坐标
  545. * @param rawX 触控点相对屏幕的x坐标
  546. * @param rawY 触控点相对屏幕的y坐标
  547. */
  548. void onDraging(int x, int y, int rawX, int rawY);
  549. /**
  550. * 拖动结束时回调
  551. *
  552. * @param finalPosition 拖动终点坐标
  553. */
  554. void onFinish(int finalPosition);
  555. }
  556. /**
  557. * 拖动目标位置改变监听接口
  558. */
  559. public interface OnDragTargetChangedListener {
  560. /**
  561. * 拖动过程中,每次目标位置改变,会在该方法回调
  562. *
  563. * @param targetPosition 拖动目标位置坐标
  564. */
  565. void onTargetChanged(int targetPosition);
  566. }
  567. }

实现原理和DragListView差不多,就不多做解释了。DragGridView的setAdapter方法同样只接收BaseDragAdapter的继承类,用法和DragListView一样。

简单使用一下,先写一个item布局item_test_gridview.xml:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. android:id="@+id/item_layout"
  4. android:layout_width="match_parent"
  5. android:minHeight="130dp"
  6. android:layout_height="match_parent"
  7. android:background="#FFFAFA"
  8. android:gravity="center_horizontal"
  9. android:orientation="vertical">
  10. <TextView
  11. android:id="@+id/content_textview"
  12. android:layout_width="match_parent"
  13. android:layout_height="0dp"
  14. android:layout_weight="1"
  15. android:gravity="center"
  16. android:padding="15dp"
  17. android:text="我是内容"
  18. android:textSize="20sp" />
  19. </LinearLayout>

再写一个适配器TestGridViewAdapter:

  1. public class TestGridViewAdapter extends BaseDragAdapter {
  2. private Context context;
  3. private int resourceId;
  4. private ArrayList<String> list;
  5. private Vibrator vibrator;
  6. private OnItemLongClickListener listener;
  7. public TestGridViewAdapter(Context context, int resourceId, ArrayList<String> list) {
  8. this.context = context;
  9. this.resourceId = resourceId;
  10. this.list = list;
  11. this.vibrator = (Vibrator) context.getSystemService(context.VIBRATOR_SERVICE);
  12. }
  13. @Override
  14. public List getDataList() {
  15. return list;
  16. }
  17. @Override
  18. public boolean isDragAvailable(int position) {
  19. return true;
  20. }
  21. @Override
  22. public int getCount() {
  23. return list.size();
  24. }
  25. @Override
  26. public Object getItem(int position) {
  27. return list.get(position);
  28. }
  29. @Override
  30. public long getItemId(int position) {
  31. return position;
  32. }
  33. @Override
  34. public View getView(final int position, View convertView, ViewGroup parent) {
  35. View view;
  36. ViewHolder viewHolder;
  37. if (convertView == null) {
  38. view = LayoutInflater.from(context).inflate(resourceId, null);
  39. viewHolder = new ViewHolder();
  40. viewHolder.itemLayout = view.findViewById(R.id.item_layout);
  41. viewHolder.contentTextView = view.findViewById(R.id.content_textview);
  42. view.setTag(viewHolder);
  43. } else {
  44. view = convertView;
  45. viewHolder = (ViewHolder) view.getTag();
  46. }
  47. viewHolder.contentTextView.setText(list.get(position));
  48. viewHolder.itemLayout.setOnLongClickListener(new View.OnLongClickListener() {
  49. @Override
  50. public boolean onLongClick(View v) {
  51. vibrator.vibrate(100);
  52. if (listener != null) {
  53. listener.onItemLongClick(position);
  54. }
  55. return false;
  56. }
  57. });
  58. return view;
  59. }
  60. public void setOnItemLongClickListener(OnItemLongClickListener listener) {
  61. this.listener = listener;
  62. }
  63. public interface OnItemLongClickListener {
  64. void onItemLongClick(int position);
  65. }
  66. class ViewHolder {
  67. LinearLayout itemLayout;
  68. TextView contentTextView;
  69. }
  70. }

最后Activity这样写:

  1. private DragGridView dragGridView;
  2. private TestGridViewAdapter adapter;
  3. private ArrayList<String> list;
  4. @Override
  5. protected void onCreate(@Nullable Bundle savedInstanceState) {
  6. super.onCreate(savedInstanceState);
  7. setContentView(R.layout.activity_drag_gridview_test);
  8. initData();
  9. initView();
  10. }
  11. private void initData() {
  12. list = new ArrayList<>();
  13. for (int i = 1; i <= 40; i++) {
  14. list.add("我是第" + i + "条数据");
  15. }
  16. }
  17. private void initView() {
  18. dragGridView = findViewById(R.id.drag_gridview);
  19. dragGridView.setOnDragingListener(new DragGridView.OnDragingListener() {
  20. @Override
  21. public void onStart(int startPosition) {
  22. }
  23. @Override
  24. public void onDraging(int x, int y, int rawX, int rawY) {
  25. }
  26. @Override
  27. public void onFinish(int finalPosition) {
  28. dragGridView.lockDrag();
  29. }
  30. });
  31. adapter = new TestGridViewAdapter(this, R.layout.item_test_gridview, list);
  32. adapter.setOnItemLongClickListener(new TestGridViewAdapter.OnItemLongClickListener() {
  33. @Override
  34. public void onItemLongClick(int position) {
  35. dragGridView.unlockDrag();
  36. }
  37. });
  38. dragGridView.setAdapter(adapter);
  39. }

用法和DragListView一毛一样。运行一下就能看到开头的效果了。

DragGridView同样可以自定义拖拽View的样式,同样通过setDragViewCreator()方法来实现。比如说添加一个高亮效果:

  1. dragGridView.setDragViewCreator(new DragGridView.DragViewCreator() {
  2. @Override
  3. public View createDragView(int width, int height, Bitmap viewCache) {
  4. RelativeLayout layout = new RelativeLayout(DragGridViewTestActivity.this);
  5. ImageView imageView = new ImageView(DragGridViewTestActivity.this);
  6. imageView.setImageBitmap(viewCache);
  7. layout.addView(imageView, new RelativeLayout.LayoutParams(width, height));
  8. View view = new View(DragGridViewTestActivity.this);
  9. view.setBackground(getDrawable(R.drawable.edging_red));
  10. layout.addView(view, new RelativeLayout.LayoutParams(width, height));
  11. return layout;
  12. }
  13. });

看看效果:

 

以上就是全部内容了,最后来总结一下。


DragListView和DragGridView分别实现ListView和GridView的item拖拽功能。接收Adapter必须是BaseDragAdapter的继承类,通过unlockDrag()方法和lockDrag()方法来开启和关闭拖动。提供OnDragingListener接口来监听拖动过程,提供DragViewCreator接口来自定义拖拽样式。

 

最后的最后,附上源码地址:https://download.csdn.net/download/Sure_Min/12572918

这次的内容就到这里,我们下次再见。
 

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