Android 仿微信对话列表滑动删除效果

http://blog.csdn.net/top_code/article/details/17965743


微信对话列表滑动删除效果很不错的,借鉴了github上SwipeListView(项目地址:https://github.com/likebamboo/SwipeListView),在其上进行了一些重构,最终实现了微信对话列表滑动删除效果。


实现原理
1.通过ListView的pointToPosition(int x, int y)来获取按下的position,然后通过android.view.ViewGroup.getChildAt(position)来得到滑动对象swipeView
2.在onTouchEvent中计算要滑动的距离,调用swipeView.scrollTo即可。



运行效果如下
Android 仿微信对话列表滑动删除效果
文章图片




Android 仿微信对话列表滑动删除效果
文章图片




Android 仿微信对话列表滑动删除效果
文章图片




下面是最核心的部分SwipeListView代码:

[java] view plain copy

  1. package com.fxsky.swipelist.widget;
  2. import android.annotation.SuppressLint;
  3. import android.content.Context;
  4. import android.content.res.TypedArray;
  5. import android.os.Handler;
  6. import android.os.Message;
  7. import android.util.AttributeSet;
  8. import android.view.MotionEvent;
  9. import android.view.View;
  10. import android.widget.ListView;
  11. import com.fxsky.swipelist.R;
  12. public class SwipeListView extends ListView {
  13. private Boolean mIsHorizontal;
  14. private View mPreItemView;
  15. private View mCurrentItemView;
  16. private float mFirstX;
  17. private float mFirstY;
  18. private int mRightViewWidth;
  19. // private boolean mIsInAnimation = false;
  20. private final int mDuration = 100;
  21. private final int mDurationStep = 10;
  22. private boolean mIsShown;
  23. public SwipeListView(Context context) {
  24. this(context,null);
  25. }
  26. public SwipeListView(Context context, AttributeSet attrs) {
  27. this(context, attrs,0);
  28. }
  29. public SwipeListView(Context context, AttributeSet attrs, int defStyle) {
  30. super(context, attrs, defStyle);
  31. TypedArray mTypedArray = context.obtainStyledAttributes(attrs,
  32. R.styleable.swipelistviewstyle);
  33. //获取自定义属性和默认值
  34. mRightViewWidth = (int) mTypedArray.getDimension(R.styleable.swipelistviewstyle_right_width, 200);
  35. mTypedArray.recycle();
  36. }
  37. /**
  38. * return true, deliver to listView. return false, deliver to child. if
  39. * move, return true
  40. */
  41. @Override
  42. public boolean onInterceptTouchEvent(MotionEvent ev) {
  43. float lastX = ev.getX();
  44. float lastY = ev.getY();
  45. switch (ev.getAction()) {
  46. case MotionEvent.ACTION_DOWN:
  47. mIsHorizontal = null;
  48. System.out.println("onInterceptTouchEvent----->ACTION_DOWN");
  49. mFirstX = lastX;
  50. mFirstY = lastY;
  51. int motionPosition = pointToPosition((int)mFirstX, (int)mFirstY);
  52. if (motionPosition >= 0) {
  53. View currentItemView = getChildAt(motionPosition - getFirstVisiblePosition());
  54. mPreItemView = mCurrentItemView;
  55. mCurrentItemView = currentItemView;
  56. }
  57. break;
  58. case MotionEvent.ACTION_MOVE:
  59. float dx = lastX - mFirstX;
  60. float dy = lastY - mFirstY;
  61. if (Math.abs(dx) >= 5 && Math.abs(dy) >= 5) {
  62. return true;
  63. }
  64. break;
  65. case MotionEvent.ACTION_UP:
  66. case MotionEvent.ACTION_CANCEL:
  67. System.out.println("onInterceptTouchEvent----->ACTION_UP");
  68. if (mIsShown && (mPreItemView != mCurrentItemView || isHitCurItemLeft(lastX))) {
  69. System.out.println("1---> hiddenRight");
  70. /**
  71. * 情况一:
  72. *
  73. * 一个Item的右边布局已经显示,
  74. *
  75. * 这时候点击任意一个item, 那么那个右边布局显示的item隐藏其右边布局
  76. */
  77. hiddenRight(mPreItemView);
  78. }
  79. break;
  80. }
  81. return super.onInterceptTouchEvent(ev);
  82. }
  83. private boolean isHitCurItemLeft(float x) {
  84. return x < getWidth() - mRightViewWidth;
  85. }
  86. /**
  87. * @param dx
  88. * @param dy
  89. * @return judge if can judge scroll direction
  90. */
  91. private boolean judgeScrollDirection(float dx, float dy) {
  92. boolean canJudge = true;
  93. if (Math.abs(dx) > 30 && Math.abs(dx) > 2 * Math.abs(dy)) {
  94. mIsHorizontal = true;
  95. System.out.println("mIsHorizontal---->" + mIsHorizontal);
  96. } else if (Math.abs(dy) > 30 && Math.abs(dy) > 2 * Math.abs(dx)) {
  97. mIsHorizontal = false;
  98. System.out.println("mIsHorizontal---->" + mIsHorizontal);
  99. } else {
  100. canJudge = false;
  101. }
  102. return canJudge;
  103. }
  104. /**
  105. * return false, can't move any direction. return true, cant't move
  106. * vertical, can move horizontal. return super.onTouchEvent(ev), can move
  107. * both.
  108. */
  109. @Override
  110. public boolean onTouchEvent(MotionEvent ev) {
  111. float lastX = ev.getX();
  112. float lastY = ev.getY();
  113. switch (ev.getAction()) {
  114. case MotionEvent.ACTION_DOWN:
  115. System.out.println("---->ACTION_DOWN");
  116. break;
  117. case MotionEvent.ACTION_MOVE:
  118. float dx = lastX - mFirstX;
  119. float dy = lastY - mFirstY;
  120. // confirm is scroll direction
  121. if (mIsHorizontal == null) {
  122. if (!judgeScrollDirection(dx, dy)) {
  123. break;
  124. }
  125. }
  126. if (mIsHorizontal) {
  127. if (mIsShown && mPreItemView != mCurrentItemView) {
  128. System.out.println("2---> hiddenRight");
  129. /**
  130. * 情况二:
  131. * 【Android 仿微信对话列表滑动删除效果】
  132. * 一个Item的右边布局已经显示,
  133. *
  134. * 这时候左右滑动另外一个item,那个右边布局显示的item隐藏其右边布局
  135. *
  136. * 向左滑动只触发该情况,向右滑动还会触发情况五
  137. */
  138. hiddenRight(mPreItemView);
  139. }
  140. if (mIsShown && mPreItemView == mCurrentItemView) {
  141. dx = dx - mRightViewWidth;
  142. System.out.println("======dx " + dx);
  143. }
  144. // can't move beyond boundary
  145. if (dx < 0 && dx > -mRightViewWidth) {
  146. mCurrentItemView.scrollTo((int)(-dx), 0);
  147. }
  148. return true;
  149. } else {
  150. if (mIsShown) {
  151. System.out.println("3---> hiddenRight");
  152. /**
  153. * 情况三:
  154. *
  155. * 一个Item的右边布局已经显示,
  156. *
  157. * 这时候上下滚动ListView,那么那个右边布局显示的item隐藏其右边布局
  158. */
  159. hiddenRight(mPreItemView);
  160. }
  161. }
  162. break;
  163. case MotionEvent.ACTION_UP:
  164. case MotionEvent.ACTION_CANCEL:
  165. System.out.println("============ACTION_UP");
  166. clearPressedState();
  167. if (mIsShown) {
  168. System.out.println("4---> hiddenRight");
  169. /**
  170. * 情况四:
  171. *
  172. * 一个Item的右边布局已经显示,
  173. *
  174. * 这时候左右滑动当前一个item,那个右边布局显示的item隐藏其右边布局
  175. */
  176. hiddenRight(mPreItemView);
  177. }
  178. if (mIsHorizontal != null && mIsHorizontal) {
  179. if (mFirstX - lastX > mRightViewWidth / 2) {
  180. showRight(mCurrentItemView);
  181. } else {
  182. System.out.println("5---> hiddenRight");
  183. /**
  184. * 情况五:
  185. *
  186. * 向右滑动一个item,且滑动的距离超过了右边View的宽度的一半,隐藏之。
  187. */
  188. hiddenRight(mCurrentItemView);
  189. }
  190. return true;
  191. }
  192. break;
  193. }
  194. return super.onTouchEvent(ev);
  195. }
  196. private void clearPressedState() {
  197. // TODO current item is still has background, issue
  198. mCurrentItemView.setPressed(false);
  199. setPressed(false);
  200. refreshDrawableState();
  201. // invalidate();
  202. }
  203. private void showRight(View view) {
  204. System.out.println("=========showRight");
  205. Message msg = new MoveHandler().obtainMessage();
  206. msg.obj = view;
  207. msg.arg1 = view.getScrollX();
  208. msg.arg2 = mRightViewWidth;
  209. msg.sendToTarget();
  210. mIsShown = true;
  211. }
  212. private void hiddenRight(View view) {
  213. System.out.println("=========hiddenRight");
  214. if (mCurrentItemView == null) {
  215. return;
  216. }
  217. Message msg = new MoveHandler().obtainMessage(); //
  218. msg.obj = view;
  219. msg.arg1 = view.getScrollX();
  220. msg.arg2 = 0;
  221. msg.sendToTarget();
  222. mIsShown = false;
  223. }
  224. /**
  225. * show or hide right layout animation
  226. */
  227. @SuppressLint("HandlerLeak")
  228. class MoveHandler extends Handler {
  229. int stepX = 0;
  230. int fromX;
  231. int toX;
  232. View view;
  233. private boolean mIsInAnimation = false;
  234. private void animatioOver() {
  235. mIsInAnimation = false;
  236. stepX = 0;
  237. }
  238. @Override
  239. public void handleMessage(Message msg) {
  240. super.handleMessage(msg);
  241. if (stepX == 0) {
  242. if (mIsInAnimation) {
  243. return;
  244. }
  245. mIsInAnimation = true;
  246. view = (View)msg.obj;
  247. fromX = msg.arg1;
  248. toX = msg.arg2;
  249. stepX = (int)((toX - fromX) * mDurationStep * 1.0 / mDuration);
  250. if (stepX < 0 && stepX > -1) {
  251. stepX = -1;
  252. } else if (stepX > 0 && stepX < 1) {
  253. stepX = 1;
  254. }
  255. if (Math.abs(toX - fromX) < 10) {
  256. view.scrollTo(toX, 0);
  257. animatioOver();
  258. return;
  259. }
  260. }
  261. fromX += stepX;
  262. boolean isLastStep = (stepX > 0 && fromX > toX) || (stepX < 0 && fromX < toX);
  263. if (isLastStep) {
  264. fromX = toX;
  265. }
  266. view.scrollTo(fromX, 0);
  267. invalidate();
  268. if (!isLastStep) {
  269. this.sendEmptyMessageDelayed(0, mDurationStep);
  270. } else {
  271. animatioOver();
  272. }
  273. }
  274. }
  275. public int getRightViewWidth() {
  276. return mRightViewWidth;
  277. }
  278. public void setRightViewWidth(int mRightViewWidth) {
  279. this.mRightViewWidth = mRightViewWidth;
  280. }
  281. }



github上有另一个项目47deg / android-swipelistview,项目地址:https://github.com/47deg/android-swipelistview,效果如下:








Demo下载地址:http://download.csdn.net/detail/fx_sky/6820665


------------------------------------------------------------
Demo中SwipeAdapter源码中有一处由于粗心写错了,会导致向下滑动时出现数组越界异常,现更正如下:

[java] view plain copy
  1. @Override
  2. public int getCount() {
  3. //return 100;
  4. return data.size();
  5. }







    推荐阅读