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

SwipeMenuListView(滑动菜单)

A swipe menu for ListView.--一个非常好的滑动菜单开源项目。

Demo

Screenshot

 

一、简介

看了挺长时间的自定义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​: 代码中注释的很清楚

/*** 横向的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));}}//创建imgprivate 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​:

这个类代码有点长,我们分成三部分看,只粘贴核心代码,剩下的看一下应该就懂啦。

public class SwipeMenuLayout extends FrameLayout {private static final int CONTENT_VIEW_ID = 1;private static final int MENU_VIEW_ID = 2;private static final int STATE_CLOSE = 0;private static final int STATE_OPEN = 1;//方向private int mSwipeDirection;private View mContentView;private SwipeMenuView mMenuView;。。。。。public SwipeMenuLayout(View contentView, SwipeMenuView menuView) {this(contentView, menuView, null, null);}public SwipeMenuLayout(View contentView, SwipeMenuView menuView,Interpolator closeInterpolator, Interpolator openInterpolator) {super(contentView.getContext());mCloseInterpolator = closeInterpolator;mOpenInterpolator = openInterpolator;mContentView = contentView;mMenuView = menuView;//将SwipeMenuLayout设置给SwipeMenuView,用于判断是否打开mMenuView.setLayout(this);init();}private void init() {setLayoutParams(new AbsListView.LayoutParams(LayoutParams.MATCH_PARENT,LayoutParams.WRAP_CONTENT));mGestureListener = new SimpleOnGestureListener() {@Overridepublic boolean onDown(MotionEvent e) {isFling = false;return true;}@Override//velocityX这个参数是x轴方向的速率,向左是负的,向右是正的public boolean onFling(MotionEvent e1, MotionEvent e2,float velocityX, float velocityY) {// TODOif (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 ResourceTypemContentView.setId(CONTENT_VIEW_ID);}//noinspection ResourceTypemMenuView.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 来完成的。

 

/*** 滑动事件,用于外边调用的接口* 这是一个对外暴露的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就是0Log.i("tag", "dis = " + dis);//这个值一直是0//DIRECTION_LEFT = 1 || DIRECTION_RIGHT = -1dis += mMenuView.getWidth()*mSwipeDirection;//mSwipeDirection=1Log.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());// opensmoothOpenMenu();} else {// closesmoothCloseMenu();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 negativeif (Math.signum(dis) != mSwipeDirection) {//left=1;right =-1dis = 0;  //不滑动} else if (Math.abs(dis) > mMenuView.getWidth()) {//大于它的宽度,dis就是mMenuView.getWidth()dis = mMenuView.getWidth()*mSwipeDirection;}//重新设置布局,不断左移(或者右移),mContentView.layout(-dis, mContentView.getTop(),mContentView.getWidth() -dis, getMeasuredHeight());if (mSwipeDirection == SwipeMenuListView.DIRECTION_LEFT) {//1//同上重新设置menuview的布局,画图很清晰mMenuView.layout(mContentView.getWidth() - dis, mMenuView.getTop(),mContentView.getWidth() + mMenuView.getWidth() - dis,mMenuView.getBottom());} else {mMenuView.layout(-mMenuView.getWidth() - dis, mMenuView.getTop(),- dis, mMenuView.getBottom());}}/*** 更新状态state = STATE_CLOSE;* 关闭menu*/public void smoothCloseMenu() {state = STATE_CLOSE;if (mSwipeDirection == SwipeMenuListView.DIRECTION_LEFT) {mBaseX = -mContentView.getLeft();//滑动mMenuView.getWidth()的距离,正好隐藏掉mCloseScroller.startScroll(0, 0, mMenuView.getWidth(), 0, 350);} else {mBaseX = mMenuView.getRight();mCloseScroller.startScroll(0, 0, mMenuView.getWidth(), 0, 350);}postInvalidate();}public void smoothOpenMenu() {if(!mSwipEnable){return ;}state = STATE_OPEN;if (mSwipeDirection == SwipeMenuListView.DIRECTION_LEFT) {mOpenScroller.startScroll(-mContentView.getLeft(), 0, mMenuView.getWidth(), 0, 350);Log.i("tag","mContentView.getLeft()="+mContentView.getLeft()+", mMenuView="+mMenuView.getWidth());//-451,就是移动的距离dis,-(downX-moveX)//mContentView.getLeft()=-540, mMenuView=540 ,这俩的绝对值是相等的,完全正确!哈哈·} else {mOpenScroller.startScroll(mContentView.getLeft(), 0, mMenuView.getWidth(), 0, 350);}//在非ui thread中调用这个方法,使视图重绘postInvalidate();}。。。
}

上面主要的方法是onSwipe和swipe这两个方法,主要逻辑是:onSwipe是暴漏给外面调用的API,

在SwipeMenuListView的onTouchEvent事件处理方法中调用了onSwipe;而swipe就是把mContentView和mMenuView都滑动dis距离​。

 

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);//宽度是无限扩展的,高度是指定的mMenuView.measure(MeasureSpec.makeMeasureSpec(0,MeasureSpec.UNSPECIFIED), MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY));}protected void onLayout(boolean changed, int l, int t, int r, int b) {mContentView.layout(0, 0, getMeasuredWidth(),mContentView.getMeasuredHeight());if (mSwipeDirection == SwipeMenuListView.DIRECTION_LEFT) {//左滑//相对于父view,以左边和上边为基准,隐藏在右边mMenuView.layout(getMeasuredWidth(), 0,getMeasuredWidth() + mMenuView.getMeasuredWidth(),mContentView.getMeasuredHeight());} else {   //右滑,隐藏在左边mMenuView.layout(-mMenuView.getMeasuredWidth(), 0,0, mContentView.getMeasuredHeight());}}

 上面的onMeasure、onLayout方法就是自定义View中经常重写的方法,在onMeasure是测量view的大小,这里把宽度类型设置为UNSPECIFIED,可以无限扩展。 onLayout是在view的大小测量之后,把view放到父布局的什么位置,代码里可以看出根据滑动方向吧menuView隐藏在左边(或右边)。

