SwipeMenuListview源码解析
来源:互联网 发布:daz3d知乎 编辑:程序博客网 时间:2024/06/06 00:10
SwipeMenuListView 是一个像 Android QQ 那样在 ListView 中拉出菜单的开源库。
一、SwipeMenuListView
SwipeMenuListView 是一个很棒的 ListView 控件,但是现在作者已经没有维护了。
二、使用
添加依赖
123
dependencies { compile 'com.baoyz.swipemenulistview:library:1.3.0'}
Step 1
添加 SwipeMenuListView 到 layout 布局中
1234
<com.baoyz.swipemenulistview.SwipeMenuListView android:id="@+id/listView" android:layout_width="match_parent" android:layout_height="match_parent" />
Step 2
创建 SwipeMenuCreator
并添加 items
1234567891011121314151617181920212223242526272829303132333435363738
SwipeMenuCreator creator = new SwipeMenuCreator() { @Override public void create(SwipeMenu menu) { // create "open" item SwipeMenuItem openItem = new SwipeMenuItem( getApplicationContext()); // set item background openItem.setBackground(new ColorDrawable(Color.rgb(0xC9, 0xC9, 0xCE))); // set item width openItem.setWidth(dp2px(90)); // set item title openItem.setTitle("Open"); // set item title fontsize openItem.setTitleSize(18); // set item title font color openItem.setTitleColor(Color.WHITE); // add to menu menu.addMenuItem(openItem); // create "delete" item SwipeMenuItem deleteItem = new SwipeMenuItem( getApplicationContext()); // set item background deleteItem.setBackground(new ColorDrawable(Color.rgb(0xF9, 0x3F, 0x25))); // set item width deleteItem.setWidth(dp2px(90)); // set a icon deleteItem.setIcon(R.drawable.ic_delete); // add to menu menu.addMenuItem(deleteItem); }};// set creatorlistView.setMenuCreator(creator);
Step 3
Menu 的 Click 监听器
123456789101112131415
listView.setOnMenuItemClickListener(new OnMenuItemClickListener() { @Override public boolean onMenuItemClick(int position, SwipeMenu menu, int index) { switch (index) { case 0: // open break; case 1: // delete break; } // false : close the menu; true : not close the menu return false; }});
设定滑动方向
12345
// RightmListView.setSwipeDirection(SwipeMenuListView.DIRECTION_RIGHT);// LeftmListView.setSwipeDirection(SwipeMenuListView.DIRECTION_LEFT);
创建不同的 Menu
利用 Adapter 中的 ViewType
123456789101112131415161718
class AppAdapter extends BaseAdapter { ... @Override public int getViewTypeCount() { // menu type count return 2; } @Override public int getItemViewType(int position) { // current menu type return type; } ...}
通过 view type 来创建不同的 menus
1234567891011121314151617
SwipeMenuCreator creator = new SwipeMenuCreator() { @Override public void create(SwipeMenu menu) { // Create different menus depending on the view type switch (menu.getViewType()) { case 0: // create menu of type 0 break; case 1: // create menu of type 1 break; ... } } };
其他
OnSwipeListener
123456789101112
listView.setOnSwipeListener(new OnSwipeListener() { @Override public void onSwipeStart(int position) { // swipe start } @Override public void onSwipeEnd(int position) { // swipe end }});
平滑打开 menu
1
listView.smoothOpenMenu(position);
打开或者关闭 menu 的动画插值器
1234
// Close InterpolatorlistView.setCloseInterpolator(new BounceInterpolator());// Open InterpolatorlistView.setOpenInterpolator(...);
三、源码
SwipeMenuListView 0x00
1、先从 SwipeMenuListView
开始看,从构造函数和常用 API 开始:
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455
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
:
SwipeMenuAdapter
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
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;} //WrapperListAdapter中方法@Overridepublic int getCount() {return mAdapter.getCount();}//WrapperListAdapter中方法@Overridepublic Object getItem(int position) {return mAdapter.getItem(position);}//WrapperListAdapter中方法@Overridepublic long getItemId(int position) {return mAdapter.getItemId(position);} //WrapperListAdapter中方法@Overridepublic View getView(int position, View convertView, ViewGroup parent) {SwipeMenuLayout layout = null;if (convertView == null) {//刚初始化的时候//调用adapter的getView得到用户返回的ViewView contentView = mAdapter.getView(position, convertView, parent);//new一个SwipeMenuSwipeMenu menu = new SwipeMenu(mContext);//设置当前的ViewTypemenu.setViewType(mAdapter.getItemViewType(position));//向SwipeMenu中添加SwipeMenuItemcreateMenu(menu); //menu的ViewSwipeMenuView menuView = new SwipeMenuView(menu,(SwipeMenuListView) parent);//设置监听器menuView.setOnSwipeItemClickListener(this);//SwipeMenuListViewSwipeMenuListView listView = (SwipeMenuListView) parent;//整个item,SwipeMenuLayoutlayout = new SwipeMenuLayout(contentView, menuView,listView.getCloseInterpolator(),listView.getOpenInterpolator());//设置position位置layout.setPosition(position);} else {layout = (SwipeMenuLayout) convertView;//关闭menulayout.closeMenu();//设置position位置layout.setPosition(position);//调用adapter的getView,将用户的View传递出去View view = mAdapter.getView(position, layout.getContentView(),parent);}return layout;}public void createMenu(SwipeMenu menu) {// Test CodeSwipeMenuItem 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);}@Overridepublic 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;}//WrapperListAdapter中方法@Overridepublic void registerDataSetObserver(DataSetObserver observer) {mAdapter.registerDataSetObserver(observer);}//WrapperListAdapter中方法@Overridepublic void unregisterDataSetObserver(DataSetObserver observer) {mAdapter.unregisterDataSetObserver(observer);}//WrapperListAdapter中方法@Overridepublic boolean areAllItemsEnabled() {return mAdapter.areAllItemsEnabled();}//WrapperListAdapter中方法@Overridepublic boolean isEnabled(int position) {return mAdapter.isEnabled(position);}//WrapperListAdapter中方法@Overridepublic boolean hasStableIds() {return mAdapter.hasStableIds();}//WrapperListAdapter中方法@Overridepublic int getItemViewType(int position) {return mAdapter.getItemViewType(position);}//WrapperListAdapter中方法@Overridepublic int getViewTypeCount() {return mAdapter.getViewTypeCount();}//WrapperListAdapter中方法@Overridepublic boolean isEmpty() {return mAdapter.isEmpty();}//WrapperListAdapter中方法@Overridepublic 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 :
SwipeMenuView
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
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;//获得传入的Menu中的MenuItemList<SwipeMenuItem> items = menu.getMenuItems();int id = 0;//通过item构造出View添加到SwipeMenuView中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;}//点击事件@Overridepublic void onClick(View v) { //menu滑开的时候才算点击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 定了的。
SwipeMenuLayout
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
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;//滑开的scrollerprivate ScrollerCompat mCloseScroller;//关闭的scroller private boolean isFling;//手指满足是否滑动的标志位 private int MIN_FLING = dp2px(15);//手指最小移动距离,大于这个距离可能会算作滑动private int MAX_VELOCITYX = -dp2px(500);//X轴方向手指滑动速度 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设置给SwipeMenuViewmMenuView.setLayout(this);init();} private SwipeMenuLayout(Context context, AttributeSet attrs) {super(context, attrs);}private SwipeMenuLayout(Context context) {super(context);} private void init() {//设置改控件的宽度match_parent,高度wrap_contentsetLayoutParams(new AbsListView.LayoutParams(LayoutParams.MATCH_PARENT,LayoutParams.WRAP_CONTENT));//Simple GesturemGestureListener = new SimpleOnGestureListener() {@Overridepublic boolean onDown(MotionEvent e) {isFling = false;return true;}//滑动@Overridepublic boolean onFling(MotionEvent e1, MotionEvent e2,float velocityX, float velocityY) {//判断滑动的距离是否大于MIN_FLING 以及X轴方向的速度是否小于MAX_VELOCITYX(MAX_VELOCITYX是负值)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);//new Scrollerif (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());}//设置layoutParamsLayoutParams contentParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);mContentView.setLayoutParams(contentParams);//设置IDif (mContentView.getId() < 1) {mContentView.setId(CONTENT_VIEW_ID);}//设置layoutParams和idmMenuView.setId(MENU_VIEW_ID);mMenuView.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT));//添加到SwipeMenuLayout中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
:
1234567891011121314151617181920212223242526272829303132
public class SwipeMenuLayout extends FrameLayout {private int mSwipeDirection; public void setSwipeDirection(int swipeDirection) {mSwipeDirection = swipeDirection;} @Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);//measure SwipeMenuView 高传的是getMeasuredHeight()且MeasureSpec.EXACTLYmMenuView.measure(MeasureSpec.makeMeasureSpec(0,MeasureSpec.UNSPECIFIED), MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY));}@Overrideprotected void onLayout(boolean changed, int l, int t, int r, int b) {mContentView.layout(0, 0, getMeasuredWidth(),mContentView.getMeasuredHeight());//通过方向来判断将SwipeMenuView放在哪个位置,而且这些位置都是在屏幕外边的位置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
是怎么滑动的:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
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; //这是一个对外暴露的API,而调用这个API的是SwipeMenuListView,那么MotionEvent是SwipeMenuListView的MotionEventpublic boolean onSwipe(MotionEvent event) { //Gesture的判断mGestureDetector.onTouchEvent(event);switch (event.getAction()) {case MotionEvent.ACTION_DOWN: //记录down的X坐标mDownX = (int) event.getX(); //滑动标志位置为falseisFling = false;break;case MotionEvent.ACTION_MOVE: //计算手指滑动距离int dis = (int) (mDownX - event.getX());if (state == STATE_OPEN) { //DIRECTION_LEFT = 1 || DIRECTION_RIGHT = -1dis += mMenuView.getWidth()*mSwipeDirection;;}swipe(dis);break;case MotionEvent.ACTION_UP: //滑动状态 && 滑开距离 > SwipeMenuView / 2 && 滑动方向 == mSwipeDirectionif ((isFling || Math.abs(mDownX - event.getX()) > (mMenuView.getWidth() / 2)) && Math.signum(mDownX - event.getX()) == mSwipeDirection) {//滑开这个menusmoothOpenMenu();} else {//关闭这个menusmoothCloseMenu();return false;}break;}return true;} private void swipe(int dis) { //通过dis的正负 与 mSwipeDirection 对比相同与否if (Math.signum(dis) != mSwipeDirection) { //不相同的话dis归0,表示不滑动dis = 0;} else if (Math.abs(dis) > mMenuView.getWidth()) {//此时说明相同 //如果dis大于了SwipeMenuView的宽度的话,将dis设置为SwipeMenuView的宽度dis = mMenuView.getWidth()*mSwipeDirection;}//用户的View移动,腾出位置mContentView.layout(-dis, mContentView.getTop(),mContentView.getWidth() -dis, getMeasuredHeight());//通过方向移动SwipeMenuViewif (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());}} //顺滑的滑开menu public void smoothOpenMenu() { //将状态设置为STATE_OPENstate = 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();} //顺滑的关闭menu public void smoothCloseMenu() { //将状态设置为STATE_CLOSEstate = STATE_CLOSE;if (mSwipeDirection == SwipeMenuListView.DIRECTION_LEFT) { //设置baseXmBaseX = -mContentView.getLeft();mCloseScroller.startScroll(0, 0, mMenuView.getWidth(), 0, 350);} else {mBaseX = mMenuView.getRight(); //设置baseXmCloseScroller.startScroll(0, 0, mMenuView.getWidth(), 0, 350);}postInvalidate();} //直接关闭menupublic void closeMenu() { //如果Scroller还没有滑完,就阻断滑动动画if (mCloseScroller.computeScrollOffset()) {mCloseScroller.abortAnimation();} //如果状态为STATE_OPENif (state == STATE_OPEN) { //将状态设置为STATE_CLOSEstate = STATE_CLOSE; //调用swipe()-->调用layout()swipe(0);}} //scroll开始滑动的时候就进到了这里 @Overridepublic void computeScroll() {if (state == STATE_OPEN) {if (mOpenScroller.computeScrollOffset()) { //调用swipe()-->调用layout()swipe(mOpenScroller.getCurrX()*mSwipeDirection);postInvalidate();}} else {if (mCloseScroller.computeScrollOffset()) { //通过mBaseX的值来计算滑动swipe((mBaseX - mCloseScroller.getCurrX())*mSwipeDirection);postInvalidate();}}}}
SwipeMenuLayout
通过 Scroller
达到顺滑的打开和关闭,同时 Scroller
每次计算出滑动的值的时候传递给 swipe(int)
方法,该方法通过 View#layout(...)
方法实现位置的变换。
好,那么看完了 SwipeMenuLayout
,回过头来再看看 SwipeMenuListView
:
SwipeMenuListView 0x01
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
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 mTouchState;private int mTouchPosition; private SwipeMenuLayout mTouchView; private float mDownX; private float mDownY; @Override public boolean onTouchEvent(MotionEvent ev) { //处理一些有时候不希望用户有操作,或者关闭menu的时候的手势操作等 if (ev.getAction() != MotionEvent.ACTION_DOWN && mTouchView == null) return super.onTouchEvent(ev); //得到action int action = ev.getAction(); switch (action) { case MotionEvent.ACTION_DOWN: //之前的点击位置 int oldPos = mTouchPosition; mDownX = ev.getX();//记录X mDownY = ev.getY();//记录Y mTouchState = TOUCH_STATE_NONE;//得到新的手指的位置 mTouchPosition = pointToPosition((int) ev.getX(), (int) ev.getY());//如果是同一位置 && mTouchView != null && mTouchView 滑开 if (mTouchPosition == oldPos && mTouchView != null && mTouchView.isOpen()) { //将mTouchState置为TOUCH_STATE_X mTouchState = TOUCH_STATE_X; //剩下的交给SwipeMenuLayout来处理 mTouchView.onSwipe(ev); return true; }//得到当前手指的 item View View view = getChildAt(mTouchPosition - getFirstVisiblePosition());//这里判断是指当前手指对应的view与mTouchView不是同一个,且mTouchView是滑开的状态,那么就去处理mTouchView,将menu关闭,然后结束这次手势操作 if (mTouchView != null && mTouchView.isOpen()) { //关闭menu mTouchView.smoothCloseMenu(); //置为null mTouchView = null; // return super.onTouchEvent(ev); // try to cancel the touch event MotionEvent cancelEvent = MotionEvent.obtain(ev); cancelEvent.setAction(MotionEvent.ACTION_CANCEL); onTouchEvent(cancelEvent); //进行close回调 if (mOnMenuStateChangeListener != null) { mOnMenuStateChangeListener.onMenuClose(oldPos); } return true; } if (view instanceof SwipeMenuLayout) { //将最新的view传递给mTouchView mTouchView = (SwipeMenuLayout) view; //设置方向 mTouchView.setSwipeDirection(mDirection); } if (mTouchView != null) { //剩下的交给SwipeMenuLayout来处理 mTouchView.onSwipe(ev); } break; case MotionEvent.ACTION_MOVE: //计算x和y滑动了多少距离 float dy = Math.abs((ev.getY() - mDownY)); float dx = Math.abs((ev.getX() - mDownX)); //当mTouchState为TOUCH_STATE_X时候 if (mTouchState == TOUCH_STATE_X) { //交给SwipeMenuLayout处理移动的操作 if (mTouchView != null) { mTouchView.onSwipe(ev); } //将item的drawable设置为normal的 getSelector().setState(new int[]{0}); //让ListView对此次操作不做任何处理 ev.setAction(MotionEvent.ACTION_CANCEL); super.onTouchEvent(ev); return true; } else if (mTouchState == TOUCH_STATE_NONE) {//当mTouchState为TOUCH_STATE_NONE时候 if (Math.abs(dy) > MAX_Y) {//计算Y上的距离,Y上是否有动作 mTouchState = TOUCH_STATE_Y; } else if (dx > MAX_X) {//计算X上的距离,X上是否有动作 mTouchState = TOUCH_STATE_X; //回调 if (mOnSwipeListener != null) { mOnSwipeListener.onSwipeStart(mTouchPosition); } } } break; case MotionEvent.ACTION_UP: //X上有动作 if (mTouchState == TOUCH_STATE_X) { if (mTouchView != null) { //是否滑开 boolean isBeforeOpen = mTouchView.isOpen(); //让SwipeMenuLayout处理 mTouchView.onSwipe(ev); //是否滑开,因为手指抬起的时候SwipeMenuLayout回去判断是否满足调教而开启和关闭 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); } //希望ListView不处理接下来的手势操作 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
核心部分就分析完了。
SwipeMenu
12345678910111213141516171819202122232425262728293031323334353637383940
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;}}
之前代码里面也出现过,这里把这部分数据结构拿出来。
SwipeMenuItem
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384
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
来实现的
- SwipeMenuListview源码解析
- [开源学习]SwipeMenuListView源码实现过程解析
- Android源码解析--SwipeMenuListView仿QQ聊天左滑
- SwipeMenuListView的源码下载
- SwipeMenuListView(滑动菜单)-SwipeMenuListView框架完全解析
- SwipeMenuListView
- Swipemenulistview
- swipemenulistview
- Swipemenulistview
- SwipeMenuListView
- 安卓Swipemenulistview侧滑删除 源码分析
- Android SwipeMenuListView
- Android SwipeMenuListView
- 源码解析
- 源码解析
- SwipeMenuListView使用详解
- 学习日记--SwipeMenuListView
- pullToRefresh集成SwipeMenuListview,RecyclerView
- 下拉刷新超简单
- Sublime-Text2使用技巧
- 怎么用Navicat 进行服务器监控
- Android Studio小技巧 cast快捷键
- iOS 开发过程中遇到的Bug和Warning收录(持续更新)
- SwipeMenuListview源码解析
- 多线程编程4 - GCD
- 关于jsp页面的${param.key} 的用法
- Spring的四种依赖方式
- ubuntu用户添加adduser, useradd并给予sudo权限
- web-xml详解
- C#强密匙加密文件.snk
- Mysql中limit的用法详解
- iOS 开发工具