在 android 中 ListView 可以说是使用最多的控件之一,ListView 有很多的用法和处理事件,比如 item 的点击和长按事件,在比较多的应用中,点击就是跳转,长按会弹出一些选择菜单等。 这里我要介绍的是一个 ListView 侧滑出菜单的自定义控件
效果图如下:
正常状态 侧滑出菜单状态主要用到了 Scroller 这个滑动类,刚开始拦截触摸事件在 action ==MotionEvent.ACTION_DOWN的时候,根据出点获取我们点击的itemView 然后根据滑动模式(左滑动 or 右滑动)来自动获取左侧或者右侧的宽度;
在 action == MotionEvent.ACTION_MOVE 中根据移动判断是否可以侧滑,以及侧滑的方向,并使用 itemView.scrollTo(deltaX, 0); 来移动itemView ;
最后在 ction == MotionEvent.ACTION_UP 中判断模式和移动的距离完成侧滑或者还原到初始状态。
scroller = new Scroller(context); mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
case MotionEvent.ACTION_DOWN: if (this.mode == MOD_FORBID) { return super.onTouchEvent(ev); } // 如果处于侧滑完成状态,侧滑回去,并直接返回 if (isSlided) { scrollBack(); return false; } // 假如scroller滚动还没有结束,我们直接返回 if (!scroller.isFinished()) { return false; } downX = (int) ev.getX(); downY = (int) ev.getY(); slidePosition = pointToPosition(downX, downY); // 无效的position, 不做任何处理 if (slidePosition == AdapterView.INVALID_POSITION) { return super.onTouchEvent(ev); } // 获取我们点击的item view itemView = getChildAt(slidePosition - getFirstVisiblePosition()); /*此处根据设置的滑动模式,自动获取左侧或右侧菜单的长度*/ if (this.mode == MOD_BOTH) { this.leftLength = -itemView.getPaddingLeft(); this.rightLength = -itemView.getPaddingRight(); } else if (this.mode == MOD_LEFT) { this.leftLength = -itemView.getPaddingLeft(); } else if (this.mode == MOD_RIGHT) { this.rightLength = -itemView.getPaddingRight(); } break;
case MotionEvent.ACTION_MOVE: if (!canMove && slidePosition != AdapterView.INVALID_POSITION && (Math.abs(ev.getX() - downX) > mTouchSlop && Math.abs(ev .getY() - downY) < mTouchSlop)) { if (mSwipeLayout != null) mSwipeLayout.setEnabled(false); int offsetX = downX - lastX; if (offsetX > 0 && (this.mode == MOD_BOTH || this.mode == MOD_RIGHT)) { /*从右向左滑*/ canMove = true; } else if (offsetX < 0 && (this.mode == MOD_BOTH || this.mode == MOD_LEFT)) { /*从左向右滑*/ canMove = true; } else { canMove = false; } /*此段代码是为了避免我们在侧向滑动时同时触发ListView的OnItemClickListener时间*/ MotionEventcancelEvent = MotionEvent.obtain(ev); cancelEvent .setAction(MotionEvent.ACTION_CANCEL | (ev.getActionIndex() << MotionEvent.ACTION_POINTER_INDEX_SHIFT)); onTouchEvent(cancelEvent); } if (canMove) { /*设置此属性,可以在侧向滑动时,保持ListView不会上下滚动*/ requestDisallowInterceptTouchEvent(true); // 手指拖动itemView滚动, deltaX大于0向左滚动,小于0向右滚 int deltaX = downX - lastX; if (deltaX < 0 && (this.mode == MOD_BOTH || this.mode == MOD_LEFT)) { /*向左滑*/ itemView.scrollTo(deltaX, 0); } else if (deltaX > 0 && (this.mode == MOD_BOTH || this.mode == MOD_RIGHT)) { /*向右滑*/ itemView.scrollTo(deltaX, 0); } else { itemView.scrollTo(0, 0); } return true; } break;
case MotionEvent.ACTION_UP: if (mSwipeLayout != null) mSwipeLayout.setEnabled(true); //requestDisallowInterceptTouchEvent(false); if (canMove){ canMove = false; scrollByDistanceX(); } break;
一下是完整代码
package com.jwenfeng.fastdev.view; importandroid.content.Context; importandroid.support.v4.widget.SwipeRefreshLayout; importandroid.util.AttributeSet; importandroid.view.MotionEvent; importandroid.view.View; importandroid.view.ViewConfiguration; importandroid.widget.AdapterView; importandroid.widget.ListView; importandroid.widget.Scroller; /** * 当前类注释: ListView 侧滑出菜单 * 项目名:fastdev * 包名:com.jwenfeng.fastdev.view * 作者:jinwenfeng on 16/4/11 10:55 * 邮箱:823546371@qq.com * QQ: 823546371 * 公司:南京穆尊信息科技有限公司 * © 2016 jinwenfeng * ©版权所有,未经允许不得传播 */ public class SlideListView extends ListView { /**下拉刷新view*/ private SwipeRefreshLayoutmSwipeLayout; /** * 禁止侧滑模式 */ public static int MOD_FORBID = 0; /** * 从左向右滑出菜单模式 */ public static int MOD_LEFT = 1; /** * 从右向左滑出菜单模式 */ public static int MOD_RIGHT = 2; /** * 左右均可以滑出菜单模式 */ public static int MOD_BOTH = 3; /** * 当前的模式 */ private int mode = MOD_FORBID; /** * 左侧菜单的长度 */ private int leftLength = 0; /** * 右侧菜单的长度 */ private int rightLength = 0; /** * 当前滑动的ListView position */ private int slidePosition; /** * 手指按下X的坐标 */ private int downY; /** * 手指按下Y的坐标 */ private int downX; /** * ListView的item */ private ViewitemView; /** * 滑动类 */ private Scrollerscroller; /** * 认为是用户滑动的最小距离 */ private int mTouchSlop; /** * 判断是否可以侧向滑动 */ private boolean canMove = false; /** * 标示是否完成侧滑 */ private boolean isSlided = false; public SlideListView(Contextcontext) { this(context, null); } public SlideListView(Contextcontext, AttributeSetattrs) { this(context, attrs,0); } public SlideListView(Contextcontext, AttributeSetattrs, int defStyleAttr) { super(context, attrs, defStyleAttr); scroller = new Scroller(context); mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); } /** * 初始化菜单的滑出模式 * * @param mode */ public void initSlideMode(int mode) { this.mode = mode; } /** * 处理我们拖动ListView item的逻辑 */ @Override public boolean onTouchEvent(MotionEventev) { final int action = ev.getAction(); int lastX = (int) ev.getX(); switch (action) { case MotionEvent.ACTION_DOWN: if (this.mode == MOD_FORBID) { return super.onTouchEvent(ev); } // 如果处于侧滑完成状态,侧滑回去,并直接返回 if (isSlided) { scrollBack(); return false; } // 假如scroller滚动还没有结束,我们直接返回 if (!scroller.isFinished()) { return false; } downX = (int) ev.getX(); downY = (int) ev.getY(); slidePosition = pointToPosition(downX, downY); // 无效的position, 不做任何处理 if (slidePosition == AdapterView.INVALID_POSITION) { return super.onTouchEvent(ev); } // 获取我们点击的item view itemView = getChildAt(slidePosition - getFirstVisiblePosition()); /*此处根据设置的滑动模式,自动获取左侧或右侧菜单的长度*/ if (this.mode == MOD_BOTH) { this.leftLength = -itemView.getPaddingLeft(); this.rightLength = -itemView.getPaddingRight(); } else if (this.mode == MOD_LEFT) { this.leftLength = -itemView.getPaddingLeft(); } else if (this.mode == MOD_RIGHT) { this.rightLength = -itemView.getPaddingRight(); } break; case MotionEvent.ACTION_MOVE: if (!canMove && slidePosition != AdapterView.INVALID_POSITION && (Math.abs(ev.getX() - downX) > mTouchSlop && Math.abs(ev .getY() - downY) < mTouchSlop)) { if (mSwipeLayout != null) mSwipeLayout.setEnabled(false); int offsetX = downX - lastX; if (offsetX > 0 && (this.mode == MOD_BOTH || this.mode == MOD_RIGHT)) { /*从右向左滑*/ canMove = true; } else if (offsetX < 0 && (this.mode == MOD_BOTH || this.mode == MOD_LEFT)) { /*从左向右滑*/ canMove = true; } else { canMove = false; } /*此段代码是为了避免我们在侧向滑动时同时触发ListView的OnItemClickListener时间*/ MotionEventcancelEvent = MotionEvent.obtain(ev); cancelEvent .setAction(MotionEvent.ACTION_CANCEL | (ev.getActionIndex() << MotionEvent.ACTION_POINTER_INDEX_SHIFT)); onTouchEvent(cancelEvent); } if (canMove) { /*设置此属性,可以在侧向滑动时,保持ListView不会上下滚动*/ requestDisallowInterceptTouchEvent(true); // 手指拖动itemView滚动, deltaX大于0向左滚动,小于0向右滚 int deltaX = downX - lastX; if (deltaX < 0 && (this.mode == MOD_BOTH || this.mode == MOD_LEFT)) { /*向左滑*/ itemView.scrollTo(deltaX, 0); } else if (deltaX > 0 && (this.mode == MOD_BOTH || this.mode == MOD_RIGHT)) { /*向右滑*/ itemView.scrollTo(deltaX, 0); } else { itemView.scrollTo(0, 0); } return true; } break; case MotionEvent.ACTION_UP: if (mSwipeLayout != null) mSwipeLayout.setEnabled(true); //requestDisallowInterceptTouchEvent(false); if (canMove){ canMove = false; scrollByDistanceX(); } break; } return super.onTouchEvent(ev); } private void scrollByDistanceX() { if(this.mode == MOD_FORBID){ return; } if(itemView.getScrollX() > 0 && (this.mode == MOD_BOTH || this.mode == MOD_RIGHT)){ /*从右向左滑*/ if (itemView.getScrollX() >= rightLength / 2) { scrollLeft(); } else { // 滚回到原始位置 scrollBack(); } }else if(itemView.getScrollX() < 0 && (this.mode == MOD_BOTH || this.mode == MOD_LEFT)){ /*从左向右滑*/ if (itemView.getScrollX() <= -leftLength / 2) { scrollRight(); } else { // 滚回到原始位置 scrollBack(); } }else{ // 滚回到原始位置 scrollBack(); } } /** * 往右滑动,getScrollX()返回的是左边缘的距离,就是以View左边缘为原点到开始滑动的距离,所以向右边滑动为负值 */ private void scrollRight() { isSlided = true; final int delta = (leftLength + itemView.getScrollX()); // 调用startScroll方法来设置一些滚动的参数,我们在computeScroll()方法中调用scrollTo来滚动item scroller.startScroll(itemView.getScrollX(), 0, -delta, 0, Math.abs(delta)); postInvalidate(); // 刷新itemView } /** * 向左滑动,根据上面我们知道向左滑动为正值 */ private void scrollLeft() { isSlided = true; final int delta = (rightLength - itemView.getScrollX()); // 调用startScroll方法来设置一些滚动的参数,我们在computeScroll()方法中调用scrollTo来滚动item scroller.startScroll(itemView.getScrollX(), 0, delta, 0, Math.abs(delta)); postInvalidate(); // 刷新itemView } private void scrollBack() { isSlided = false; scroller.startScroll(itemView.getScrollX(), 0, -itemView.getScrollX(), 0, Math.abs(itemView.getScrollX())); postInvalidate(); // 刷新itemView } @Override public void computeScroll() { // 调用startScroll的时候scroller.computeScrollOffset()返回true, if (scroller.computeScrollOffset()) { // 让ListView item根据当前的滚动偏移量进行滚动 itemView.scrollTo(scroller.getCurrX(), scroller.getCurrY()); postInvalidate(); } } /** * 提供给外部调用,用以将侧滑出来的滑回去 */ public void slideBack() { this.scrollBack(); } public void setSwipeLayout(SwipeRefreshLayoutmSwipeLayout) { this.mSwipeLayout = mSwipeLayout; } }