@(安卓开发)[android开发|自定义view]
SwipeMenuListView是利用Scroller实现的横滑item出现菜单项的listview, 源码已分享在Github,注释清晰,欢迎查看指正。
一、Demo展示
先上效果图
1、为SwipeMenuListView 新建Adapter
为ListView 新建Adapter的方式和普通的listview没有太大的不同,只是在Adapter的getView里要用item的主体View和滑动出来的菜单View来构造SwipeMenuItemView,然后将构造的SwipeMenuItemView返回给adapter。
//传入上下文context、ListView的宽度、主体内容View 和 菜单View
convertView = new SwipeMenuItemView(context, mListView.getWidth(), contentView, menuView);
adapter getView() 部分的代码如下:
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
Holder holder;
if (null == convertView) {
//获取主体内容View 和 菜单View
View contentView = LayoutInflater.from(activity).inflate(R.layout.item_content, null);
View menuView = LayoutInflater.from(activity).inflate(R.layout.item_menu, null);
//构造可以横滑显示菜单项的SwipeMenuItemView
convertView = new SwipeMenuItemView(context, mListView.getWidth(), contentView, menuView);
holder.contentView = content;
holder.menuView = menuView;
convertView.setTag(holder);
} else {
holder = (Holder) convertView.getTag();
}
//横滑出的菜单项设置点击事件
holder.menuView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//item_menu is clicked
}
});
return convertView;
}
2、为SwipeMenuListView 设置adapter
private void initData() {
mAdapter = new CommentListAdapter(this, mListView, commentList);
mSwipeMenuListView.setAdapter(mAdapter);
mAdapter.notifyDataSetChanged();
}
三、具体实现
从实用方式中相信大家也可以看出来,这套横滑显示菜单的控件核心就在
SwipeMenuListView
和SwipeMenuItemView
,下面就详细说一下这两个部分的具体实现,其中主要涉及到事件的分发
和Scroller
相关的知识。1、SwipeMenuListView
SwipeMenuListView继承ListView,主要负责
事件的分发
,将横滑事件分发到对应的SwipeMenuItemView中,具体实现代码如下:
@Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
//通过点击时的坐标 获得对应点击Item的position
int position = pointToPosition((int) ev.getX(), (int) ev.getY());
//通过getFirstVisiblePosition()获取到屏幕显示的第一个item的position
// 通过 getChildAt( position - getFirstVisiblePosition() )可以得到点击的item的view实例
if (position != INVALID_POSITION) {
mSwipeMenuItemView = (SwipeMenuItemView) getChildAt(position - getFirstVisiblePosition());
}
break;
}
if (mSwipeMenuItemView != null) {
//将当前的touchEvent传递给itemView·
mSwipeMenuItemView.onTouchEvent(ev);
//将其它 item 的 menuView 置为隐藏
mSwipeMenuItemView.setSwipeMenuItemChangeListener(new SwipeMenuItemChangeListener() {
@Override
public void onSwipeMenuItemChange(boolean isMenuShow) {
if (isMenuShow) {
for (int i = getFirstVisiblePosition(); i <= getLastVisiblePosition(); i++) {
if (getChildAt(i)!=mSwipeMenuItemView)
((SwipeMenuItemView) getChildAt(i)).setIsMenuShow(false);
}
}
}
});
}
return super.onTouchEvent(ev);
}
2、SwipeMenuItemView
SwipeMenuItemView本质上就是一个包含了两个子View的横向的 Linearlayout
,如下图 View1的宽度和SwipeMenuListView的宽度一样View2 可以自定义一个或者多个Button。
在SwipeMenuItemView里,主要在onTouchEvent里根据用户的横向滑动操作实时滚动itemView,并利用scroller给itemView增加自动滑动的效果。核心代码如下:
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
//记录手指按下时的坐标
mDownPosX = (int) event.getRawX();
mDownPosY = (int) event.getRawY();
mOldPosX = mDownPosX;
mOldPosY = mDownPosY;
break;
case MotionEvent.ACTION_MOVE:
//计算当前滑动距离
int dX = (int) event.getRawX() - mOldPosX;
int dY = (int) event.getRawY() - mOldPosY;
//计算自按下时的总滑动距离
moveDistance = (int) (event.getRawX() - mDownPosX);
//如果右侧菜单栏隐藏就不往左滑,如果右侧菜单栏显示就不往右再滑了
if (menuStatu == MENU_HIDE && dX > 0 || menuStatu == MENU_SHOW && dX < 0 || moveDistance == 0)
break;
if (moveDistance >= mWidth) {
//总移动距离为正(向右) 且大于等于菜单的宽度就表示菜单栏已被隐藏
menuStatu = MENU_HIDE;
} else if (moveDistance < -mMenuWidth) {
//总移动距离为负(向左) 且大于等于菜单的宽度就表示菜单栏已被显示
menuStatu = MENU_SHOW;
}else
menuStatu = MENU_CHANGING;
//如果当次滑动横向距离大于纵向距离 就将view整体横向移动对应距离(为什么scrollBy(-dx)请查看scrollBy文档)
if (Math.abs(dX) > Math.abs(dY))
scrollBy(-dX, 0);
//记录当前位置,用于下一次滑动事件计算滑动距离
mOldPosX = (int) event.getRawX();
mOldPosY = (int) event.getRawY();
break;
case MotionEvent.ACTION_UP:
//计算总的滑动距离
moveDistance = (int) (event.getRawX() - mDownPosX);
//如果右侧菜单栏隐藏就不往左滑,如果右侧菜单栏显示就不往右再滑了
if (menuStatu == MENU_HIDE && moveDistance > 0 || menuStatu == MENU_SHOW && moveDistance < 0)
break;
//如果滑动距离不足菜单宽度的0.3倍,此次滑动视为无效,itemView回到原位
if (Math.abs(moveDistance) < 0.3 * mMenuWidth) {
setIsMenuShow(false);
} else {
//如果滑动方向向左(距离为负)则显示右侧菜单栏,否则隐藏菜单栏
setIsMenuShow(moveDistance < 0);
}
break;
}
return super.onTouchEvent(event);
}
在用户滑动item未完全显示或隐藏菜单栏时,需要我们利用
Scroller
来给item一个滑动效果,具体实现如下:
public SwipeMenuItemView setIsMenuShow(boolean isMenuShow) {
int[] position = new int[2];
contentView.getLocationOnScreen(position);
if (isMenuShow) {
if (menuStatu == MENU_SHOW)
return this;
lis.onSwipeMenuItemChange(true);
menuStatu = MENU_SHOW;
smoothScrollTo(-position[0], mScroller.getFinalY(), mMenuWidth, mScroller.getFinalY(), AUTO_SCROLL_TIEM);
} else {
if (menuStatu == MENU_HIDE)
return this;
lis.onSwipeMenuItemChange(false);
menuStatu = MENU_HIDE;
smoothScrollTo(-position[0], mScroller.getFinalY(), 0, mScroller.getFinalY(), AUTO_SCROLL_TIEM);
}
invalidate();
return this;
}
private void smoothScrollTo(int startX, int startY, int endX, int endY, int duration) {
mScroller.startScroll(startX, startY, 0, 0, duration);
mScroller.setFinalX(endX);
mScroller.setFinalY(endY);
invalidate();
}
@Override
public void computeScroll() {
//先判断mScroller滚动是否完成
if (mScroller.computeScrollOffset()) {
//这里调用View的scrollTo()完成实际的滚动
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
//必须调用该方法,否则不一定能看到滚动效果
postInvalidate();
}
super.computeScroll();
}
3、SwipeMenuItemChangeListener
上述实现中,用于监听item滑动事件然后将其他item的menu隐藏的SwipeMenuItemChangeListener如下:
interface SwipeMenuItemChangeListener {
void onSwipeMenuItemChange(boolean isMenuShow);
}
四、总结
整个自定义View主要涉及到了View和ViewGroup的事件分发机制、Scroller相关知识,这两块我没有仔细总结,后期补上。