Github: SwipeMenuListView 分析版本:d1cb862
SwipeMenuListView 是一个像 Android QQ 那样在 ListView 中拉出菜单的开源库。
SwipeMenuListView 是一个很棒的 ListView 控件,但是现在作者已经没有维护了。
使用 添加依赖 1 2 3 dependencies { compile 'com.baoyz.swipemenulistview:library:1.3.0' }
Step 1 添加 SwipeMenuListView 到 layout 布局中
1 2 3 4 <com.baoyz.swipemenulistview.SwipeMenuListView android:id ="@+id/listView" android:layout_width ="match_parent" android:layout_height ="match_parent" />
Step 2 创建 SwipeMenuCreator
并添加 items
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 SwipeMenuCreator creator = new SwipeMenuCreator() { @Override public void create (SwipeMenu menu) { SwipeMenuItem openItem = new SwipeMenuItem( getApplicationContext()); openItem.setBackground(new ColorDrawable(Color.rgb(0xC9 , 0xC9 , 0xCE ))); openItem.setWidth(dp2px(90 )); openItem.setTitle("Open" ); openItem.setTitleSize(18 ); openItem.setTitleColor(Color.WHITE); menu.addMenuItem(openItem); SwipeMenuItem deleteItem = new SwipeMenuItem( getApplicationContext()); deleteItem.setBackground(new ColorDrawable(Color.rgb(0xF9 , 0x3F , 0x25 ))); deleteItem.setWidth(dp2px(90 )); deleteItem.setIcon(R.drawable.ic_delete); menu.addMenuItem(deleteItem); } }; listView.setMenuCreator(creator);
Step 3 Menu 的 Click 监听器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 listView.setOnMenuItemClickListener(new OnMenuItemClickListener() { @Override public boolean onMenuItemClick (int position, SwipeMenu menu, int index) { switch (index) { case 0 : break ; case 1 : break ; } return false ; } });
滑动方向 1 2 3 4 5 mListView.setSwipeDirection(SwipeMenuListView.DIRECTION_RIGHT); mListView.setSwipeDirection(SwipeMenuListView.DIRECTION_LEFT);
利用 Adapter 中的 ViewType
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class AppAdapter extends BaseAdapter { ... @Override public int getViewTypeCount () { return 2 ; } @Override public int getItemViewType (int position) { return type; } ... }
通过 view type 来创建不同的 menus
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 SwipeMenuCreator creator = new SwipeMenuCreator() { @Override public void create (SwipeMenu menu) { switch (menu.getViewType()) { case 0 : break ; case 1 : break ; ... } } };
其他 OnSwipeListener
1 2 3 4 5 6 7 8 9 10 11 12 listView.setOnSwipeListener(new OnSwipeListener() { @Override public void onSwipeStart (int position) { } @Override public void onSwipeEnd (int position) { } });
平滑打开 menu
1 listView.smoothOpenMenu(position);
打开或者关闭 menu 的动画插值器
1 2 3 4 listView.setCloseInterpolator(new BounceInterpolator()); listView.setOpenInterpolator(...);
源码 先从 SwipeMenuListView
开始看,从构造函数和常用 API 开始:
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 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 ; private int MAX_Y = 5 ; private int MAX_X = 3 ; private int mTouchState; 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 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); } if (mTouchView != null && !flag) { mTouchView.smoothCloseMenu(); } } }); } }
从构造器中看不出来什么,只是进行了初始化操作,以及有一个手势的状态机。当看到 ListView#setAdapter(ListAdapter)
的时候,发现实际设置进去的 Adapter 外面还包了一层 SwipeMenuAdapter
,那么来看看 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 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 public class SwipeMenuAdapter implements WrapperListAdapter , SwipeMenuView .OnSwipeItemClickListener { private ListAdapter mAdapter; private Context mContext; private SwipeMenuListView.OnMenuItemClickListener onMenuItemClickListener; public SwipeMenuAdapter (Context context, ListAdapter adapter) { mAdapter = adapter; mContext = context; } @Override public int getCount () { return mAdapter.getCount(); } @Override public Object getItem (int position) { return mAdapter.getItem(position); } @Override public long getItemId (int position) { return mAdapter.getItemId(position); } @Override public View getView (int position, View convertView, ViewGroup parent) { SwipeMenuLayout layout = null ; if (convertView == null ) { View contentView = mAdapter.getView(position, convertView, parent); SwipeMenu menu = new SwipeMenu(mContext); menu.setViewType(mAdapter.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); } return layout; } public void createMenu (SwipeMenu menu) { SwipeMenuItem item = new SwipeMenuItem(mContext); item.setTitle("Item 1" ); item.setBackground(new ColorDrawable(Color.GRAY)); item.setWidth(300 ); menu.addMenuItem(item); item = new SwipeMenuItem(mContext); item.setTitle("Item 2" ); item.setBackground(new ColorDrawable(Color.RED)); item.setWidth(300 ); menu.addMenuItem(item); } @Override public void onItemClick (SwipeMenuView view, SwipeMenu menu, int index) { if (onMenuItemClickListener != null ) { onMenuItemClickListener.onMenuItemClick(view.getPosition(), menu, index); } } public void setOnMenuItemClickListener ( SwipeMenuListView.OnMenuItemClickListener onMenuItemClickListener) { this .onMenuItemClickListener = onMenuItemClickListener; } @Override public void registerDataSetObserver (DataSetObserver observer) { mAdapter.registerDataSetObserver(observer); } @Override public void unregisterDataSetObserver (DataSetObserver observer) { mAdapter.unregisterDataSetObserver(observer); } @Override public boolean areAllItemsEnabled () { return mAdapter.areAllItemsEnabled(); } @Override public boolean isEnabled (int position) { return mAdapter.isEnabled(position); } @Override public boolean hasStableIds () { return mAdapter.hasStableIds(); } @Override public int getItemViewType (int position) { return mAdapter.getItemViewType(position); } @Override public int getViewTypeCount () { return mAdapter.getViewTypeCount(); } @Override public boolean isEmpty () { return mAdapter.isEmpty(); } @Override public ListAdapter getWrappedAdapter () { return mAdapter; } }
SwipeMenuAdapter
实现了 WrapperListAdapter
,而 WrapperListAdapter
的父类是 ListAdapter
,也就是说 WrapperListAdapter
是一个 ListAdapter
包装类。那么我们这里可以将这个类看简单点,可以通过看一个 BaseAdapter
来看这个类,也就是我们需要关心的是 getView()
,getItemId()
,getItem()
,getCounts()
,而在 SwipeMenuAdapter
中可以看出在 getView()
中的操作比较多。
在 getView()
中首先判断参数 convertView
是不是为 null ,如果是,那么 new 出一个自己 SwipeMenuLayou
t 出来,包括用户的 item view 和 menu view,然后返回;如果不为 null ,那么参数 convertView
应该是 SwipeMenuLayout
,通过调用 adapter.getView()
将 用户的 item view 传递给用户。
那么我们先来看看 Menu 的 View :
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 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; List<SwipeMenuItem> items = menu.getMenuItems(); int id = 0 ; for (SwipeMenuItem item : items) { addItem(item, id++); } } * 将 MenuItem 转换成 UI控件 */ private void addItem (SwipeMenuItem item, int id) { LayoutParams params = new LayoutParams(item.getWidth(), LayoutParams.MATCH_PARENT); LinearLayout parent = new LinearLayout(getContext()); parent.setId(id); parent.setGravity(Gravity.CENTER); parent.setOrientation(LinearLayout.VERTICAL); parent.setLayoutParams(params); parent.setBackgroundDrawable(item.getBackground()); parent.setOnClickListener(this ); addView(parent); if (item.getIcon() != null ) { parent.addView(createIcon(item)); } if (!TextUtils.isEmpty(item.getTitle())) { parent.addView(createTitle(item)); } } * 创建图片 */ private ImageView createIcon (SwipeMenuItem item) { ImageView iv = new ImageView(getContext()); iv.setImageDrawable(item.getIcon()); return iv; } * 创建文字 */ 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 public void onClick (View v) { if (onItemClickListener != null && mLayout.isOpen()) { onItemClickListener.onItemClick(this , mMenu, v.getId()); } } public OnSwipeItemClickListener getOnSwipeItemClickListener () { return onItemClickListener; } public void setOnSwipeItemClickListener (OnSwipeItemClickListener onItemClickListener) { this .onItemClickListener = onItemClickListener; } * 设置SwipeMenuLayout */ public void setLayout (SwipeMenuLayout mLayout) { this .mLayout = mLayout; } public static interface OnSwipeItemClickListener { void onItemClick (SwipeMenuView view, SwipeMenu menu, int index) ; } }
SwipeMenuView
继承 LinearLayout
,然后在子 View 是很多个 LinearLayout
,而这每个子 View 中的子子 View 就是 各个 Menu Item 所转换出来的 UI 控件。需要注意的是 LayoutParams params = new LayoutParams(item.getWidth(), LayoutParams.MATCH_PARENT);
,设置的宽度是通过 Menu Item 定了的。
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 public class SwipeMenuLayout extends FrameLayout { private View mContentView; private SwipeMenuView mMenuView; private Interpolator mCloseInterpolator; private Interpolator mOpenInterpolator; private OnGestureListener mGestureListener; private GestureDetectorCompat mGestureDetector; private ScrollerCompat mOpenScroller; private ScrollerCompat mCloseScroller; private boolean isFling; private int MIN_FLING = dp2px(15 ); private int MAX_VELOCITYX = -dp2px(500 ); 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; mMenuView.setLayout(this ); init(); } private SwipeMenuLayout (Context context, AttributeSet attrs) { super (context, attrs); } private SwipeMenuLayout (Context context) { super (context); } 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 public boolean onFling (MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { if (Math.abs(e1.getX() - e2.getX()) > MIN_FLING && velocityX < MAX_VELOCITYX) { isFling = true ; } return super .onFling(e1, e2, velocityX, velocityY); } }; mGestureDetector = new GestureDetectorCompat(getContext(), mGestureListener); if (mCloseInterpolator != null ) { mCloseScroller = ScrollerCompat.create(getContext(), mCloseInterpolator); } else { mCloseScroller = ScrollerCompat.create(getContext()); } if (mOpenInterpolator != null ) { mOpenScroller = ScrollerCompat.create(getContext(), mOpenInterpolator); } else { mOpenScroller = ScrollerCompat.create(getContext()); } LayoutParams contentParams = new LayoutParams( LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); mContentView.setLayoutParams(contentParams); if (mContentView.getId() < 1 ) { mContentView.setId(CONTENT_VIEW_ID); } mMenuView.setId(MENU_VIEW_ID); mMenuView.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)); addView(mContentView); addView(mMenuView); } private int dp2px (int dp) { return (int ) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, getContext().getResources().getDisplayMetrics()); } }
SwipeMenuLayout
是一个 FrameLayout
,两个子 View ,分别是用户的 item View 和 menu View 。手指的时候滑动的操作是通过 SimpleOnGestureListener
来完成的。
继续看这个 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 public class SwipeMenuLayout extends FrameLayout { private int mSwipeDirection; public void setSwipeDirection (int swipeDirection) { mSwipeDirection = swipeDirection; } @Override protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec) { super .onMeasure(widthMeasureSpec, heightMeasureSpec); mMenuView.measure(MeasureSpec.makeMeasureSpec(0 , MeasureSpec.UNSPECIFIED), MeasureSpec.makeMeasureSpec( getMeasuredHeight(), MeasureSpec.EXACTLY)); } @Override 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) { mMenuView.layout(getMeasuredWidth(), 0 , getMeasuredWidth() + mMenuView.getMeasuredWidth(), mContentView.getMeasuredHeight()); } else { mMenuView.layout(-mMenuView.getMeasuredWidth(), 0 , 0 , mContentView.getMeasuredHeight()); } } }
通过 SwipeMenuLayout
的 onMeasure()
给 SwipeMenuView
传递一个确切的高度,然后在 onLayout()
中将 SwipeMenuView
通过方向放在方向对应的屏幕外边的位置。
接下来看看 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 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 public class SwipeMenuLayout extends FrameLayout { private static final int STATE_CLOSE = 0 ; private static final int STATE_OPEN = 1 ; private int state = STATE_CLOSE; private int mDownX; private int mBaseX; public boolean onSwipe (MotionEvent event) { mGestureDetector.onTouchEvent(event); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: mDownX = (int ) event.getX(); isFling = false ; break ; case MotionEvent.ACTION_MOVE: int dis = (int ) (mDownX - event.getX()); if (state == STATE_OPEN) { dis += mMenuView.getWidth()*mSwipeDirection;; } swipe(dis); break ; case MotionEvent.ACTION_UP: if ((isFling || Math.abs(mDownX - event.getX()) > (mMenuView.getWidth() / 2 )) && Math.signum(mDownX - event.getX()) == mSwipeDirection) { smoothOpenMenu(); } else { smoothCloseMenu(); return false ; } break ; } return true ; } private void swipe (int dis) { if (Math.signum(dis) != mSwipeDirection) { dis = 0 ; } else if (Math.abs(dis) > mMenuView.getWidth()) { dis = mMenuView.getWidth()*mSwipeDirection; } mContentView.layout(-dis, mContentView.getTop(), mContentView.getWidth() -dis, getMeasuredHeight()); if (mSwipeDirection == SwipeMenuListView.DIRECTION_LEFT) { 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()); } } public void smoothOpenMenu () { state = STATE_OPEN; if (mSwipeDirection == SwipeMenuListView.DIRECTION_LEFT) { mOpenScroller.startScroll(-mContentView.getLeft(), 0 , mMenuView.getWidth(), 0 , 350 ); } else { mOpenScroller.startScroll(mContentView.getLeft(), 0 , mMenuView.getWidth(), 0 , 350 ); } postInvalidate(); } public void smoothCloseMenu () { state = STATE_CLOSE; if (mSwipeDirection == SwipeMenuListView.DIRECTION_LEFT) { mBaseX = -mContentView.getLeft(); mCloseScroller.startScroll(0 , 0 , mMenuView.getWidth(), 0 , 350 ); } else { mBaseX = mMenuView.getRight(); mCloseScroller.startScroll(0 , 0 , mMenuView.getWidth(), 0 , 350 ); } postInvalidate(); } public void closeMenu () { if (mCloseScroller.computeScrollOffset()) { mCloseScroller.abortAnimation(); } if (state == STATE_OPEN) { state = STATE_CLOSE; swipe(0 ); } } @Override public void computeScroll () { if (state == STATE_OPEN) { if (mOpenScroller.computeScrollOffset()) { swipe(mOpenScroller.getCurrX()*mSwipeDirection); postInvalidate(); } } else { if (mCloseScroller.computeScrollOffset()) { swipe((mBaseX - mCloseScroller.getCurrX())*mSwipeDirection); postInvalidate(); } } } }
SwipeMenuLayout
通过 Scroller
达到顺滑的打开和关闭,同时 Scroller
每次计算出滑动的值的时候传递给 swipe(int)
方法,该方法通过 View#layout(...)
方法实现位置的变换。
好,那么看完了 SwipeMenuLayout
,回过头来再看看 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 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 ; private int mTouchState; private int mTouchPosition; private SwipeMenuLayout mTouchView; private float mDownX; private float mDownY; @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: int oldPos = mTouchPosition; mDownX = ev.getX(); mDownY = ev.getY(); mTouchState = TOUCH_STATE_NONE; mTouchPosition = pointToPosition((int ) ev.getX(), (int ) ev.getY()); if (mTouchPosition == oldPos && mTouchView != null && mTouchView.isOpen()) { mTouchState = TOUCH_STATE_X; mTouchView.onSwipe(ev); return true ; } View view = getChildAt(mTouchPosition - getFirstVisiblePosition()); if (mTouchView != null && mTouchView.isOpen()) { mTouchView.smoothCloseMenu(); mTouchView = null ; MotionEvent cancelEvent = MotionEvent.obtain(ev); cancelEvent.setAction(MotionEvent.ACTION_CANCEL); onTouchEvent(cancelEvent); 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: float dy = Math.abs((ev.getY() - mDownY)); float dx = Math.abs((ev.getX() - mDownX)); if (mTouchState == TOUCH_STATE_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) { 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: if (mTouchState == TOUCH_STATE_X) { if (mTouchView != null ) { 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); } }
在 MotionEvent.ACTION_DOWN
中有一段是 if (mTouchView != null && mTouchView.isOpen()) {...}
,这里的判断是指当前手指对应的 view 与 mTouchView 不是同一个的话,且 mTouchView 是滑开的状态,那么就去处理 mTouchView ,将 menu 关闭,然后结束这次手势操作,这里可以发现结束手势操作之后,手指不离开屏幕的话也是无法滑动的,因为就是一开始的 if (ev.getAction() != MotionEvent.ACTION_DOWN && mTouchView == null)
,满足条件就会调用 return super.onTouchEvent(ev);
,而 ev 的 action 一直是 CANCEL 。
在 MotionEvent.ACTION_MOVE
中 mTouchView.onSwipe(ev)
之后 ev.setAction(MotionEvent.ACTION_CANCEL); super.onTouchEvent(ev);
是因为 SwipeMenuLayout
移动位置的时候,手指可能在 Y 轴上的动作也比较大,此时让 ListView 忽略,直接穿的 action 为 CANCEL ,这样 ListView 就不会因为在 Y 轴上有动作而滑动。
SwipeListView
核心部分就分析完了。
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 public class SwipeMenu { private Context mContext; private List<SwipeMenuItem> mItems; private int mViewType; public SwipeMenu (Context context) { mContext = context; mItems = new ArrayList<SwipeMenuItem>(); } public Context getContext () { return mContext; } public void addMenuItem (SwipeMenuItem item) { mItems.add(item); } public void removeMenuItem (SwipeMenuItem item) { mItems.remove(item); } public List<SwipeMenuItem> getMenuItems () { return mItems; } public SwipeMenuItem getMenuItem (int index) { return mItems.get(index); } public int getViewType () { return mViewType; } public void setViewType (int viewType) { this .mViewType = viewType; } }
之前代码里面也出现过,这里把这部分数据结构拿出来。
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 public class SwipeMenuItem { private int id; private Context mContext; private String title; private Drawable icon; private Drawable background; private int titleColor; private int titleSize; private int width; public SwipeMenuItem (Context context) { mContext = context; } public int getId () { return id; } public void setId (int id) { this .id = id; } public int getTitleColor () { return titleColor; } public int getTitleSize () { return titleSize; } public void setTitleSize (int titleSize) { this .titleSize = titleSize; } public void setTitleColor (int titleColor) { this .titleColor = titleColor; } public String getTitle () { return title; } public void setTitle (String title) { this .title = title; } public void setTitle (int resId) { setTitle(mContext.getString(resId)); } public Drawable getIcon () { return icon; } public void setIcon (Drawable icon) { this .icon = icon; } public void setIcon (int resId) { this .icon = mContext.getResources().getDrawable(resId); } public Drawable getBackground () { return background; } public void setBackground (Drawable background) { this .background = background; } public void setBackground (int resId) { this .background = mContext.getResources().getDrawable(resId); } public int getWidth () { return width; } public void setWidth (int width) { this .width = width; } }
之前代码里面也出现过,这里把这部分数据结构拿出来。
总结
在 SwipeListView
手势那部分的 ACTION_CANCEL
很经典!
ListView 的手势操作太复杂,没有通过 onInterceptTouchEvent
来拦截事件,转而是将 Event 传递给 item View ,让 item View 去操作
View 的移动是通过 View.layout(...)
来实现的
平滑的移动是通过 Scroller
来实现的