保存到桌面加入收藏设为首页
IOS开发
当前位置:首页 > IOS开发

SwipeMenuListView框架完全解析-安度博客

时间:2019-02-08 15:56:40   作者:   来源:   阅读:127   评论:0
内容摘要:SwipeMenuListView(滑动菜单)A swipe menu for ListView.--一个非常好的滑动菜单开源项目。Demo一、简介看了挺长时间的自定义View和事件分发,想找一个项目练习下。。正好印证自己所学。在github上找到了这个项目:SwipeMenuL......
  • 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 menuViewInterpolator 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_PARENTLayoutParams.WRAP_CONTENT));mGestureListener = new SimpleOnGestureListener() {@Overridepublic boolean onDown(MotionEvent e) {isFling = false;return true;}@Override//velocityX这个参数是x轴方向的速率,向左是负的,向右是正的public boolean onFling(MotionEvent e1 MotionEvent e2float velocityX float velocityY) {// TODOif (Math.abs(e1.getX() - e2.getX()) > MIN_FLING&& velocityX < MAX_VELOCITYX) {isFling = true;}Log.i(&#39;tag&#39;&#39;isFling=&#39;+isFling+&#39; e1.getX()=&#39;+e1.getX()+&#39; e2.getX()=&#39;+e2.getX()+&#39; velocityX=&#39;+velocityX+&#39; MAX_VELOCITYX=&#39;+MAX_VELOCITYX);// Log.i(&#39;byz&#39; MAX_VELOCITYX + &#39; velocityX = &#39; + 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_CONTENTLayoutParams.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(&#39;byz&#39; &#39;downX = &#39; + mDownX + &#39; moveX = &#39; + event.getX());int dis = (int) (mDownX - event.getX());if (state == STATE_OPEN) {//当状态是open时,dis就是0Log.i(&#39;tag&#39; &#39;dis = &#39; + dis);//这个值一直是0//DIRECTION_LEFT = 1 || DIRECTION_RIGHT = -1dis += mMenuView.getWidth()*mSwipeDirection;//mSwipeDirection=1Log.i(&#39;tag&#39; &#39;dis = &#39; + dis + &#39; mSwipeDirection = &#39; + mSwipeDirection);}Log.i(&#39;tag&#39; &#39;ACTION_MOVE downX = &#39; + mDownX + &#39; moveX = &#39; + event.getX()+&#39; dis=&#39;+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(&#39;tag&#39; &#39;ACTION_UP downX = &#39; + mDownX + &#39; moveX = &#39; + 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() - dismMenuView.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(&#39;tag&#39;&#39;mContentView.getLeft()=&#39;+mContentView.getLeft()+&#39; mMenuView=&#39;+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(0MeasureSpec.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() 0getMeasuredWidth() + mMenuView.getMeasuredWidth()mContentView.getMeasuredHeight());} else { //右滑,隐藏在左边mMenuView.layout(-mMenuView.getMeasuredWidth() 00 mContentView.getMeasuredHeight());}}

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

    2.3 SwipeMenuAdapter

    public class SwipeMenuAdapter implements WrapperListAdapterOnSwipeItemClickListener { private ListAdapter mAdapter; private Context mContext; private SwipeMenuListView.OnMenuItemClickListener onMenuItemClickListener; public SwipeMenuAdapter(Context context ListAdapter adapter) { mAdapter = adapter; mContext = context; } 。。。。 /** * 添加滑动时的显示的菜单 * 在这里可以看出每一个Item都是一个SwipeMenuLayout */ public View getView(int position View convertView ViewGroup parent) { SwipeMenuLayout layout = null; if (convertView == null) { View contentView = mAdapter.getView(position convertView parent);//item的view SwipeMenu menu = new SwipeMenu(mContext); //创建SwipeMenu menu.setViewType(getItemViewType(position)); createMenu(menu); //测试的,可以先不管 SwipeMenuView menuView = new SwipeMenuView(menu (SwipeMenuListView) parent); menuView.setOnSwipeItemClickListener(this); SwipeMenuListView listView = (SwipeMenuListView) parent; layout = new SwipeMenuLayout(contentView menuView listView.getCloseInterpolator() listView.getOpenInterpolator()); layout.setPosition(position); } else { layout = (SwipeMenuLayout) convertView; layout.closeMenu(); layout.setPosition(position); View view = mAdapter.getView(position layout.getContentView() parent); } if (mAdapter instanceof baseSwipListAdapter) { boolean swipEnable = (((baseSwipListAdapter) mAdapter).getSwipEnableByPosition(position)); layout.setSwipEnable(swipEnable); } return layout; } //这个方法在创建时,重写啦,在这里是测试的,可以不管。 public void createMenu(SwipeMenu menu) { // Test Code 。。。。。。 } /** * OnSwipeItemClickListener的回掉方法 * 这个方法在该类创建时,重写啦。 */ public void onItemClick(SwipeMenuView view SwipeMenu menu int index) { if (onMenuItemClickListener != null) { onMenuItemClickListener.onMenuItemClick(view.getPosition() menu index); } } 。。。。//省略了不重要的}

      

    2.4 核心类:SwipeMenuListview,

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

    public class SwipeMenuListView extends ListView { private static final int TOUCH_STATE_NONE = 0; private static final int TOUCH_STATE_X = 1; private static final int TOUCH_STATE_Y = 2; public static final int DIRECTION_LEFT = 1; //方向 public static final int DIRECTION_RIGHT = -1; private int mDirection = 1;//swipe from right to left by default private int MAX_Y = 5; private int MAX_X = 3; private float mDownX; private float mDownY; private int mTouchState; private int mTouchPosition; private SwipeMenuLayout mTouchView; private OnSwipeListener mOnSwipeListener; //创建menuItem的 private SwipeMenuCreator mMenuCreator; //menuItem的item点击事件 private OnMenuItemClickListener mOnMenuItemClickListener; private OnMenuStateChangeListener mOnMenuStateChangeListener; private Interpolator mCloseInterpolator; //动画变化率 private Interpolator mOpenInterpolator; //----added in myself--下面这两行是我自己加的, //你如果下下来代码demo运行下你会发现,当一个item已经滑开时,滑动另外的item,此时原来打开的item没有关闭,可以看下QQ的侧滑,它是关闭的,我这里就稍微修改了下。 private int mOldTouchPosition = -1; private boolean shouldCloseMenu; //-------- public SwipeMenuListView(Context context) { super(context); init(); } public SwipeMenuListView(Context context AttributeSet attrs int defStyle) { super(context attrs defStyle); init(); } public SwipeMenuListView(Context context AttributeSet attrs) { super(context attrs); init(); } //初始化变量 private void init() { MAX_X = dp2px(MAX_X); MAX_Y = dp2px(MAX_Y); mTouchState = TOUCH_STATE_NONE; } @Override /** * 对参数adapter进行了一次包装,包装成SwipeMenuAdapter */ public void setAdapter(ListAdapter adapter) { super.setAdapter(new SwipeMenuAdapter(getContext() adapter) { @Override public void createMenu(SwipeMenu menu) { if (mMenuCreator != null) { mMenuCreator.create(menu); } } @Override public void onItemClick(SwipeMenuView view SwipeMenu menu int index) { boolean flag = false; if (mOnMenuItemClickListener != null) { flag = mOnMenuItemClickListener.onMenuItemClick( view.getPosition() menu index); } //再次点击list中的item关闭menu if (mTouchView != null && !flag) { mTouchView.smoothCloseMenu(); } } }); } 。。。。。 @Override //拦截事件,判断事件是点击事件还是滑动事件 public boolean onInterceptTouchEvent(MotionEvent ev) { //在拦截处处理,在滑动设置了点击事件的地方也能swip,点击时又不能影响原来的点击事件 int action = ev.getAction(); switch (action) { case MotionEvent.ACTION_DOWN: mDownX = ev.getX(); mDownY = ev.getY(); boolean handled = super.onInterceptTouchEvent(ev); mTouchState = TOUCH_STATE_NONE; //每次Down都把状态变为无状态 //返回item的position mTouchPosition = pointToPosition((int) ev.getX() (int) ev.getY()); //得到那个点击的item对应的view,就是SwipeMenuLayout View view = getChildAt(mTouchPosition - getFirstVisiblePosition()); //只在空的时候赋值 以免每次触摸都赋值,会有多个open状态 if (view instanceof SwipeMenuLayout) { //如果有打开了 就拦截.mTouchView是SwipeMenuLayout //如果两次是一个mTouchView,更新mTouchView;如果不是一个view,就拦截返回true if (mTouchView != null && mTouchView.isOpen() && !inRangeOfView(mTouchView.getMenuView() ev)) { Log.i(&#39;tag&#39;&#39;Listview中的onInterceptTouchEvent ACTION_DOWN。&#39;); return true; } mTouchView = (SwipeMenuLayout) view; mTouchView.setSwipeDirection(mDirection);//默认是left=1 } //如果摸在另外一个view,拦截此事件 if (mTouchView != null && mTouchView.isOpen() && view != mTouchView) { handled = true; } if (mTouchView != null) { mTouchView.onSwipe(ev); } return handled; case MotionEvent.ACTION_MOVE: //MOVE时拦截事件,在onTouch中进行处理 float dy = Math.abs((ev.getY() - mDownY)); float dx = Math.abs((ev.getX() - mDownX)); if (Math.abs(dy) > MAX_Y || Math.abs(dx) > MAX_X) { //每次拦截的down都把触摸状态设置成了TOUCH_STATE_NONE 只有返回true才会走onTouchEvent 所以写在这里就够了 if (mTouchState == TOUCH_STATE_NONE) { if (Math.abs(dy) > MAX_Y) { mTouchState = TOUCH_STATE_Y; } else if (dx > MAX_X) { mTouchState = TOUCH_STATE_X; if (mOnSwipeListener != null) { mOnSwipeListener.onSwipeStart(mTouchPosition); } } } return true; } } return super.onInterceptTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent ev) { if (ev.getAction() != MotionEvent.ACTION_DOWN && mTouchView == null) return super.onTouchEvent(ev); int action = ev.getAction(); switch (action) { case MotionEvent.ACTION_DOWN: //这个DOWN事件的前提是已经拦截事件啦,所以可能的情况时:1.该menu已经滑出来,再点击左边的item区域 //2.menu已经滑出来,点击了其他的item //3.滑动item时,先DOWN在MOVE Log.i(&#39;tag&#39;&#39;Listview中的onTouchEvent ACTION_DOWN。是否点击了另一个item&#39;); int oldPos = mTouchPosition; //这里设计不合理,onInterceptTouchEvent之后直接调用的这个事件,mTouchPosition是一样的 if(mOldTouchPosition == -1){//-1 is the original value mOldTouchPosition = mTouchPosition; } mDownX = ev.getX(); mDownY = ev.getY(); mTouchState = TOUCH_STATE_NONE; mTouchPosition = pointToPosition((int) ev.getX() (int) ev.getY());//list中 //这里改了,pldPos没有用,改为mOldTouchPosition if (mTouchPosition == mOldTouchPosition && mTouchView != null && mTouchView.isOpen()) { mTouchState = TOUCH_STATE_X; //x方向(横着)滑开 //调用SwipeMenuLayout的onSwipe()事件接口 mTouchView.onSwipe(ev); Log.i(&#39;tag&#39;&#39;Listview中的onTouchEvent ACTION_DOWN。滑动了或点击了另一个item&#39;); return true; } if(mOldTouchPosition != mTouchPosition){ //when the DOWN position is different //shouldCloseMenu = true; mOldTouchPosition = mTouchPosition; } View view = getChildAt(mTouchPosition - getFirstVisiblePosition()); //已经有一个menu滑开了,此时如果点击了另一个item //这个方法永远执行不到! if (mTouchView != null && mTouchView.isOpen()) { //关闭swipeMenu mTouchView.smoothCloseMenu(); mTouchView = null; // return super.onTouchEvent(ev); // try to cancel the touch event MotionEvent cancelEvent = MotionEvent.obtain(ev); cancelEvent.setAction(MotionEvent.ACTION_CANCEL); onTouchEvent(cancelEvent); //取消事件,时间结束 //进行menu close的回掉 if (mOnMenuStateChangeListener != null) { mOnMenuStateChangeListener.onMenuClose(oldPos); } return true; } if (view instanceof SwipeMenuLayout) { mTouchView = (SwipeMenuLayout) view; mTouchView.setSwipeDirection(mDirection); } if (mTouchView != null) { mTouchView.onSwipe(ev); } break; case MotionEvent.ACTION_MOVE: //有些可能有header要减去header再判断 mTouchPosition = pointToPosition((int) ev.getX() (int) ev.getY()) - getHeaderViewsCount(); //如果滑动了一下没完全展现,就收回去,这时候mTouchView已经赋值,再滑动另外一个不可以swip的view //会导致mTouchView swip 。 所以要用位置判断是否滑动的是一个view if (!mTouchView.getSwipEnable() || mTouchPosition != mTouchView.getPosition()) { break; } float dy = Math.abs((ev.getY() - mDownY)); float dx = Math.abs((ev.getX() - mDownX)); if (mTouchState == TOUCH_STATE_X) { //X方向的话 if (mTouchView != null) { mTouchView.onSwipe(ev); //调用滑动事件 } getSelector().setState(new int[]{0}); ev.setAction(MotionEvent.ACTION_CANCEL); super.onTouchEvent(ev);//事件结束 return true; } else if (mTouchState == TOUCH_STATE_NONE) {//DOWN事件后的Move if (Math.abs(dy) > MAX_Y) { mTouchState = TOUCH_STATE_Y; } else if (dx > MAX_X) { mTouchState = TOUCH_STATE_X; if (mOnSwipeListener != null) { mOnSwipeListener.onSwipeStart(mTouchPosition); } } } break; case MotionEvent.ACTION_UP: //关闭了menu Log.i(&#39;tag&#39;&#39;onTouchEvent事件的ACTION_UP&#39;); if (mTouchState == TOUCH_STATE_X) { if (mTouchView != null) { Log.i(&#39;tag&#39;&#39;onTouchEvent事件的ACTION_UP 为什么没有关闭&#39;); 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.SwipeMenuLayoutI/tag: onInterceptTouchEvent ACTION_DOWN handled=falseI/tag: SwipeMenuLayout onTouchEventI/tag: Listview中的onTouchEvent ACTION_DOWN。是否点击了另一个itemI/tag: oldPos=1 mTouchPosition=1I/tag: ACTION_MOVE downX = 987 moveX = 906.69666 dis=80I/tag: ACTION_MOVE downX = 987 moveX = 855.5785 dis=131I/tag: ACTION_MOVE downX = 987 moveX = 797.6258 dis=189I/tag: ACTION_MOVE downX = 987 moveX = 735.9639 dis=251I/tag: ACTION_MOVE downX = 987 moveX = 666.5104 dis=320I/tag: ACTION_MOVE downX = 987 moveX = 589.0626 dis=397I/tag: ACTION_MOVE downX = 987 moveX = 509.15567 dis=477I/tag: ACTION_MOVE downX = 987 moveX = 431.7224 dis=555I/tag: ACTION_MOVE downX = 987 moveX = 361.2613 dis=625I/tag: ACTION_MOVE downX = 987 moveX = 319.70398 dis=667I/tag: onTouchEvent事件的ACTION_UPI/tag: onTouchEvent事件的ACTION_UP 为什么没有关闭I/tag: isFling=true e1.getX()=987.08606 e2.getX()=319.70398 velocityX=-4122.911 MAX_VELOCITYX=-1500I/tag: ACTION_UP downX = 987 moveX = 319.70398I/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()) { //关闭swipeMenu mTouchView.smoothCloseMenu(); //mTouchView = null; // return super.onTouchEvent(ev); // try to cancel the touch event MotionEvent cancelEvent = MotionEvent.obtain(ev); cancelEvent.setAction(MotionEvent.ACTION_CANCEL); onTouchEvent(cancelEvent); //取消事件,时间结束 //进行menu close的回掉 if (mOnMenuStateChangeListener != null) { mOnMenuStateChangeListener.onMenuClose(oldPos); } return true; } 

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

    出处:http://www.cnblogs.com/jycboy/p/SwipeMenuListView.html


本站所有站内信息仅供娱乐参考,不作任何商业用途,不以营利为目的,专注分享快乐,欢迎收藏本站!
所有信息均来自:百度一下 (威尼斯人官网)