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

来源:互联网 发布:sftp命令 端口 编辑:程序博客网 时间:2024/06/05 04:39

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, nullnull);
    }
 
    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(00, mMenuView.getWidth(), 0350);
        else {
            mBaseX = mMenuView.getRight();
            mCloseScroller.startScroll(00, mMenuView.getWidth(), 0350);
        }
        postInvalidate();
    }
 
    public void smoothOpenMenu() {
        if(!mSwipEnable){
            return ;
        }
        state = STATE_OPEN;
        if (mSwipeDirection == SwipeMenuListView.DIRECTION_LEFT) {
            mOpenScroller.startScroll(-mContentView.getLeft(), 0, mMenuView.getWidth(), 0350);
            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(), 0350);
        }
        //在非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(00, 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上提交给原作者啦。