概述
前两篇分别介绍了事件分发大致的流程,且分析了下源码,这篇主要是实现一个简单的拦截和处理的例子。例子非常简单,只是介绍父控件和子控件之间的事件分发和处理。
界面
image.png
这个界面非常简单,主要由一个Outer_1,Outer_2和Inner组成。两个Outer继承自RelativeLayout。Inner继承自简单的View,重写onTouchEvent返回true,就是说默认处理点击事件。
这种情况简单的模拟了一个能左右滑动的布局里面有上下滑动的子控件(如ViewPager中有ListView,这两个控件的组合当然非常复杂,这里简单的说下原理)。
代码
public class Outer1 extends RelativeLayout {
//滑动的Scroller
private Scroller mScroller;
//在onInterceptTouchEvent中前一个点的记录
private int mLastInterceptX;
private int mLastInterceptY;
//在onTouchEvent中前一个点的记录
private int mLastX;
private int mLastY;
public Outer1(Context context) {
super(context);
init();
}
public Outer1(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public Outer1(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
mScroller = new Scroller(getContext());
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
// 如果确认拦截的话,那么这个方法只会调用一次
boolean shouldIntercept = false;
int x = (int) ev.getX();
int y = (int) ev.getY();
int action = ev.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
// 如果是down,那么默认不拦截
shouldIntercept = false;
// 只有在动画未完成时才主动拦截事件
if (!mScroller.isFinished()) {
mScroller.abortAnimation();
shouldIntercept = true;
}
break;
case MotionEvent.ACTION_MOVE:
// 判断是朝哪个方位滑动的
int deltaX = x - mLastInterceptX;
int deltaY = y - mLastInterceptY;
// 如果是朝右侧移动,那么就拦截该事件
if (deltaX > 0 && deltaX > Math.abs(deltaY)) {
shouldIntercept = true;
}
break;
case MotionEvent.ACTION_UP:
// 不做任何处理
break;
}
// 因为可能在DOWN事件的时候不是交由自己处理,所以mLastX和mLastY必须设置,不然会产生跳动
mLastX = x;
mLastY = y;
mLastInterceptX = x;
mLastInterceptY = y;
return shouldIntercept;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
int action = event.getAction();
int scrollX = getScrollX();
switch (action) {
case MotionEvent.ACTION_DOWN:
// 不做任何处理
break;
case MotionEvent.ACTION_MOVE:
// 因为拦截的向右侧的移动,所以只移动x轴
int deltaX = x - mLastX;
if (scrollX - deltaX >= 0) {
scrollBy(0, 0);
} else {
scrollBy(-deltaX, 0);
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
// 当放开或者取消的时候,平滑移动到最初的位置
smoothScrollBy(-scrollX, 0);
break;
}
mLastX = x;
mLastY = y;
return true;
}
private void smoothScrollBy(int dx, int dy) {
mScroller.startScroll(getScrollX(), getScrollY(), dx, dy, 500);
invalidate();
}
@Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
postInvalidate();
}
}
}
Outer_2和Outer_1的代码差不多,这里只贴出来不一样的部分
public class Outer2 extends RelativeLayout {
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
// 如果确认拦截的话,那么这个方法只会调用一次
boolean shouldIntercept = false;
int x = (int) ev.getX();
int y = (int) ev.getY();
int action = ev.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
// 如果是down,那么默认不拦截
shouldIntercept = false;
// 只有在动画未完成时才主动拦截事件
if (!mScroller.isFinished()) {
mScroller.abortAnimation();
shouldIntercept = true;
}
break;
case MotionEvent.ACTION_MOVE:
// 判断是朝哪个方位滑动的
int deltaX = x - mLastInterceptX;
int deltaY = y - mLastInterceptY;
// 如果是朝下侧移动,那么就拦截该事件
if (deltaY > 0 && deltaY > Math.abs(deltaX)) {
shouldIntercept = true;
}
break;
case MotionEvent.ACTION_UP:
// 不做任何处理
break;
}
mLastX = x;
mLastY = y;
mLastInterceptX = x;
mLastInterceptY = y;
return shouldIntercept;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
int action = event.getAction();
int scrollY = getScrollY();
switch (action) {
case MotionEvent.ACTION_DOWN:
// 不做任何处理
break;
case MotionEvent.ACTION_MOVE:
// 因为拦截的向下侧的移动,所以只移动x轴
int deltaY = y - mLastY;
if (scrollY - deltaY >= 0) {
scrollBy(0, 0);
} else {
scrollBy(0, -deltaY);
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
// 当放开或者取消的时候,平滑移动到最初的位置
smoothScrollBy(0, -scrollY);
break;
}
mLastX = x;
mLastY = y;
return true;
}
private void smoothScrollBy(int dx, int dy) {
mScroller.startScroll(getScrollX(), getScrollY(), dx, dy, 500);
invalidate();
}
Inner只是一个重写了onTouchEvent方法的View,非常简单
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.e("Inner:", "x:" + event.getX() + ",y:" + event.getY());
return true;
}
第一篇和第二篇中讲了原理,这里看下效果怎么样
1.gif
可以发现,当我们向下滑动的时候,Outer_1不会去拦截,当向右滑动时Outer_1会拦截事件。当先向下滑动再向右滑动时Outer_1会拦截事件,随后所有的事件便不会向下分发。但是注意一点,拦截时会通知MotionEvent.ACTION_CANCEL给子控件。