理解Android 触摸分发机制详解
概述
View中的手势流程
View中关于点击触摸的方法有以下几个相关的方法:
setOnClickListener()点击监听
setOnTouchListener()触摸监听(里面重写了onTouch()方法)
dispatchTouchEvent()事件分发
onTouchEvent()触摸事件(用于解析处理事件)
- setOnClickListener()点击监听
对于需要点击操作的控件增加一个点击监听,这样才能在点击完成后指定响应
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//这里编写完成响应的具体代码
}
});
- onTouchListener()触摸监听
对需要手势操作的控件增加一个触摸监听,这样才能在有手势的时候进行判断
例:
button.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
return false; //默认返回 false
}
});
3.View的dispatchTouchEvent()事件分发
手指操作屏幕就会触发dispatchTouchEvent()方法,dispatchTouchEvent()源码如下:
public boolean dispatchTouchEvent(MotionEvent event) {
//mOnTouchListener触摸监听;如果我们给控件设置了setOnTouchListener监听,mOnTouchListener就不为null
//(mViewFlags & ENABLED_MASK) == ENABLED是判断当前点击的控件是否是可以点击的
//mOnTouchListener.onTouch(this, event),回调控件setOnTouchListener()中的onTouch方法
//三个条件全部成立,从而整个方法直接返回true
if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
mOnTouchListener.onTouch(this, event)) {
return true;
}
//如果上述三个条件一个为false则调用onTouchEvent()
return onTouchEvent(event);
}
}
onTouch()与onTouchEvent()方法都是在dispatchTouchEvent中调用的;onTouch()方法调用在onTouchEvent()方法之前调用;如果一个可以点击的控件在onTouch()方法中通过返回true将事件消费掉,onTouchEvent将不会再执行。
- onTouchEvent()触摸事件
这方法用于解析处理事件的,源码如下:
public boolean onTouchEvent(MotionEvent event) {
final int viewFlags = mViewFlags;
//一个可点击view可点击被禁用了但是仍然需要使用触摸,只是不响应
if ((viewFlags & ENABLED_MASK) == DISABLED) {
return (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
}
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
//如果控件是可以点击的执行switch-case 返回true,否则不执行switch-case返回false
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
switch (event.getAction()) {
//只需要明白在手指抬起的时候最终调用了performClick()方法
//performClick()方法判断如果设置了setOnClickListener()方法来给控件注册一个点击事件时
//都会在performClick()方法里回调被点击控件的onClick方法
case MotionEvent.ACTION_UP:
boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;
if ((mPrivateFlags & PRESSED) != 0 || prepressed) {
//如果在触摸的时候还没有取得焦点
boolean focusTaken = false;
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();
}
if (!mHasPerformedLongPress) {
//去除长按
removeLongPressCallback();
//只有当处于按下状态时才执行采取点击操作
if (!focusTaken) {
// 使用Runnable并发送点击,而不是直接调用performClick
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClick(); //点击方法
}
}
}
if (mUnsetPressedState == null) {
mUnsetPressedState = new UnsetPressedState();
}
if (prepressed) {
mPrivateFlags |= PRESSED;
refreshDrawableState();
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
} else if (!post(mUnsetPressedState)) {
// 如果发送点击失败了,马上解压缩
mUnsetPressedState.run();
}
removeTapCallback();
}
break;
case MotionEvent.ACTION_DOWN:
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
mPrivateFlags |= PREPRESSED;
mHasPerformedLongPress = false;
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
break;
case MotionEvent.ACTION_CANCEL:
mPrivateFlags &= ~PRESSED;
refreshDrawableState();
removeTapCallback();
break;
case MotionEvent.ACTION_MOVE:
final int x = (int) event.getX();
final int y = (int) event.getY();
//按钮不要移位
int slop = mTouchSlop;
if ((x < 0 - slop) || (x >= getWidth() + slop) ||
(y < 0 - slop) || (y >= getHeight() + slop)) {
//去除按钮回调
removeTapCallback();
if ((mPrivateFlags & PRESSED) != 0) {
//去除长按
removeLongPressCallback();
//需要从按下切换到未按下
mPrivateFlags &= ~PRESSED;
refreshDrawableState();
}
}
break;
}
return true;
}
return false;
}
ViewGroup中的手势分发流程
ViewGroup就是一组View的集合,它可以包含多个子View和子VewGroup;例如:LinearLayout、RelativeLayout
ViewGroup中关于点击触摸的方法有以下几个相关的方法:
dispatchTouchEvent()事件分发
disallowIntercept判断事件拦截是否被禁用(默认false;可以通过requestDisallowInterceptTouchEvent()方法设置)
onInterceptTouchEvent()事件拦截
- ViewGroup的dispatchTouchEvent()事件分发
ViewGroup的dispatchTouchEvent()方法与View中的dispatchTouchEvent()方法不同;源码如下:
public boolean dispatchTouchEvent(MotionEvent ev) {
final int action = ev.getAction();
final float xf = ev.getX();
final float yf = ev.getY();
final float scrolledXFloat = xf + mScrollX;
final float scrolledYFloat = yf + mScrollY;
final Rect frame = mTempRect;
boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (action == MotionEvent.ACTION_DOWN) {
if (mMotionTarget != null) {
mMotionTarget = null;
}
//disallowIntercept和!onInterceptTouchEvent(ev)两者有一个为true,就会进入到下面判断中
//disallowIntercept为false启用拦截方法;true为不启用拦截方法
//onInterceptTouchEvent为false不拦截;true为拦截
if (disallowIntercept || !onInterceptTouchEvent(ev)) {
ev.setAction(MotionEvent.ACTION_DOWN);
final int scrolledXInt = (int) scrolledXFloat;
final int scrolledYInt = (int) scrolledYFloat;
final View[] children = mChildren;
final int count = mChildrenCount; //子控件数
//遍历所有的子控件
for (int i = count - 1; i >= 0; i--) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE
|| child.getAnimation() != null) {
child.getHitRect(frame);
if (frame.contains(scrolledXInt, scrolledYInt)) { //是否是当前点击的view
final float xc = scrolledXFloat - child.mLeft;
final float yc = scrolledYFloat - child.mTop;
ev.setLocation(xc, yc);
child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
if (child.dispatchTouchEvent(ev)) {//调用该View的dispatchTouchEvent()事件分发
mMotionTarget = child;
return true; //
}
}
}
}
}
}
boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||
(action == MotionEvent.ACTION_CANCEL);
if (isUpOrCancel) {
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
final View target = mMotionTarget;
if (target == null) {
ev.setLocation(xf, yf);
if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
ev.setAction(MotionEvent.ACTION_CANCEL);
mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
}
return super.dispatchTouchEvent(ev);//50
}
if (!disallowIntercept && onInterceptTouchEvent(ev)) {
final float xc = scrolledXFloat - (float) target.mLeft;
final float yc = scrolledYFloat - (float) target.mTop;
mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
ev.setAction(MotionEvent.ACTION_CANCEL);
ev.setLocation(xc, yc);
if (!target.dispatchTouchEvent(ev)) {
}
mMotionTarget = null;
return true;
}
if (isUpOrCancel) {
mMotionTarget = null;
}
final float xc = scrolledXFloat - (float) target.mLeft;
final float yc = scrolledYFloat - (float) target.mTop;
ev.setLocation(xc, yc);
if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
ev.setAction(MotionEvent.ACTION_CANCEL);
target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
mMotionTarget = null;
}
return target.dispatchTouchEvent(ev);
}
-
disallowIntercept判断事件拦截是否被禁用(默认false;可以通过requestDisallowInterceptTouchEvent()方法设置)
例:需改disallowIntercept值为true
requestDisallowInterceptTouchEvent(true); -
onInterceptTouchEvent()事件拦截
源码如下:
public boolean onInterceptTouchEvent(MotionEvent ev) {
return false;
}
Android事件的传递是从外层控件传递到内层控件(父控件传子控件);操作屏幕后系统收到事件后首先传递给最外层的容器,调用外层容器的事件分发dispatchTouchEvent()方法,外层容器通过自己的disallowIntercept先判断事件拦截是否被禁用(disallowIntercept默认false,通过requestDisallowInterceptTouchEvent()设置),如果没被禁用(false),则会调用onInterceptTouchEvent()判断事件是否被拦截。如果返回false则事件不会被拦截,就会遍历外层控件的子控件,并调用子控件的dispatchTouchEvent(),子控件如果是容器又会重复上面步骤,如果不是容器,就会执行View的dispatchTouchEvent()进行上面介绍的View中的流程;如果外层控件的onInterceptTouchEvent()返回true,事件则不会继续往下传递;这个过程都是在外层容器的disallowIntercept方法中执行的;所以需要指出的是外层控件的disallowIntercept方法和子View的disallowIntercept虽然方法名相同,但是内部的功能是有区别的!理想状况下外层的父控件是disallowIntercept方法是负责判断是否阻拦手势,并把手势的分发到子控件;子控件的disallowIntercept方法是通过onTouchEvent()触摸事件执行具体的手势操作!