ListView滑动删除手势:
- item 从右向左滑动
- item 内部点击事件
事件分发过程中的主要方法:
boolean dispatchTouchEvent(MotionEvent ev)
:当事件能够传递到View时,那么该View的dispatchTouchEvent方法就会被调用,返回结果表示是否消耗当前事件boolean onInterceptTouchEvent(MotionEvent ev)
:在dispatchTouchEvent方法内调用,判断是否拦截某个事件,如果拦截,事件将不会再向下一层传递boolean onTouchEvent(MotionEvent ev)
:当onInterceptTouchEvent拦截事件后调用该方法,用来进行对应事件的处理上述三个方法的关系下面的伪代码解释的比较清楚:
public boolean dispatchTouchEvent(MotionEvent ev){
boolean consume = false;
if(onInterceptTouchEvent(ev){
consume = onTouchEvent(ev);
}else{
consume = child.dispathcTouchEvent(ev);
}
return consume;
}
分析滑动删除功能
事件的传递过程主要看是否消费事件。根据滑动删除功能从右向左的滑动的触摸事件分析一下事件分发:
- 用户点击屏幕准备滑动item按钮时会产生一个ACTION_DOWN事件
- ACTION_DOWN事件传递到ListView时,ListView不消费、不拦截,记录初始位置,继续向下传递到自定义滑动item
- item 接收到ACTION_DOWN事件时,记住初始位置,用来后续测量滑动速度与距离
- 用户滑动时,传递一系列的ACTION_MOVE事件
- ACTION_MOVE事件传递到ListView时,ListView判断滑动方向,看是否向下s传递滑动,如果滑动方向不是从右向左,则不向下继续传递,如果是,则继续向下传递
- item接收到ACTION_MOVE事件时,判断滑动速度,滑动View
上述就是滑动事件的大概流程,下面分析一下具体的代码:
自定义 ListView
public class SlideListView extends ListView {
private SlideItemLayout mCurrentItemView;
/**
* 记录之前滑动的位置
*/
private int prePosition = -1;
public SlideListView(Context context) {
super(context);
}
public SlideListView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public SlideListView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
/**
* 测量,主要处理 ListView 在 ScrollView 等试图中的高度问题
*
* @param widthMeasureSpec
* @param heightMeasureSpec
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2,
MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, expandSpec);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
//当之前有item滑动过,再次滑动另一item时重置前一个item
int position = pointToPosition((int) ev.getX(), (int) ev.getY());
if (position >= 0 && position != prePosition) {
View currentItemView = getChildAt(position - getFirstVisiblePosition());
if (mCurrentItemView != null) {
mCurrentItemView.reset();
}
mCurrentItemView = (SlideItemLayout) currentItemView;
prePosition = position;
}
break;
case MotionEvent.ACTION_MOVE:
//当设置状态为不能启用时不再向下传递滑动事件
if (!isEnabled()) {
return true;
}
break;
}
return super.onInterceptTouchEvent(ev);
}
}
自定义item
public class SlideItemLayout extends ViewGroup {
private float MAX_SLIDE_SPEED = 1000f; //最大滑动速度
private float deleteWidth;
public SlideItemLayout(Context context) {
super(context);
}
public SlideItemLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public SlideItemLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
// 计算自定义的ViewGroup中所有子控件的大小
measureChildren(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(measureSize(widthMode, widthSize), measureSize(heightMode, heightSize));
}
/**
* 测量高度与宽度
*
* @param mode
* @return
*/
private int measureSize(int mode, int s) {
int size = 0;
switch (mode) {
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
size = s;
break;
}
return size;
}
@Override
protected void onLayout(boolean change, int l, int t, int r, int b) {
int right = 0;
int size = getChildCount();
//获取删除按钮的宽度
deleteWidth = getChildAt(size - 1).getMeasuredWidth();
for (int i = 0; i < size; i++) {
View view = getChildAt(i);
int childHeight = view.getMeasuredHeight();
int chileWidth = view.getMeasuredWidth();
view.layout(right, 0, right + chileWidth, childHeight);
right += chileWidth;
}
}
private float startX = 0;
private float endX = 0;
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
startX = ev.getX();
break;
case MotionEvent.ACTION_MOVE:
if (slideSpeed(ev) >= MAX_SLIDE_SPEED) {
return true;
}
break;
}
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_MOVE:
endX = event.getX();
if (endX - startX < 0 && startX - endX <= deleteWidth) { //从右向左滑动
scrollTo((int) (startX - endX), 0);
}
break;
case MotionEvent.ACTION_UP:
endX = event.getX();
if (startX - endX < deleteWidth / 2) {
scrollTo(0, 0);
} else {
scrollTo((int) deleteWidth, 0);
}
releaseVelocityTracker();
break;
case MotionEvent.ACTION_CANCEL:
endX = event.getX();
if (startX - endX < deleteWidth / 2) {
scrollTo(0, 0);
} else {
scrollTo((int) deleteWidth, 0);
}
releaseVelocityTracker();
break;
}
return super.onTouchEvent(event);
}
private VelocityTracker mVelocityTracker;
/**
* 计算侧滑速度
*
* @param event
* @return
*/
private float slideSpeed(MotionEvent event) {
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(event);
final VelocityTracker velocityTracker = mVelocityTracker;
velocityTracker.computeCurrentVelocity(1000, MAX_SLIDE_SPEED);
return velocityTracker.getXVelocity();
}
/**
* 释放 VelocityTracker
*/
private void releaseVelocityTracker() {
if (mVelocityTracker != null) {
mVelocityTracker.clear();
mVelocityTracker.recycle();
mVelocityTracker = null;
}
}
/**
* 返回到初始状态
*/
public void reset() {
scrollTo(0, 0);
}
}
注:本文参考《Android开发艺术探索》—— 任玉刚