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 出一个自己 SwipeMenuLayout 出来,包括用户的 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 来实现的
0 0
原创粉丝点击