• 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));
    		}
    	}
        //创建img
    	private 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() {
    			@Override
    			public boolean onDown(MotionEvent e) {
    				isFling = false;
    				return true;
    			}
    
    			@Override
    			//velocityX这个参数是x轴方向的速率,向左是负的,向右是正的
    			public boolean onFling(MotionEvent e1, MotionEvent e2,
    					float velocityX, float velocityY) {
    				// TODO
    				if (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 ResourceType
    			mContentView.setId(CONTENT_VIEW_ID);
    		}
            //noinspection ResourceType
    		mMenuView.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就是0
    				Log.i("tag", "dis = " + dis);//这个值一直是0
    				//DIRECTION_LEFT = 1 || DIRECTION_RIGHT = -1
    				dis += mMenuView.getWidth()*mSwipeDirection;//mSwipeDirection=1
    				Log.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());
    				// open
    				smoothOpenMenu();
    			} else {
    				// close
    				smoothCloseMenu();
    				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 negative
    		if (Math.signum(dis) != mSwipeDirection) {//left=1;right =-1
    			dis = 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的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("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);
        }
    
        @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("tag","Listview中的onTouchEvent ACTION_DOWN。是否点击了另一个item");
                    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("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()) {
                        //关闭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("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()) {
                        //关闭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

  • 相关阅读:
    夏天里的敏捷:10月底总结会议笔记【问题笔记】
    如何给项目里面每个功能点设权限?
    操作svn汉化
    夏天里的敏捷[1]:搬家记
    如何去读一个有50万行代码的项目?
    如何去定位你在调用哪个function()?
    小鸡和代码代码重构[2]:MOVE METHOD
    SVN版本管理随笔
    .NET服务器端控件绑定数据源的问题
    看视频笔记1【wpf】
  • 原文地址:https://www.cnblogs.com/jycboy/p/SwipeMenuListView.html
Copyright © 2020-2023  润新知