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 出一个自己 SwipeMenuLayout 出来,包括用户的 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 来实现的