SwipeMenuListView(滑动菜单)-SwipeMenuListView框架完全解析

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上提交给原作者啦。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值