SwipeMenuListView(滑动菜单)
A swipe menu for ListView.--一个非常好的滑动菜单开源项目。
Demo
一、简介
看了挺长时间的自定义View和事件分发,想找一个项目练习下。。正好印证自己所学。
在github上找到了这个项目:SwipeMenuListView这的真不错,对事件分发和自定义View都很有启发性,虽然还有点小瑕疵,后面说明。想了解滑动菜单怎么实现的同学,这篇文章绝对对你有帮助,从宏观微观角度详细分析了每个文件。
项目地址:https://github.com/baoyongzhang/SwipeMenuListView/tree/b00e0fe8c2b8d6f08607bfe2ab390c7cee8df274 版本:b00e0fe 它的使用很简单只需要三步,在github上就可以看懂就不占用篇幅啦,本文只分析原理。另外如果你看代码感觉和我不一样,看着困难的话,可以看我加了注释的:http://download.csdn.net/detail/jycboy/9667699
先看两个图:有一个大体的了解
这是框架中所有的类。
1.下面的图是视图层次:
上面的图中:SwipeMenuLayout是ListView中item的布局,分左右两部分,一部分是正常显示的contentView,一部分是滑出来的menuView;滑出来的SwipeMenuView继承自LinearLayout,添加view时,就是横向添加,可以横向添加多个。
2.下面的图是类图结构:
上面是类之间的调用关系,类旁边注明了类的主要作用。
二、源码分析
SwipeMenu、SwipeMenuItem是实体类,定义了属性和setter、getter方法,看下就行。基本上源码的注释很清楚。
2.1 SwipeMenuView: 代码中注释的很清楚
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
|
/**
* 横向的LinearLayout,就是整个swipemenu的父布局
* 主要定义了添加Item的方法及Item的属性设置
* @author baoyz
* @date 2014-8-23
*
*/
public
class
SwipeMenuView
extends
LinearLayout
implements
OnClickListener {
private
SwipeMenuListView mListView;
private
SwipeMenuLayout mLayout;
private
SwipeMenu mMenu;
private
OnSwipeItemClickListener onItemClickListener;
private
int
position;
public
int
getPosition() {
return
position;
}
public
void
setPosition(
int
position) {
this
.position = position;
}
public
SwipeMenuView(SwipeMenu menu, SwipeMenuListView listView) {
super
(menu.getContext());
mListView = listView;
mMenu = menu;
//
// MenuItem的list集合
List<SwipeMenuItem> items = menu.getMenuItems();
int
id =
0
;
//通过item构造出View添加到SwipeMenuView中
for
(SwipeMenuItem item : items) {
addItem(item, id++);
}
}
/**
* 将 MenuItem 转换成 UI控件,一个item就相当于一个垂直的LinearLayout,
* SwipeMenuView就是横向的LinearLayout,
*/
private
void
addItem(SwipeMenuItem item,
int
id) {
//布局参数
LayoutParams params =
new
LayoutParams(item.getWidth(),
LayoutParams.MATCH_PARENT);
LinearLayout parent =
new
LinearLayout(getContext());
//设置menuitem的id,用于后边的点击事件区分item用的
parent.setId(id);
parent.setGravity(Gravity.CENTER);
parent.setOrientation(LinearLayout.VERTICAL);
parent.setLayoutParams(params);
parent.setBackgroundDrawable(item.getBackground());
//设置监听器
parent.setOnClickListener(
this
);
addView(parent);
//加入到SwipeMenuView中,横向的
if
(item.getIcon() !=
null
) {
parent.addView(createIcon(item));
}
if
(!TextUtils.isEmpty(item.getTitle())) {
parent.addView(createTitle(item));
}
}
//创建img
private
ImageView createIcon(SwipeMenuItem item) {
ImageView iv =
new
ImageView(getContext());
iv.setImageDrawable(item.getIcon());
return
iv;
}
/*根据参数创建title
*/
private
TextView createTitle(SwipeMenuItem item) {
TextView tv =
new
TextView(getContext());
tv.setText(item.getTitle());
tv.setGravity(Gravity.CENTER);
tv.setTextSize(item.getTitleSize());
tv.setTextColor(item.getTitleColor());
return
tv;
}
@Override
/**
* 用传来的mLayout判断是否打开
* 调用onItemClick点击事件
*/
public
void
onClick(View v) {
if
(onItemClickListener !=
null
&& mLayout.isOpen()) {
onItemClickListener.onItemClick(
this
, mMenu, v.getId());
}
}
public
OnSwipeItemClickListener getOnSwipeItemClickListener() {
return
onItemClickListener;
}
/**
* 设置item的点击事件
* @param onItemClickListener
*/
public
void
setOnSwipeItemClickListener(OnSwipeItemClickListener onItemClickListener) {
this
.onItemClickListener = onItemClickListener;
}
public
void
setLayout(SwipeMenuLayout mLayout) {
this
.mLayout = mLayout;
}
/**
* 点击事件的回调接口
*/
public
static
interface
OnSwipeItemClickListener {
/**
* onClick点击事件中调用onItemClick
* @param view 父布局
* @param menu menu实体类
* @param index menuItem的id
*/
void
onItemClick(SwipeMenuView view, SwipeMenu menu,
int
index);
}
}
|
**SwipeMenuView就是滑动时显示的View,看他的构造函数SwipeMenuView(SwipeMenu menu, SwipeMenuListView listView);遍历Items:menu.getMenuItems();调用addItem方法向SwipeMenuView中添加item。
在addItem方法中:每一个item都是一个LinearLayout。
2.2 SwipeMenuLayout:
这个类代码有点长,我们分成三部分看,只粘贴核心代码,剩下的看一下应该就懂啦。
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
|
public
class
SwipeMenuLayout
extends
FrameLayout {
private
static
final
int
CONTENT_VIEW_ID =
1
;
private
static
final
int
MENU_VIEW_ID =
2
;
private
static
final
int
STATE_CLOSE =
0
;
private
static
final
int
STATE_OPEN =
1
;
//方向
private
int
mSwipeDirection;
private
View mContentView;
private
SwipeMenuView mMenuView;
。。。。。
public
SwipeMenuLayout(View contentView, SwipeMenuView menuView) {
this
(contentView, menuView,
null
,
null
);
}
public
SwipeMenuLayout(View contentView, SwipeMenuView menuView,
Interpolator closeInterpolator, Interpolator openInterpolator) {
super
(contentView.getContext());
mCloseInterpolator = closeInterpolator;
mOpenInterpolator = openInterpolator;
mContentView = contentView;
mMenuView = menuView;
//将SwipeMenuLayout设置给SwipeMenuView,用于判断是否打开
mMenuView.setLayout(
this
);
init();
}
private
void
init() {
setLayoutParams(
new
AbsListView.LayoutParams(LayoutParams.MATCH_PARENT,
LayoutParams.WRAP_CONTENT));
mGestureListener =
new
SimpleOnGestureListener() {
@Override
public
boolean
onDown(MotionEvent e) {
isFling =
false
;
return
true
;
}
@Override
//velocityX这个参数是x轴方向的速率,向左是负的,向右是正的
public
boolean
onFling(MotionEvent e1, MotionEvent e2,
float
velocityX,
float
velocityY) {
// TODO
if
(Math.abs(e1.getX() - e2.getX()) > MIN_FLING
&& velocityX < MAX_VELOCITYX) {
isFling =
true
;
}
Log.i(
"tag"
,
"isFling="
+isFling+
" e1.getX()="
+e1.getX()+
" e2.getX()="
+e2.getX()+
" velocityX="
+velocityX+
" MAX_VELOCITYX="
+MAX_VELOCITYX);
// Log.i("byz", MAX_VELOCITYX + ", velocityX = " + velocityX);
return
super
.onFling(e1, e2, velocityX, velocityY);
}
};
mGestureDetector =
new
GestureDetectorCompat(getContext(),
mGestureListener);
。。。。
LayoutParams contentParams =
new
LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
mContentView.setLayoutParams(contentParams);
if
(mContentView.getId() <
1
) {
//noinspection ResourceType
mContentView.setId(CONTENT_VIEW_ID);
}
//noinspection ResourceType
mMenuView.setId(MENU_VIEW_ID);
mMenuView.setLayoutParams(
new
LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT));
addView(mContentView);
addView(mMenuView);
}
|
从上边的init方法中可以看出SwipeMenuLayout由两部分组成,分别是用户的 item View 和 menu View 。手指的时候滑动的操作是通过 SimpleOnGestureListener
来完成的。
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
|
/**
* 滑动事件,用于外边调用的接口
* 这是一个对外暴露的API,而调用这个API的是SwipeMenuListView,那么MotionEvent是SwipeMenuListView的MotionEvent
* @param event
* @return
*/
public
boolean
onSwipe(MotionEvent event) {
mGestureDetector.onTouchEvent(event);
switch
(event.getAction()) {
case
MotionEvent.ACTION_DOWN:
mDownX = (
int
) event.getX();
//记下点击的x坐标
isFling =
false
;
break
;
case
MotionEvent.ACTION_MOVE:
// Log.i("byz", "downX = " + mDownX + ", moveX = " + event.getX());
int
dis = (
int
) (mDownX - event.getX());
if
(state == STATE_OPEN) {
//当状态是open时,dis就是0
Log.i(
"tag"
,
"dis = "
+ dis);
//这个值一直是0
//DIRECTION_LEFT = 1 || DIRECTION_RIGHT = -1
dis += mMenuView.getWidth()*mSwipeDirection;
//mSwipeDirection=1
Log.i(
"tag"
,
"dis = "
+ dis +
", mSwipeDirection = "
+ mSwipeDirection);
}
Log.i(
"tag"
,
"ACTION_MOVE downX = "
+ mDownX +
", moveX = "
+ event.getX()+
", dis="
+dis);
swipe(dis);
break
;
case
MotionEvent.ACTION_UP:
//判断滑动距离,是打开还是关闭
//在这里,如果已经有一个item打开了,此时滑动另外的一个item,还是执行这个方法,怎么改进?
if
((isFling || Math.abs(mDownX - event.getX()) > (mMenuView.getWidth() /
2
)) &&
Math.signum(mDownX - event.getX()) == mSwipeDirection) {
Log.i(
"tag"
,
"ACTION_UP downX = "
+ mDownX +
", moveX = "
+ event.getX());
// open
smoothOpenMenu();
}
else
{
// close
smoothCloseMenu();
return
false
;
}
break
;
}
return
true
;
}
public
boolean
isOpen() {
return
state == STATE_OPEN;
}
/**
* 滑动dis的距离,把mContentView和mMenuView都滑动dis距离
* @param dis
*/
private
void
swipe(
int
dis) {
if
(!mSwipEnable){
return
;
}
//left is positive;right is negative
if
(Math.signum(dis) != mSwipeDirection) {
//left=1;right =-1
dis =
0
;
//不滑动
}
else
if
(Math.abs(dis) > mMenuView.getWidth()) {
//大于它的宽度,dis就是mMenuView.getWidth()
dis = mMenuView.getWidth()*mSwipeDirection;
}
//重新设置布局,不断左移(或者右移),
mContentView.layout(-dis, mContentView.getTop(),
mContentView.getWidth() -dis, getMeasuredHeight());
if
(mSwipeDirection == SwipeMenuListView.DIRECTION_LEFT) {
//1
//同上重新设置menuview的布局,画图很清晰
mMenuView.layout(mContentView.getWidth() - dis, mMenuView.getTop(),
mContentView.getWidth() + mMenuView.getWidth() - dis,
mMenuView.getBottom());
}
else
{
mMenuView.layout(-mMenuView.getWidth() - dis, mMenuView.getTop(),
- dis, mMenuView.getBottom());
}
}
/**
* 更新状态state = STATE_CLOSE;
* 关闭menu
*/
public
void
smoothCloseMenu() {
state = STATE_CLOSE;
if
(mSwipeDirection == SwipeMenuListView.DIRECTION_LEFT) {
mBaseX = -mContentView.getLeft();
//滑动mMenuView.getWidth()的距离,正好隐藏掉
mCloseScroller.startScroll(
0
,
0
, mMenuView.getWidth(),
0
,
350
);
}
else
{
mBaseX = mMenuView.getRight();
mCloseScroller.startScroll(
0
,
0
, mMenuView.getWidth(),
0
,
350
);
}
postInvalidate();
}
public
void
smoothOpenMenu() {
if
(!mSwipEnable){
return
;
}
state = STATE_OPEN;
if
(mSwipeDirection == SwipeMenuListView.DIRECTION_LEFT) {
mOpenScroller.startScroll(-mContentView.getLeft(),
0
, mMenuView.getWidth(),
0
,
350
);
Log.i(
"tag"
,
"mContentView.getLeft()="
+mContentView.getLeft()+
", mMenuView="
+mMenuView.getWidth());
//-451,就是移动的距离dis,-(downX-moveX)
//mContentView.getLeft()=-540, mMenuView=540 ,这俩的绝对值是相等的,完全正确!哈哈·
}
else
{
mOpenScroller.startScroll(mContentView.getLeft(),
0
, mMenuView.getWidth(),
0
,
350
);
}
//在非ui thread中调用这个方法,使视图重绘
postInvalidate();
}
。。。
}
|
上面主要的方法是onSwipe和swipe这两个方法,主要逻辑是:onSwipe是暴漏给外面调用的API,
在SwipeMenuListView的onTouchEvent事件处理方法中调用了onSwipe;而swipe就是把mContentView和mMenuView都滑动dis距离。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
protected
void
onMeasure(
int
widthMeasureSpec,
int
heightMeasureSpec) {
super
.onMeasure(widthMeasureSpec, heightMeasureSpec);
//宽度是无限扩展的,高度是指定的
mMenuView.measure(MeasureSpec.makeMeasureSpec(
0
,
MeasureSpec.UNSPECIFIED), MeasureSpec.makeMeasureSpec(
getMeasuredHeight(), MeasureSpec.EXACTLY));
}
protected
void
onLayout(
boolean
changed,
int
l,
int
t,
int
r,
int
b) {
mContentView.layout(
0
,
0
, getMeasuredWidth(),
mContentView.getMeasuredHeight());
if
(mSwipeDirection == SwipeMenuListView.DIRECTION_LEFT) {
//左滑
//相对于父view,以左边和上边为基准,隐藏在右边
mMenuView.layout(getMeasuredWidth(),
0
,
getMeasuredWidth() + mMenuView.getMeasuredWidth(),
mContentView.getMeasuredHeight());
}
else
{
//右滑,隐藏在左边
mMenuView.layout(-mMenuView.getMeasuredWidth(),
0
,
0
, mContentView.getMeasuredHeight());
}
}
|
上面的onMeasure、onLayout方法就是自定义View中经常重写的方法,在onMeasure是测量view的大小,这里把宽度类型设置为UNSPECIFIED,可以无限扩展。 onLayout是在view的大小测量之后,把view放到父布局的什么位置,代码里可以看出根据滑动方向吧menuView隐藏在左边(或右边)。
2.3 SwipeMenuAdapter
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
|
public
class
SwipeMenuAdapter
implements
WrapperListAdapter,
OnSwipeItemClickListener {
private
ListAdapter mAdapter;
private
Context mContext;
private
SwipeMenuListView.OnMenuItemClickListener onMenuItemClickListener;
public
SwipeMenuAdapter(Context context, ListAdapter adapter) {
mAdapter = adapter;
mContext = context;
}
。。。。
/**
* 添加滑动时的显示的菜单
* 在这里可以看出每一个Item都是一个SwipeMenuLayout
*/
public
View getView(
int
position, View convertView, ViewGroup parent) {
SwipeMenuLayout layout =
null
;
if
(convertView ==
null
) {
View contentView = mAdapter.getView(position, convertView, parent);
//item的view
SwipeMenu menu =
new
SwipeMenu(mContext);
//创建SwipeMenu
menu.setViewType(getItemViewType(position));
createMenu(menu);
//测试的,可以先不管
SwipeMenuView menuView =
new
SwipeMenuView(menu,
(SwipeMenuListView) parent);
menuView.setOnSwipeItemClickListener(
this
);
SwipeMenuListView listView = (SwipeMenuListView) parent;
layout =
new
SwipeMenuLayout(contentView, menuView,
listView.getCloseInterpolator(),
listView.getOpenInterpolator());
layout.setPosition(position);
}
else
{
layout = (SwipeMenuLayout) convertView;
layout.closeMenu();
layout.setPosition(position);
View view = mAdapter.getView(position, layout.getContentView(),
parent);
}
if
(mAdapter
instanceof
BaseSwipListAdapter) {
boolean
swipEnable = (((BaseSwipListAdapter) mAdapter).getSwipEnableByPosition(position));
layout.setSwipEnable(swipEnable);
}
return
layout;
}
//这个方法在创建时,重写啦,在这里是测试的,可以不管。
public
void
createMenu(SwipeMenu menu) {
// Test Code
。。。。。。
}
/**
* OnSwipeItemClickListener的回掉方法
* 这个方法在该类创建时,重写啦。
*/
public
void
onItemClick(SwipeMenuView view, SwipeMenu menu,
int
index) {
if
(onMenuItemClickListener !=
null
) {
onMenuItemClickListener.onMenuItemClick(view.getPosition(), menu,
index);
}
}
。。。。
//省略了不重要的
}
|
2.4 核心类:SwipeMenuListview,
这个代码很长,看的时候需要耐心。
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
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
|
public
class
SwipeMenuListView
extends
ListView {
private
static
final
int
TOUCH_STATE_NONE =
0
;
private
static
final
int
TOUCH_STATE_X =
1
;
private
static
final
int
TOUCH_STATE_Y =
2
;
public
static
final
int
DIRECTION_LEFT =
1
;
//方向
public
static
final
int
DIRECTION_RIGHT = -
1
;
private
int
mDirection =
1
;
//swipe from right to left by default
private
int
MAX_Y =
5
;
private
int
MAX_X =
3
;
private
float
mDownX;
private
float
mDownY;
private
int
mTouchState;
private
int
mTouchPosition;
private
SwipeMenuLayout mTouchView;
private
OnSwipeListener mOnSwipeListener;
//创建menuItem的
private
SwipeMenuCreator mMenuCreator;
//menuItem的item点击事件
private
OnMenuItemClickListener mOnMenuItemClickListener;
private
OnMenuStateChangeListener mOnMenuStateChangeListener;
private
Interpolator mCloseInterpolator;
//动画变化率
private
Interpolator mOpenInterpolator;
//----added in myself--下面这两行是我自己加的,
//你如果下下来代码demo运行下你会发现,当一个item已经滑开时,滑动另外的item,此时原来打开的item没有关闭,可以看下QQ的侧滑,它是关闭的,我这里就稍微修改了下。
private
int
mOldTouchPosition = -
1
;
private
boolean
shouldCloseMenu;
//--------
public
SwipeMenuListView(Context context) {
super
(context);
init();
}
public
SwipeMenuListView(Context context, AttributeSet attrs,
int
defStyle) {
super
(context, attrs, defStyle);
init();
}
public
SwipeMenuListView(Context context, AttributeSet attrs) {
super
(context, attrs);
init();
}
//初始化变量
private
void
init() {
MAX_X = dp2px(MAX_X);
MAX_Y = dp2px(MAX_Y);
mTouchState = TOUCH_STATE_NONE;
}
@Override
/**
* 对参数adapter进行了一次包装,包装成SwipeMenuAdapter
*/
public
void
setAdapter(ListAdapter adapter) {
super
.setAdapter(
new
SwipeMenuAdapter(getContext(), adapter) {
@Override
public
void
createMenu(SwipeMenu menu) {
if
(mMenuCreator !=
null
) {
mMenuCreator.create(menu);
}
}
@Override
public
void
onItemClick(SwipeMenuView view, SwipeMenu menu,
int
index) {
boolean
flag =
false
;
if
(mOnMenuItemClickListener !=
null
) {
flag = mOnMenuItemClickListener.onMenuItemClick(
view.getPosition(), menu, index);
}
//再次点击list中的item关闭menu
if
(mTouchView !=
null
&& !flag) {
mTouchView.smoothCloseMenu();
}
}
});
}
。。。。。
@Override
//拦截事件,判断事件是点击事件还是滑动事件
public
boolean
onInterceptTouchEvent(MotionEvent ev) {
//在拦截处处理,在滑动设置了点击事件的地方也能swip,点击时又不能影响原来的点击事件
int
action = ev.getAction();
switch
(action) {
case
MotionEvent.ACTION_DOWN:
mDownX = ev.getX();
mDownY = ev.getY();
boolean
handled =
super
.onInterceptTouchEvent(ev);
mTouchState = TOUCH_STATE_NONE;
//每次Down都把状态变为无状态
//返回item的position
mTouchPosition = pointToPosition((
int
) ev.getX(), (
int
) ev.getY());
//得到那个点击的item对应的view,就是SwipeMenuLayout
View view = getChildAt(mTouchPosition - getFirstVisiblePosition());
//只在空的时候赋值 以免每次触摸都赋值,会有多个open状态
if
(view
instanceof
SwipeMenuLayout) {
//如果有打开了 就拦截.mTouchView是SwipeMenuLayout
//如果两次是一个mTouchView,更新mTouchView;如果不是一个view,就拦截返回true
if
(mTouchView !=
null
&& mTouchView.isOpen() && !inRangeOfView(mTouchView.getMenuView(), ev)) {
Log.i(
"tag"
,
"Listview中的onInterceptTouchEvent ACTION_DOWN。"
);
return
true
;
}
mTouchView = (SwipeMenuLayout) view;
mTouchView.setSwipeDirection(mDirection);
//默认是left=1
}
//如果摸在另外一个view,拦截此事件
if
(mTouchView !=
null
&& mTouchView.isOpen() && view != mTouchView) {
handled =
true
;
}
if
(mTouchView !=
null
) {
mTouchView.onSwipe(ev);
}
return
handled;
case
MotionEvent.ACTION_MOVE:
//MOVE时拦截事件,在onTouch中进行处理
float
dy = Math.abs((ev.getY() - mDownY));
float
dx = Math.abs((ev.getX() - mDownX));
if
(Math.abs(dy) > MAX_Y || Math.abs(dx) > MAX_X) {
//每次拦截的down都把触摸状态设置成了TOUCH_STATE_NONE 只有返回true才会走onTouchEvent 所以写在这里就够了
if
(mTouchState == TOUCH_STATE_NONE) {
if
(Math.abs(dy) > MAX_Y) {
mTouchState = TOUCH_STATE_Y;
}
else
if
(dx > MAX_X) {
mTouchState = TOUCH_STATE_X;
if
(mOnSwipeListener !=
null
) {
mOnSwipeListener.onSwipeStart(mTouchPosition);
}
}
}
return
true
;
}
}
return
super
.onInterceptTouchEvent(ev);
}
@Override
public
boolean
onTouchEvent(MotionEvent ev) {
if
(ev.getAction() != MotionEvent.ACTION_DOWN && mTouchView ==
null
)
return
super
.onTouchEvent(ev);
int
action = ev.getAction();
switch
(action) {
case
MotionEvent.ACTION_DOWN:
//这个DOWN事件的前提是已经拦截事件啦,所以可能的情况时:1.该menu已经滑出来,再点击左边的item区域
//2.menu已经滑出来,点击了其他的item
//3.滑动item时,先DOWN在MOVE
Log.i(
"tag"
,
"Listview中的onTouchEvent ACTION_DOWN。是否点击了另一个item"
);
int
oldPos = mTouchPosition;
//这里设计不合理,onInterceptTouchEvent之后直接调用的这个事件,mTouchPosition是一样的
if
(mOldTouchPosition == -
1
){
//-1 is the original value
mOldTouchPosition = mTouchPosition;
}
mDownX = ev.getX();
mDownY = ev.getY();
mTouchState = TOUCH_STATE_NONE;
mTouchPosition = pointToPosition((
int
) ev.getX(), (
int
) ev.getY());
//list中
//这里改了,pldPos没有用,改为mOldTouchPosition
if
(mTouchPosition == mOldTouchPosition && mTouchView !=
null
&& mTouchView.isOpen()) {
mTouchState = TOUCH_STATE_X;
//x方向(横着)滑开
//调用SwipeMenuLayout的onSwipe()事件接口
mTouchView.onSwipe(ev);
Log.i(
"tag"
,
"Listview中的onTouchEvent ACTION_DOWN。滑动了或点击了另一个item"
);
return
true
;
}
if
(mOldTouchPosition != mTouchPosition){
//when the DOWN position is different
//shouldCloseMenu = true;
mOldTouchPosition = mTouchPosition;
}
View view = getChildAt(mTouchPosition - getFirstVisiblePosition());
//已经有一个menu滑开了,此时如果点击了另一个item
//这个方法永远执行不到!
if
(mTouchView !=
null
&& mTouchView.isOpen()) {
//关闭swipeMenu
mTouchView.smoothCloseMenu();
mTouchView =
null
;
// return super.onTouchEvent(ev);
// try to cancel the touch event
MotionEvent cancelEvent = MotionEvent.obtain(ev);
cancelEvent.setAction(MotionEvent.ACTION_CANCEL);
onTouchEvent(cancelEvent);
//取消事件,时间结束
//进行menu close的回掉
if
(mOnMenuStateChangeListener !=
null
) {
mOnMenuStateChangeListener.onMenuClose(oldPos);
}
return
true
;
}
if
(view
instanceof
SwipeMenuLayout) {
mTouchView = (SwipeMenuLayout) view;
mTouchView.setSwipeDirection(mDirection);
}
if
(mTouchView !=
null
) {
mTouchView.onSwipe(ev);
}
break
;
case
MotionEvent.ACTION_MOVE:
//有些可能有header,要减去header再判断
mTouchPosition = pointToPosition((
int
) ev.getX(), (
int
) ev.getY()) - getHeaderViewsCount();
//如果滑动了一下没完全展现,就收回去,这时候mTouchView已经赋值,再滑动另外一个不可以swip的view
//会导致mTouchView swip 。 所以要用位置判断是否滑动的是一个view
if
(!mTouchView.getSwipEnable() || mTouchPosition != mTouchView.getPosition()) {
break
;
}
float
dy = Math.abs((ev.getY() - mDownY));
float
dx = Math.abs((ev.getX() - mDownX));
if
(mTouchState == TOUCH_STATE_X) {
//X方向的话
if
(mTouchView !=
null
) {
mTouchView.onSwipe(ev);
//调用滑动事件
}
getSelector().setState(
new
int
[]{
0
});
ev.setAction(MotionEvent.ACTION_CANCEL);
super
.onTouchEvent(ev);
//事件结束
return
true
;
}
else
if
(mTouchState == TOUCH_STATE_NONE) {
//DOWN事件后的Move
if
(Math.abs(dy) > MAX_Y) {
mTouchState = TOUCH_STATE_Y;
}
else
if
(dx > MAX_X) {
mTouchState = TOUCH_STATE_X;
if
(mOnSwipeListener !=
null
) {
mOnSwipeListener.onSwipeStart(mTouchPosition);
}
}
}
break
;
case
MotionEvent.ACTION_UP:
//关闭了menu
Log.i(
"tag"
,
"onTouchEvent事件的ACTION_UP"
);
if
(mTouchState == TOUCH_STATE_X) {
if
(mTouchView !=
null
) {
Log.i(
"tag"
,
"onTouchEvent事件的ACTION_UP 为什么没有关闭"
);
boolean
isBeforeOpen = mTouchView.isOpen();
//调用滑动事件
mTouchView.onSwipe(ev);
boolean
isAfterOpen = mTouchView.isOpen();
if
(isBeforeOpen != isAfterOpen && mOnMenuStateChangeListener !=
null
) {
if
(isAfterOpen) {
mOnMenuStateChangeListener.onMenuOpen(mTouchPosition);
}
else
{
mOnMenuStateChangeListener.onMenuClose(mTouchPosition);
}
}
if
(!isAfterOpen) {
mTouchPosition = -
1
;
mTouchView =
null
;
}
}
if
(mOnSwipeListener !=
null
) {
//进行滑动结束的回掉
mOnSwipeListener.onSwipeEnd(mTouchPosition);
}
ev.setAction(MotionEvent.ACTION_CANCEL);
super
.onTouchEvent(ev);
return
true
;
}
break
;
}
return
super
.onTouchEvent(ev);
}
public
void
smoothOpenMenu(
int
position) {
if
(position >= getFirstVisiblePosition()
&& position <= getLastVisiblePosition()) {
View view = getChildAt(position - getFirstVisiblePosition());
if
(view
instanceof
SwipeMenuLayout) {
mTouchPosition = position;
if
(mTouchView !=
null
&& mTouchView.isOpen()) {
mTouchView.smoothCloseMenu();
}
mTouchView = (SwipeMenuLayout) view;
mTouchView.setSwipeDirection(mDirection);
mTouchView.smoothOpenMenu();
}
}
}
/**
* 可以进去看源代码,就是将不同的单位统一转换成像素px,这里是dp->px
* @param dp
* @return
*/
private
int
dp2px(
int
dp) {
return
(
int
) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp,
getContext().getResources().getDisplayMetrics());
}
public
static
interface
OnMenuItemClickListener {
boolean
onMenuItemClick(
int
position, SwipeMenu menu,
int
index);
}
public
static
interface
OnSwipeListener {
void
onSwipeStart(
int
position);
void
onSwipeEnd(
int
position);
}
public
static
interface
OnMenuStateChangeListener {
void
onMenuOpen(
int
position);
void
onMenuClose(
int
position);
}
。。。。
}
|
这个类中最重要的逻辑就是关于事件的判断和分发,什么时候拦截事件,不同的事件对应什么操作。如果对事件分发不清楚的同学,可以在网上找找相关的博客,也可以看我的后续博客,应该这两天的事。
在这里分析SwipeMenuListView的事件分发逻辑:核心就是SwipeMenuListView中item的点击事件和滑动事件的处理。当滑动时SwipeMenuListView拦截事件,自己处理,记住这个逻辑看代码就一目了然了。下面是我画的一个事件分发流程图:
触摸事件是一个事件序列:ACTION_DOWN->ACTION_MOVE->....ACTION_MOVE->ACTION_UP. 以ACTION_DOWN开始,以ACTION_UP结束。
下边是我的一个打印的流程:(自己在代码中加log)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
I/tag: Listview中的onInterceptTouchEvent ACTION_DOWN。view=
class
com.baoyz.swipemenulistview.SwipeMenuLayout
I/tag: onInterceptTouchEvent ACTION_DOWN handled=
false
I/tag: SwipeMenuLayout onTouchEvent
I/tag: Listview中的onTouchEvent ACTION_DOWN。是否点击了另一个item
I/tag: oldPos=
1
mTouchPosition=
1
I/tag: ACTION_MOVE downX =
987
, moveX =
906.69666
, dis=
80
I/tag: ACTION_MOVE downX =
987
, moveX =
855.5785
, dis=
131
I/tag: ACTION_MOVE downX =
987
, moveX =
797.6258
, dis=
189
I/tag: ACTION_MOVE downX =
987
, moveX =
735.9639
, dis=
251
I/tag: ACTION_MOVE downX =
987
, moveX =
666.5104
, dis=
320
I/tag: ACTION_MOVE downX =
987
, moveX =
589.0626
, dis=
397
I/tag: ACTION_MOVE downX =
987
, moveX =
509.15567
, dis=
477
I/tag: ACTION_MOVE downX =
987
, moveX =
431.7224
, dis=
555
I/tag: ACTION_MOVE downX =
987
, moveX =
361.2613
, dis=
625
I/tag: ACTION_MOVE downX =
987
, moveX =
319.70398
, dis=
667
I/tag: onTouchEvent事件的ACTION_UP
I/tag: onTouchEvent事件的ACTION_UP 为什么没有关闭
I/tag: isFling=
true
e1.getX()=
987.08606
e2.getX()=
319.70398
velocityX=-
4122.911
MAX_VELOCITYX=-
1500
I/tag: ACTION_UP downX =
987
, moveX =
319.70398
I/tag: mContentView.getLeft()=-
540
, mMenuView=
540
|
三、存在的问题
1.如果你下下来框架运行了,你会发现一个问题:
当ListView的一个item已经滑开,假设为item1;此时滑动另外一个的item,叫它item2;
这种情况下item1不会关闭,item2当然也不会打开。
这种效果并不好,我在代码中已经修改了这个问题。具体代码,我已经标明。
2.就是下面的这段代码:在SwipeMenuListView的onTouchEvent(MotionEvent ev)的ACTION_DOWN中,这段代码永远不会执行到,因为onTouchEvent和onInterceptTouchEvent对应的一个MotionEvent。
mTouchPosition ==oldPos永远相等。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
//这个方法永远执行不到!作者的愿意是当mTouchPosition != oldPos时CloseMenu,但是按照这个代码这两个值是永远相等的,
//因为对应的是一个MotionEvent当然就相等啦
if
(mTouchView !=
null
&& mTouchView.isOpen()) {
//关闭swipeMenu
mTouchView.smoothCloseMenu();
//mTouchView = null;
// return super.onTouchEvent(ev);
// try to cancel the touch event
MotionEvent cancelEvent = MotionEvent.obtain(ev);
cancelEvent.setAction(MotionEvent.ACTION_CANCEL);
onTouchEvent(cancelEvent);
//取消事件,时间结束
//进行menu close的回掉
if
(mOnMenuStateChangeListener !=
null
) {
mOnMenuStateChangeListener.onMenuClose(oldPos);
}
return
true
;
}
|
在代码中我已经修改了这个问题。目前已经在github上提交给原作者啦。