高仿微信对话列表滑动删除效果
www.credesign.net 高仿微信对话列表滑动删除效果
前言
用过微信的都知道,微信对话列表滑动删除效果是很不错的,这个效果我们也可以有。思路其实很简单,弄个ListView,然后里面的每个item做成一个可以滑动的自定义控件即可。由于ListView是上下滑动而item是左右滑动,因此会有滑动冲突,也许你需要了解下android中点击事件的派发
,请参考Android源码
-点击事件派发机制。我的解决思路是这样的:重写ListView的onInterceptTouchEvent方法,在move的时候做判断,如果是左右滑动就返回false,否则返回true;重写SlideView(即自定义item控件)的onTouchEvent方法来处理滑动。整个思路没有问题,滑动冲突也解决了,可是ListView无法得到焦点了,也就是ListView无法处理点击事件了。让我们回想下问题出在哪里:我的理解是这样的,上述处理滑动本身没有问题,但是有一个副作用,就是会让外层View失去焦点且无法处理点击事件。常见的滑动冲突场景,比如launcher内部嵌入ListView却是没有问题的,因为这个时候launcher不需要获得焦点,需要获得焦点的是内部的ListView。因此,上述处理方式对于外部需要获得焦点的情况(比如外部是ListView)就不太适合了。于是我就和ttdevs探讨,发现他采用了另外一种思路,我从来没有想过还可以这样玩。下面介绍他的思路。
新的思路
不考虑那么复杂,不采用主流玩法,所有的事件均由外层的ListView做拦截,同时把事件传递给SlideView做滑动,这种实现的确可以达到效果,而且代码很简单,根本不需要处理什么复杂的滑动冲突。
效果
下面分别为微信和高仿效果
www.credesign.net
www.credesign.net
代码分析
先看SlideView是如何实现的
看layout xml:
[html] view plaincopy
1.
2.
5.
6.
11.
12.
13. 19.
20.
29.
30.
31.
上述xml文件中,所有的view都会被放在view_content中,而holder是放置诸如删除按钮之类的东西,我们的
SlideView会加载这个布局。
再看SlideView.java:
[java] view plaincopy
1. /**
2. * SlideView 继承自LinearLayout
3. */
4. public class SlideView extends LinearLayout { 5.
6. private static final String TAG = "SlideView"; 7.
8. private Context mContext;
9.
10. // 用来放置所有view的容器
11. private LinearLayout mViewContent; 12.
13. // 用来放置内置view的容器,比如删除 按钮 14. private RelativeLayout mHolder;
15.
16. // 弹性滑动对象,提供弹性滑动效果
17. private Scroller mScroller;
18.
19. // 滑动回调接口,用来向上层通知滑动事件 20. private OnSlideListener mOnSlideListener; 21.
www.credesign.net
www.credesign.net
22. // 内置容器的宽度 单位:dp
23. private int mHolderWidth = 120;
24.
25. // 分别记录上次滑动的坐标
26. private int mLastX = 0;
27. private int mLastY = 0;
28.
29. // 用来控制滑动角度,仅当角度a满足如下条件才进行滑动:tan a = deltaX / deltaY > 2
30. private static final int TAN = 2;
31.
32. public interface OnSlideListener {
33. // SlideView的三种状态:开始滑动,打开,关闭 34. public static final int SLIDE_STATUS_OFF = 0; 35. public static final int SLIDE_STATUS_START_SCROLL = 1; 36. public static final int SLIDE_STATUS_ON = 2; 37.
38. /**
39. * @param view
40. * current SlideView
41. * @param status
42. * SLIDE_STATUS_ON, SLIDE_STATUS_OFF or 43. * SLIDE_STATUS_START_SCROLL 44. */
45. public void onSlide(View view, int status); 46. }
47.
48. public SlideView(Context context) {
49. super(context);
50. initView();
51. }
52.
53. public SlideView(Context context, AttributeSet attrs) { 54. super(context, attrs);
55. initView();
56. }
57.
58. private void initView() {
59. mContext = getContext();
60. // 初始化弹性滑动对象
61. mScroller = new Scroller(mContext); 62. // 设置其方向为横向
63. setOrientation(LinearLayout.HORIZONTAL); 64. // 将slide_view_merge加载进来
65. View.inflate(mContext, R.layout.slide_view_merge, this);
www.credesign.net
www.credesign.net
66. mViewContent = (LinearLayout) findViewById(R.id.view_content); 67. mHolderWidth = Math.round(TypedValue.applyDimension( 68. TypedValue.COMPLEX_UNIT_DIP, mHolderWidth, getResources() 69. .getDisplayMetrics()));
70. }
71.
72. // 设置按钮的内容,也可以设置图标啥的,我没写
73. public void setButtonText(CharSequence text) { 74. ((TextView) findViewById(R.id.delete)).setText(text); 75. }
76.
77. // 将view加入到ViewContent中
78. public void setContentView(View view) {
79. mViewContent.addView(view);
80. }
81.
82. // 设置滑动回调
83. public void setOnSlideListener(OnSlideListener onSlideListener) { 84. mOnSlideListener = onSlideListener;
85. }
86.
87. // 将当前状态置为关闭
88. public void shrink() {
89. if (getScrollX() != 0) {
90. this.smoothScrollTo(0, 0);
91. }
92. }
93.
94. // 根据MotionEvent来进行滑动,这个方法的作用相当于onTouchEvent 95. // 如果你不需要处理滑动冲突,可以直接重命名,照样能正常工作 96. public void onRequireTouchEvent(MotionEvent event) { 97. int x = (int) event.getX();
98. int y = (int) event.getY();
99. int scrollX = getScrollX();
100. Log.d(TAG, "x=" + x + " y=" + y);
101.
102. switch (event.getAction()) {
103. case MotionEvent.ACTION_DOWN: {
104. if (!mScroller.isFinished()) {
105. mScroller.abortAnimation();
106. }
107. if (mOnSlideListener != null) {
108. mOnSlideListener.onSlide(this,
109. OnSlideListener.SLIDE_STATUS_START_SCROLL);
www.credesign.net
www.credesign.net
110. }
111. break;
112. }
113. case MotionEvent.ACTION_MOVE: {
114. int deltaX = x - mLastX;
115. int deltaY = y - mLastY;
116. if (Math.abs(deltaX) < Math.abs(deltaY) * TAN) { 117. // 滑动不满足条件,不做横向滑动 118. break;
119. }
120.
121. // 计算滑动终点是否合法,防止滑动越界 122. int newScrollX = scrollX - deltaX; 123. if (deltaX != 0) {
124. if (newScrollX < 0) {
125. newScrollX = 0;
126. } else if (newScrollX > mHolderWidth) { 127. newScrollX = mHolderWidth; 128. }
129. this.scrollTo(newScrollX, 0); 130. }
131. break;
132. }
133. case MotionEvent.ACTION_UP: {
134. int newScrollX = 0;
135. // 这里做了下判断,当松开手的时候,会自动向两边滑动,具体向哪边滑,要看当前所处的位置
136. if (scrollX - mHolderWidth * 0.75 > 0) { 137. newScrollX = mHolderWidth; 138. }
139. // 慢慢滑向终点
140. this.smoothScrollTo(newScrollX, 0); 141. // 通知上层滑动事件
142. if (mOnSlideListener != null) { 143. mOnSlideListener.onSlide(this, 144. newScrollX == 0 ? OnSlideListener.SLIDE_STATUS_OFF
145. : OnSlideListener.SLIDE_STATUS_ON);
146. }
147. break;
148. }
149. default:
150. break;
151. }
152.
153. mLastX = x;
www.credesign.net
www.credesign.net
154. mLastY = y;
155. }
156.
157. private void smoothScrollTo(int destX, int destY) {
158. // 缓慢滚动到指定位置
159. int scrollX = getScrollX();
160. int delta = destX - scrollX;
161. // 以三倍时长滑向destX,效果就是慢慢滑动
162. mScroller.startScroll(scrollX, 0, delta, 0, Math.abs(delta) * 3); 163. invalidate();
164. }
165.
166. @Override
167. public void computeScroll() {
168. if (mScroller.computeScrollOffset()) {
169. scrollTo(mScroller.getCurrX(), mScroller.getCurrY()); 170. postInvalidate();
171. }
172. }
173.
174. }
上述代码做了很详细的说明,这就是滑动控件的完整代码,大家要明白的是:你所添加的view都是加在SlideView
的子View : view_content中的,而不是直接加在SlideView中,只有这样我们才方便做滑动效果。
接着看ListView的代码:核心就是下面这一个方法,将点击事件发送给SlideView处理。
[java] view plaincopy
1. @Override
2. public boolean onTouchEvent(MotionEvent event) {
3. switch (event.getAction()) {
4. case MotionEvent.ACTION_DOWN: {
5. int x = (int) event.getX();
6. int y = (int) event.getY();
7. //我们想知道当前点击了哪一行
8. int position = pointToPosition(x, y);
9. Log.e(TAG, "postion=" + position);
10. if (position != INVALID_POSITION) {
11. //得到当前点击行的数据从而取出当前行的item。
12. //可能有人怀疑,为什么要这么干,为什么不用getChildAt(position), 13. //因为ListView会进行缓存,如果你不这么干,有些行的view你是得不到的。 14. MessageItem data = (MessageItem) getItemAtPosition(position); 15. mFocusedItemView = data.slideView;
16. Log.e(TAG, "FocusedItemView=" + mFocusedItemView); 17. }
www.credesign.net
www.credesign.net
18. }
19. default:
20. break;
21. }
22.
23. //向当前点击的view发送滑动事件请求,其实就是向SlideView发请求 24. if (mFocusedItemView != null) {
25. mFocusedItemView.onRequireTouchEvent(event); 26. }
27.
28. return super.onTouchEvent(event);
29. }
最后看Activity的代码:
[java] view plaincopy
1. public class MainActivity extends Activity implements OnItemClickListener,
2. OnClickListener, OnSlideListener {
3.
4. private static final String TAG = "MainActivity"; 5.
6. private ListViewCompat mListView;
7.
8. private List
mMessageItems = new ArrayList();
9.
10. private SlideAdapter mSlideAdapter;
11.
12. // 上次处于打开状态的SlideView
13. private SlideView mLastSlideViewWithStatusOn; 14.
15. @Override
16. protected void onCreate(Bundle savedInstanceState) { 17. super.onCreate(savedInstanceState);
18. setContentView(R.layout.activity_main); 19. initView();
20. }
21.
22. private void initView() {
23. mListView = (ListViewCompat) findViewById(R.id.list); 24.
25. for (int i = 0; i < 20; i++) {
26. MessageItem item = new MessageItem(); 27. if (i % 3 == 0) {
28. item.iconRes = R.drawable.default_qq_avatar;
www.credesign.net
www.credesign.net
29. item.title = "腾讯新闻"; 30. item.msg = "青岛爆炸满月:大量鱼虾死亡"; 31. item.time = "晚上18:18"; 32. } else {
33. item.iconRes = R.drawable.wechat_icon; 34. item.title = "微信团队"; 35. item.msg = "欢迎你使用微信"; 36. item.time = "12月18日"; 37. }
38. mMessageItems.add(item);
39. }
40. mSlideAdapter = new SlideAdapter(); 41. mListView.setAdapter(mSlideAdapter); 42. mListView.setOnItemClickListener(this); 43. }
44.
45. private class SlideAdapter extends BaseAdapter { 46.
47. private LayoutInflater mInflater; 48.
49. SlideAdapter() {
50. super();
51. mInflater = getLayoutInflater(); 52. }
53.
54. @Override
55. public int getCount() {
56. return mMessageItems.size(); 57. }
58.
59. @Override
60. public Object getItem(int position) { 61. return mMessageItems.get(position); 62. }
63.
64. @Override
65. public long getItemId(int position) { 66. return position;
67. }
68.
69. @Override
70. public View getView(int position, View convertView, ViewGroup parent) {
71. ViewHolder holder;
72. SlideView slideView = (SlideView) convertView;
www.credesign.net
www.credesign.net
73. if (slideView == null) {
74. // 这里是我们的item
75. View itemView = mInflater.inflate(R.layout.list_item, null);
76.
77. slideView = new SlideView(MainActivity.this); 78. // 这里把item加入到slideView 79. slideView.setContentView(itemView); 80. // 下面是做一些数据缓存
81. holder = new ViewHolder(slideView); 82. slideView.setOnSlideListener(MainActivity.this); 83. slideView.setTag(holder);
84. } else {
85. holder = (ViewHolder) slideView.getTag(); 86. }
87. MessageItem item = mMessageItems.get(position); 88. item.slideView = slideView;
89. item.slideView.shrink();
90.
91. holder.icon.setImageResource(item.iconRes); 92. holder.title.setText(item.title); 93. holder.msg.setText(item.msg);
94. holder.time.setText(item.time);
95. holder.deleteHolder.setOnClickListener(MainActivity.this);
96.
97. return slideView;
98. }
99.
100. }
101.
102. public class MessageItem {
103. public int iconRes;
104. public String title;
105. public String msg;
106. public String time;
107. public SlideView slideView;
108. }
109.
110. private static class ViewHolder {
111. public ImageView icon;
112. public TextView title;
113. public TextView msg;
114. public TextView time;
115. public ViewGroup deleteHolder;
116.
www.credesign.net
www.credesign.net
117. ViewHolder(View view) {
118. icon = (ImageView) view.findViewById(R.id.icon); 119. title = (TextView) view.findViewById(R.id.title); 120. msg = (TextView) view.findViewById(R.id.msg); 121. time = (TextView) view.findViewById(R.id.time); 122. deleteHolder = (ViewGroup) view.findViewById(R.id.holder); 123. }
124. }
125.
126. @Override
127. public void onItemClick(AdapterView> parent, View view, int position,
128. long id) {
129. // 这里处理ListItem的点击事件
130. Log.e(TAG, "onItemClick position=" + position); 131. }
132.
133. @Override
134. public void onSlide(View view, int status) { 135. // 如果当前存在已经打开的SlideView,那么将其关闭 136. if (mLastSlideViewWithStatusOn != null 137. && mLastSlideViewWithStatusOn != view) { 138. mLastSlideViewWithStatusOn.shrink(); 139. }
140. // 记录本次处于打开状态的view
141. if (status == SLIDE_STATUS_ON) {
142. mLastSlideViewWithStatusOn = (SlideView) view; 143. }
144. }
145.
146. @Override
147. public void onClick(View v) {
148. // 这里处理删除按钮的点击事件,可以删除对话 149. if (v.getId() == R.id.holder) {
150. int position = mListView.getPositionForView(v); 151. if (position != ListView.INVALID_POSITION) { 152. mMessageItems.remove(position); 153. mSlideAdapter.notifyDataSetChanged(); 154. }
155. Log.e(TAG, "onClick v=" + v);
156. }
157. }
158. }
代码我都特意写了注释,就不多说了。
www.credesign.net
www.credesign.net
代码下载:
www.credesign.net