概述
这种侧滑菜单大家都不会陌生,自从qq使用了了这种方式之后,网上有很多实现方法。公司有一个业务需要这种模式,本想在github上找个现成的直接使用。于是找到了这个
https://github.com/baoyongzhang/SwipeMenuListView
问题
尽管这个项目实现了大多数功能,但是这个项目存在几个问题
当其放置于scrollView中,上下滑动和左右滑动有时候会出现冲突,导致菜单收不回来,并且时而滑动艰难
与部分下拉刷新组件会产生冲突
每次都只能拉开一个抽屉(业务需要同时打开多个)
解决问题
解决问题1、2
既然是滑动冲突,就按照一般的滑动冲突的解决方法来解决。
为SwipeMenuListView 添加onTouch
监听器。根据滑动水平方向与竖直方向的距离来处理。核心是下面的方法
requestDisallowInterceptTouchEvent
这个方法可以让父控件不去拦截这次滑动事件。我们只需设置计算出手指水平方向的偏移量。设置一个阈值即可。
if (Math.abs(localWigth - sx) > 30) {
scrollView.requestDisallowInterceptTouchEvent(true);
}
else{
scrollView.requestDisallowInterceptTouchEvent(false);
}
对于第二个问题,一般出现于那种原理是嵌套于ScrollView
或者ListView
并在dispatchTouchEvent
进行了流程控制的一些下拉刷新组件。如果你遇到到这种问题用上面的方式是无效的。当然以仿照这种方式在下拉刷新组件的dispatchTouchEvent函数中添加标志位,与scrollView 进行同样的控制
refreshScrollParentViewBase
是下拉刷新组件
@Override
public boolean onTouch(View v, MotionEvent motionEvent) {
if (scrollView == null || refreshScrollParentViewBase == null) {
GetView();
}
switch (motionEvent.getAction()) {
case MotionEvent.ACTION_DOWN:
localWigth = (int) motionEvent.getX();
localHeigth = (int) motionEvent.getY();
break;
case MotionEvent.ACTION_MOVE:
int sx = (int) motionEvent.getX();
if (Math.abs(localWigth - sx) > 30) {
scrollView.requestDisallowInterceptTouchEvent(true);
refreshScrollParentViewBase.setDispath(false);
} else {
scrollView.requestDisallowInterceptTouchEvent(false);
refreshScrollParentViewBase.setDispath(true);
}
break;
case MotionEvent.ACTION_UP:
scrollView.requestDisallowInterceptTouchEvent(false);
refreshScrollParentViewBase.setDispath(true);
localWigth = 0;
localHeigth = 0;
break;
}
return false;
}
别忘了scrollView 与listview 嵌套需要
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, expandSpec);
}
解决问题3
其实解决前两个问题并不需要,对SwipeMenuListView的实现方式进行很深的了解。然而如果想要SwipeMenuListView同时展开多个,便需要大致了解一下SwipeMenuListView的实现原理。
SwipeMenuListView 侧滑本身是由 SwipeMenuLayout 类中 onSwipe()的方式来进行侧滑,onSwipe()里面的实现方式是用onlayout方法进行偏移。但是我在再使用SwipeMenuListView的时候传入的是我们自己的View,它是什么时候讲我们的普通View转变成可以支持侧滑的SwipeMenuLayout的呢?
他提供了SwipeMenuAdapter ,但是我们并不直接使用,那么他的功能是什么呢。让我们看看在适配器中做了什么。
@Override
public View getView(int position, View convertView, ViewGroup parent) {
SwipeMenuLayout layout = null;
if (convertView == null) {
View contentView = mAdapter.getView(position, convertView, parent);
SwipeMenu menu = new SwipeMenu(mContext);
menu.setViewType(mAdapter.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(SwipeMenuListView.is_edit)
{
layout.setSwipeDirection(1);
layout.smoothOpenMenu();
}
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);
}
这是一个很巧妙方式,对我们传入的adapter进行了装饰。在getview 中将我们传入的adapter.getView()
得到的View 拼装出SwipeMenuLayout。并将其返回给listview.
粗略的了解了侧滑菜单的实现方式,那我们来解决我们的问题吧,到底是哪里限制了SwipeMenuListView每次只能弹出一个菜单?
在SwipeMenuListView 中声明了如下变量
private SwipeMenuLayout mTouchView;
观测其中的onTouchEvent方法,可知,这个变量便存储当前正在操作的滑动视图
打开某一个菜单有这样一种方法
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();
}
}
}
由代码可知,每次打开一个菜单,会先调用mTouchView.smoothCloseMenu
这样就导致每次只能打开一个菜单。
如果我们希望能够同时打开所有菜单,可以当前listview当中所有的SwipeMenuLayout的smoothOpenMenu
即可。
添加打开和关闭所有菜单的方法
public void ShowAllMenu() {
is_edit = true;
for (int i = 0; i < getChildCount(); i++) {
View view=getChildAt(i);
if (view instanceof SwipeMenuLayout) {
SwipeMenuLayout menuView = (SwipeMenuLayout) view;
if(!menuView.isOpen())
{
menuView.setSwipeDirection(mDirection);
menuView.smoothOpenMenu();
}
}
}
}
public void HideAllMenu() {
is_edit = false;
for (int i = 0; i < getChildCount(); i++) {
View view=getChildAt(i);
if (view instanceof SwipeMenuLayout) {
SwipeMenuLayout menuView = (SwipeMenuLayout) view;
if(menuView.isOpen())
{
menuView.setSwipeDirection(mDirection);
menuView.smoothCloseMenu();
}
}
}
}
is_edit
变量,我说一下,由于listview中视图的复用机制。当前视图不可见时,会被回收,之后通过adapter.getView方法重新创建。因此我们要根据position去保存每个item的状态。然后在getview之后根据状态进行恢复。
if(SwipeMenuListView.is_edit)
{
layout.setSwipeDirection(1);
layout.smoothOpenMenu();
}
我的业务是要求一起开,一起关,所以一个变量即可记录,想要支持记录每个状态,可以对应存储。
最后来看一眼效果图