2.3 SwipeMenuAdapter

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的viewSwipeMenu menu = new SwipeMenu(mContext);  //创建SwipeMenumenu.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,

这个代码很长,看的时候需要耐心。

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 defaultprivate 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) {@Overridepublic void createMenu(SwipeMenu menu) {if (mMenuCreator != null) {mMenuCreator.create(menu);}}@Overridepublic void onItemClick(SwipeMenuView view, SwipeMenu menu,int index) {boolean flag = false;if (mOnMenuItemClickListener != null) {flag = mOnMenuItemClickListener.onMenuItemClick(view.getPosition(), menu, index);}//再次点击list中的item关闭menuif (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的positionmTouchPosition = pointToPosition((int) ev.getX(), (int) ev.getY());//得到那个点击的item对应的view,就是SwipeMenuLayoutView view = getChildAt(mTouchPosition - getFirstVisiblePosition());//只在空的时候赋值 以免每次触摸都赋值,会有多个open状态if (view instanceof SwipeMenuLayout) {//如果有打开了 就拦截.mTouchView是SwipeMenuLayout//如果两次是一个mTouchView,更新mTouchView;如果不是一个view,就拦截返回trueif (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);}@Overridepublic 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在MOVELog.i("tag","Listview中的onTouchEvent ACTION_DOWN。是否点击了另一个item");int oldPos = mTouchPosition; //这里设计不合理,onInterceptTouchEvent之后直接调用的这个事件,mTouchPosition是一样的if(mOldTouchPosition == -1){//-1 is the original valuemOldTouchPosition = mTouchPosition;}mDownX = ev.getX();mDownY = ev.getY();mTouchState = TOUCH_STATE_NONE;mTouchPosition = pointToPosition((int) ev.getX(), (int) ev.getY());//list中//这里改了,pldPos没有用,改为mOldTouchPositionif (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()) {//关闭swipeMenumTouchView.smoothCloseMenu();mTouchView = null;// return super.onTouchEvent(ev);// try to cancel the touch eventMotionEvent 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 。 所以要用位置判断是否滑动的是一个viewif (!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事件后的Moveif (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:  //关闭了menuLog.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)

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​永远相等。

//这个方法永远执行不到!作者的愿意是当mTouchPosition != oldPos时CloseMenu,但是按照这个代码这两个值是永远相等的,//因为对应的是一个MotionEvent当然就相等啦if (mTouchView != null && mTouchView.isOpen()) {//关闭swipeMenumTouchView.smoothCloseMenu();//mTouchView = null;// return super.onTouchEvent(ev);// try to cancel the touch eventMotionEvent cancelEvent = MotionEvent.obtain(ev);cancelEvent.setAction(MotionEvent.ACTION_CANCEL);onTouchEvent(cancelEvent); //取消事件,时间结束//进行menu close的回掉if (mOnMenuStateChangeListener != null) {mOnMenuStateChangeListener.onMenuClose(oldPos);}return true;} 

在代码中我已经修改了这个问题。目前已经在github上提交给原作者啦。

转载请注明出处:http://www.cnblogs.com/jycboy/p/SwipeMenuListView.html

 

转载于:https://www.cnblogs.com/jycboy/p/SwipeMenuListView.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/573061.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

JAVA 开发axis2_基于Apache axis2开发Java Web服务

1.安装配置axis2环境下载好后把axis2-1.4.1-war目录下面的axis2.war发布到tomcat的webapps中。发布好&#xff0c;访问&#xff1a;http://localhost:8079/axis2/ 界面如下&#xff1a;2.开发web服务1)创建一个java web project2)编写服务代码1 public classSampleService {23…

Git小技巧 - 指令别名及使用Beyond Compare作为差异比较工具

前言 本文主要写给使用命令行来操作Git的用户&#xff0c;用于提高Git使用的效率。至于使用命令还是GUI&#xff08;Tortoise Git或VS的Git插件&#xff09;就不在此讨论了&#xff0c;大家根据自己的的喜好选择就好。我个人是比较推荐使用命令行的&#xff0c;因为命令行是原生…

java cache-control_详解浏览器Cache-Control缓存策略

原来用的是 Expires 策略,浏览器可以直接从浏览器缓存读取数据&#xff0c;而无需再次请求&#xff0c;它的值对应一个 GMT&#xff0c;来告诉浏览器资源缓存过期时间&#xff0c;如果还没过该时间点则不发请求。例如下面的例子,这是京东的首页里找的一个,时间都干到 2027 年了…

前端必备切图ps篇

前言&#xff1a;当设计师通过photoshop工具设计完设计稿导出psd文件时&#xff0c;我们前端工程师该如何进行切图转换成网页的所需的图片呢&#xff0c;接下来讲讲如何利用photoshop工具快速简洁的切图&#xff0c;因为ps工具用到的行业很多&#xff0c;涉及到的各个行业&…

jenkins配置记录(2)--代码发布流程

在我们的日常运维工作中&#xff0c;使用jenkins来完成业务代码发版上线是至关重要的一环。前面已经提到在jenkins上添加用户权限的操作&#xff0c;今天重点说下如何在jenkins下构建项目工程进行代码发布&#xff1f; 在此简单介绍下jenkins进行代码发布的流程&#xff08;根据…

autoitv3点击windows界面

在自动化测试过程中会遇到如下windows安全认证&#xff0c;需要输入账号和密码&#xff0c;这个认证对话框不属于element元素。无法用selenium操作&#xff0c;需要用autoitv3操作&#xff0c;输入账号密码后&#xff0c;再进行web元素操作。 有2中方式&#xff1a; 第一种&…

java 扩展接口_详解常用的Spring Bean扩展接口

前言Spring是一款非常强大的框架&#xff0c;可以说是几乎所有的企业级Java项目使用了Spring&#xff0c;而Bean又是Spring框架的核心。Spring框架运用了非常多的设计模式&#xff0c;从整体上看&#xff0c;它的设计严格遵循了OCP----开闭原则&#xff0c;即&#xff1a;1、保…

c盘java文件误删_C盘爆满怎么办,教你有选择性删除文件,恢复空间

有很多朋友说电脑c盘有很多垃圾&#xff0c;占着空间&#xff0c;还导致电脑响应变慢。还不知道能不能删除&#xff0c;很困扰。那么c盘文件都是什么文件呢&#xff1f;哪些能删除&#xff0c;做一次全面的清理呢&#xff1f;下面给大家介绍一下program Date&#xff1a;系统文…

java向另一activity输入_Activity经典实例一:两个Activity传递数据和对象

1、概述&#xff1a;Activity类直接或者间接地继承了Context、ContextWrapper、ContextThemeWrapper等基类&#xff0c;因此Activity可以直接调用它们的方法。创建一个Activity需要实现某些方法&#xff0c;常见的是实现onCreate(Bundle status)方法&#xff0c;该方法将会在Ac…

深入理解JSON对象

深入理解JSON对象 前面的话 json(javascript object notation)全称是javascript对象表示法&#xff0c;它是一种数据交换的文本格式&#xff0c;而不是一种编程语言&#xff0c;用于读取结构化数据。2001年由Douglas Crockford提出&#xff0c;目的是取代繁琐笨重的XML格式。本…

java内存分配模型优点_高并发实战(二)-并发基础 缓存 MESI 内存模型

左图为高速缓存 右图为多级缓存数据的读取和存储都经过高速缓存&#xff0c;CPU核心与高速缓存有一条特殊的快速通道。主存与高速缓存都是连接在系统总线上&#xff0c;当然其他组件也是在此基础上进行通信的。在高速缓存出现后不久&#xff0c;系统变得愈加复杂&#xff0c;高…

CentOS 7 安装SVN服务端

CentOS7下安装SVN服务 1. yum命令即可方便的完成安装# sudo yum install subversion 测试安装是否成功&#xff1a;# svnserve --version 更改svn的默认配置(更改默认的指向目录和默认端口)# sudo vim /etc/sysconfig/svnserve编辑示例&#xff1a;OPTIONS"-r /data/svn -…

.NET简谈事务、分布式事务处理

在本人的 “ .NET简谈事务本质论”一文中我们从整体上了解了事务模型&#xff0c;在我们脑子里能有一个全局的事务处理结构&#xff0c;消除对数据库事务的依赖理解&#xff0c;重新认识事务编程模型。 今天这篇文章我们将使用.NET C#来进行事务性编程&#xff0c;从浅显、简单…

css compressor java_利用YUI Compressor压缩JS/CSS之终极秘籍

JS/CSS的压缩已经不是什么新鲜话题了&#xff0c;自打YSlow推出后&#xff0c;这方面的优化话题是铺天盖地啊&#xff1b;不过当时作为后知后觉的我&#xff0c;甚至都没法完整地写出一份漂亮的JS/CSS的代码&#xff0c;何尝会去注意呢&#xff1f;&#xff01;时至今日&#x…

HTML 列表中的dl,dt,dd,ul,li,ol区别及应用

HTML 列表中的dl,dt,dd,ul,li,ol区别及应用 HTML 列表中的dl,dt,dd,ul,li,ol区别及应用 工具/原料 html&#xff0c;dw软件方法/步骤 1无序列表 无序列表是一个项目的列表&#xff0c;此列项目使用粗体圆点&#xff08;典型的小黑圆圈&#xff09;进行标记。 无序列表始于 <…

自然语言10_分类与标注

sklearn实战-乳腺癌细胞数据挖掘(博客主亲自录制视频教程) https://study.163.com/course/introduction.htm?courseId1005269003&utm_campaigncommission&utm_sourcecp-400000000398149&utm_mediumshare http://www.tuicool.com/articles/feAfi2 NLTK读书笔记 — …

git中Please enter a commit message to explain why this merge is necessary.

Please enter a commit message to explain why this merge is necessary. 请输入提交消息来解释为什么这种合并是必要的 git 在pull或者合并分支的时候有时会遇到这个界面。可以不管(直接下面3,4步)&#xff0c;如果要输入解释的话就需要: 1.按键盘字母 i 进入insert模式 2.修…

abp框架mysql连接配置,abp框架连接数据库

ABP框架搭建项目系列教程基础版完结篇经过前面十二篇的基础教程&#xff0c;现在终于该做个总结了。回顾第一篇&#xff0c;我们建议新手朋友们先通过ABP官网的启动模板生成解决方案&#xff0c;因为这样既快速又准确&#xff0c;不会因为项目的搭建&#xff0c;而让新手畏而却…

【分布式】Zookeeper在大型分布式系统中的应用

一、前言 上一篇博文讲解了Zookeeper的典型应用场景&#xff0c;在大数据时代&#xff0c;各种分布式系统层出不穷&#xff0c;其中&#xff0c;有很多系统都直接或间接使用了Zookeeper&#xff0c;用来解决诸如配置管理、分布式通知/协调、集群管理和Master选举等一系列分布式…

Egret资源管理解决方案

关于egret开发H5页游&#xff0c;资源管理和加载的一点看法。 一 多json文件管理 二 资源归类和命名 三 exml文件编写规范 四 资源预加载、分步加载、偷载 五 资源文件group分组 六 ResUtils&#xff0c;多json文件管理类 七 ResUtils&#xff0c;资源组加载管理类 八 开发中遇…