转载

SwipeMenuListview源码解析

Github: SwipeMenuListView 分析版本: d1cb862

SwipeMenuListView 是一个像 Android QQ 那样在 ListView 中拉出菜单的开源库。

SwipeMenuListView

SwipeMenuListview源码解析

SwipeMenuListView 是一个很棒的 ListView 控件,但是现在作者已经没有维护了。

使用

添加依赖

dependencies {
compile 'com.baoyz.swipemenulistview:library:1.3.0'
}

Step 1

添加 SwipeMenuListView 到 layout 布局中

<com.baoyz.swipemenulistview.SwipeMenuListView
android:id="@+id/listView"
android:layout_width="match_parent"
android:layout_height="match_parent" />

Step 2

创建 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);

Step 3

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);

创建不同的 Menu

利用 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 0x00

先从 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

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 :

SwipeMenuView

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 定了的。

SwipeMenuLayout

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());
}
}

}

通过 SwipeMenuLayoutonMeasure()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

SwipeMenuListView 0x01

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_MOVEmTouchView.onSwipe(ev) 之后 ev.setAction(MotionEvent.ACTION_CANCEL); super.onTouchEvent(ev); 是因为 SwipeMenuLayout 移动位置的时候,手指可能在 Y 轴上的动作也比较大,此时让 ListView 忽略,直接穿的 action 为 CANCEL ,这样 ListView 就不会因为在 Y 轴上有动作而滑动。

SwipeListView 核心部分就分析完了。

SwipeMenu

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

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;
}

}

之前代码里面也出现过,这里把这部分数据结构拿出来。

原文  http://yydcdut.com/2016/05/22/swipemenulistview-analyse/
正文到此结束
Loading...