Github: SwipeMenuListView 分析版本: d1cb862
SwipeMenuListView 是一个像 Android QQ 那样在 ListView 中拉出菜单的开源库。
SwipeMenuListView 是一个很棒的 ListView 控件,但是现在作者已经没有维护了。
dependencies {
compile 'com.baoyz.swipemenulistview:library:1.3.0'
}
添加 SwipeMenuListView 到 layout 布局中
<com.baoyz.swipemenulistview.SwipeMenuListView
android:id="@+id/listView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
创建 SwipeMenuCreator
并添加 items
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 creator
listView.setMenuCreator(creator);
Menu 的 Click 监听器
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;
}
});
// Right
mListView.setSwipeDirection(SwipeMenuListView.DIRECTION_RIGHT);
// Left
mListView.setSwipeDirection(SwipeMenuListView.DIRECTION_LEFT);
利用 Adapter 中的 ViewType
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
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
listView.setOnSwipeListener(new OnSwipeListener() {
@Override
public void onSwipeStart(int position) {
// swipe start
}
@Override
public void onSwipeEnd(int position) {
// swipe end
}
});
平滑打开 menu
listView.smoothOpenMenu(position);
打开或者关闭 menu 的动画插值器
// Close Interpolator
listView.setCloseInterpolator(new BounceInterpolator());
// Open Interpolator
listView.setOpenInterpolator(...);
先从 SwipeMenuListView
开始看,从构造函数和常用 API 开始:
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
:
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中方法
@Override
public int getCount() {
return mAdapter.getCount();
}
//WrapperListAdapter中方法
@Override
public Object getItem(int position) {
return mAdapter.getItem(position);
}
//WrapperListAdapter中方法
@Override
public long getItemId(int position) {
return mAdapter.getItemId(position);
}
//WrapperListAdapter中方法
@Override
public View getView(int position, View convertView, ViewGroup parent) {
SwipeMenuLayout layout = null;
if (convertView == null) {//刚初始化的时候
//调用adapter的getView得到用户返回的View
View contentView = mAdapter.getView(position, convertView, parent);
//new一个SwipeMenu
SwipeMenu menu = new SwipeMenu(mContext);
//设置当前的ViewType
menu.setViewType(mAdapter.getItemViewType(position));
//向SwipeMenu中添加SwipeMenuItem
createMenu(menu);
//menu的View
SwipeMenuView menuView = new SwipeMenuView(menu,
(SwipeMenuListView) parent);
//设置监听器
menuView.setOnSwipeItemClickListener(this);
//SwipeMenuListView
SwipeMenuListView listView = (SwipeMenuListView) parent;
//整个item,SwipeMenuLayout
layout = new SwipeMenuLayout(contentView, menuView,
listView.getCloseInterpolator(),
listView.getOpenInterpolator());
//设置position位置
layout.setPosition(position);
} else {
layout = (SwipeMenuLayout) convertView;
//关闭menu
layout.closeMenu();
//设置position位置
layout.setPosition(position);
//调用adapter的getView,将用户的View传递出去
View view = mAdapter.getView(position, layout.getContentView(),
parent);
}
return layout;
}
public void createMenu(SwipeMenu menu) {
// Test Code
SwipeMenuItem 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);
}
@Override
public 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中方法
@Override
public void registerDataSetObserver(DataSetObserver observer) {
mAdapter.registerDataSetObserver(observer);
}
//WrapperListAdapter中方法
@Override
public void unregisterDataSetObserver(DataSetObserver observer) {
mAdapter.unregisterDataSetObserver(observer);
}
//WrapperListAdapter中方法
@Override
public boolean areAllItemsEnabled() {
return mAdapter.areAllItemsEnabled();
}
//WrapperListAdapter中方法
@Override
public boolean isEnabled(int position) {
return mAdapter.isEnabled(position);
}
//WrapperListAdapter中方法
@Override
public boolean hasStableIds() {
return mAdapter.hasStableIds();
}
//WrapperListAdapter中方法
@Override
public int getItemViewType(int position) {
return mAdapter.getItemViewType(position);
}
//WrapperListAdapter中方法
@Override
public int getViewTypeCount() {
return mAdapter.getViewTypeCount();
}
//WrapperListAdapter中方法
@Override
public boolean isEmpty() {
return mAdapter.isEmpty();
}
//WrapperListAdapter中方法
@Override
public ListAdapter getWrappedAdapter() {
return mAdapter;
}
}
SwipeMenuAdapter
实现了 WrapperListAdapter
,而 WrapperListAdapter
的父类是 ListAdapter
,也就是说 WrapperListAdapter
是一个 ListAdapter
包装类。那么我们这里可以将这个类看简单点,可以通过看一个 BaseAdapter
来看这个类,也就是我们需要关心的是 getView()
, getItemId()
, getItem()
, getCounts()
,而在 SwipeMenuAdapter
中可以看出在 getView()
中的操作比较多。
在 getView()
中首先判断参数 convertView
是不是为 null ,如果是,那么 new 出一个自己 SwipeMenuLayou
t 出来,包括用户的 item view 和 menu view,然后返回;如果不为 null ,那么参数 convertView
应该是 SwipeMenuLayout
,通过调用 adapter.getView()
将 用户的 item view 传递给用户。
那么我们先来看看 Menu 的 View :
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中的MenuItem
List<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;
}
//点击事件
@Override
public 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 定了的。
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;//滑开的scroller
private 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设置给SwipeMenuView
mMenuView.setLayout(this);
init();
}
private SwipeMenuLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
private SwipeMenuLayout(Context context) {
super(context);
}
private void init() {
//设置改控件的宽度match_parent,高度wrap_content
setLayoutParams(new AbsListView.LayoutParams(LayoutParams.MATCH_PARENT,
LayoutParams.WRAP_CONTENT));
//Simple Gesture
mGestureListener = new SimpleOnGestureListener() {
@Override
public boolean onDown(MotionEvent e) {
isFling = false;
return true;
}
//滑动
@Override
public 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 Scroller
if (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());
}
//设置layoutParams
LayoutParams contentParams = new LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
mContentView.setLayoutParams(contentParams);
//设置ID
if (mContentView.getId() < 1) {
mContentView.setId(CONTENT_VIEW_ID);
}
//设置layoutParams和id
mMenuView.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
:
public class SwipeMenuLayout extends FrameLayout {
private int mSwipeDirection;
public void setSwipeDirection(int swipeDirection) {
mSwipeDirection = swipeDirection;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//measure SwipeMenuView 高传的是getMeasuredHeight()且MeasureSpec.EXACTLY
mMenuView.measure(MeasureSpec.makeMeasureSpec(0,
MeasureSpec.UNSPECIFIED), MeasureSpec.makeMeasureSpec(
getMeasuredHeight(), MeasureSpec.EXACTLY));
}
@Override
protected 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
是怎么滑动的:
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的MotionEvent
public boolean onSwipe(MotionEvent event) {
//Gesture的判断
mGestureDetector.onTouchEvent(event);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
//记录down的X坐标
mDownX = (int) event.getX();
//滑动标志位置为false
isFling = false;
break;
case MotionEvent.ACTION_MOVE:
//计算手指滑动距离
int dis = (int) (mDownX - event.getX());
if (state == STATE_OPEN) {
//DIRECTION_LEFT = 1 || DIRECTION_RIGHT = -1
dis += mMenuView.getWidth()*mSwipeDirection;;
}
swipe(dis);
break;
case MotionEvent.ACTION_UP:
//滑动状态 && 滑开距离 > SwipeMenuView / 2 && 滑动方向 == mSwipeDirection
if ((isFling || Math.abs(mDownX - event.getX()) > (mMenuView.getWidth() / 2)) && Math.signum(mDownX - event.getX()) == mSwipeDirection) {
//滑开这个menu
smoothOpenMenu();
} else {
//关闭这个menu
smoothCloseMenu();
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());
//通过方向移动SwipeMenuView
if (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_OPEN
state = 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_CLOSE
state = STATE_CLOSE;
if (mSwipeDirection == SwipeMenuListView.DIRECTION_LEFT) {
//设置baseX
mBaseX = -mContentView.getLeft();
mCloseScroller.startScroll(0, 0, mMenuView.getWidth(), 0, 350);
} else {
mBaseX = mMenuView.getRight();
//设置baseX
mCloseScroller.startScroll(0, 0, mMenuView.getWidth(), 0, 350);
}
postInvalidate();
}
//直接关闭menu
public void closeMenu() {
//如果Scroller还没有滑完,就阻断滑动动画
if (mCloseScroller.computeScrollOffset()) {
mCloseScroller.abortAnimation();
}
//如果状态为STATE_OPEN
if (state == STATE_OPEN) {
//将状态设置为STATE_CLOSE
state = STATE_CLOSE;
//调用swipe()-->调用layout()
swipe(0);
}
}
//scroll开始滑动的时候就进到了这里
@Override
public 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
:
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
核心部分就分析完了。
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;
}
}
之前代码里面也出现过,这里把这部分数据结构拿出来。
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;
}
}
之前代码里面也出现过,这里把这部分数据结构拿出来。