您的位置:首页 > 移动开发 > Android开发

Android Touch事件分发响应机制

2015-08-19 20:36 771 查看

概述

在Android中,事件包括了点按、长按、拖拽、滑动等,有了这些事件才能让Android响应用户的各种操作。但是归根结底,所有的这些事件都是以如下三个部分作为基础的:

ACTION_DOWN(按下)

ACTION_MOVE(移动)

ACTION_UP(抬起)

所有的操作事件首先必须执行ACTION_DOWN(按下)操作,之后所有的操作都是以按下操作为前提,当按下操作完成后,接下来可能是一段ACTION_MOVE然后ACTION_UP,或者直接ACTION_UP。

所有操作事件的执行顺序必须是:ACTION_DOWN -> ACTION_MOVE -> ACTION_UP。(是否包含ACTION_MOVE取决于用户手势中是否包含了移动操作)

本文基于Android2.2.3版本进行分析(主要是因为:2.2.3版本代码清晰简单一些,而且原理是通用的)

Activity事件传递

Touch事件的产生涉及到硬件和Linux内核部分,虽然我在硬件组,但是我也不想去深究这块内容。目前只需要知道Touch事件产生后,最先响应它的是ActivitydispatchTouchEvent方法。

public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}


其中,onUserInteraction()是一个空方法,开发者可以根据自己的需求override这个方法,这个方法在一个Touch事件的周期中肯定是第一个被调用。

接着分析getWindow().superDispatchTouchEvent(ev),这个方法其实最终是调用了ViewGroup的dispatchTouchEvent方法。

小结:

通过上面的分析,我们至少可以知道,一个Touch事件首先经过Activity的dispatchTouchEvent方法处理,然后分配给了ViewGroup的dispatchTouchEvent方法。

ViewGroup响应Touch事件

重点就是分析dispatchTouchEvent()方法,2.2.3版本这个方法还算简单,添加中文注释的源码如下:

public boolean dispatchTouchEvent(MotionEvent ev) {
final int action = ev.getAction();
final float xf = ev.getX();
final float yf = ev.getY();
// mScrollX代表视图起始坐标x轴方向偏移量
// mScrollY代表视图起始坐标y轴方法偏移量
final float scrolledXFloat = xf + mScrollX;
final float scrolledYFloat = yf + mScrollY;
final Rect frame = mTempRect;

boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;

// 处理初始的ACTION_DOWN事件
if (action == MotionEvent.ACTION_DOWN) {
// 当行为是ACTION_DOWN时,应该将mMotionTarget置为null
// 因为,ACTION_DOWN代表一个新的Touch事件
if (mMotionTarget != null) {
mMotionTarget = null;
}

// 判断当前ViewGroup是否对touch事件进行拦截
if (disallowIntercept || !onInterceptTouchEvent(ev)) {
ev.setAction(MotionEvent.ACTION_DOWN);
// 从ViewGroup的子View中找到应该处理该touch事件的控件
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);
// 判断当前的Touch事件是否位于child的矩形区域中
if (frame.contains(scrolledXInt, scrolledYInt)) {
// 获取Touch事件相对于View控件的偏移
final float xc = scrolledXFloat - child.mLeft;
final float yc = scrolledYFloat - child.mTop;
ev.setLocation(xc, yc);
child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
// 调用child view的dispatchTouchEvent方法对Touch事件进行处理
if (child.dispatchTouchEvent(ev)) {
// 设置touch事件之后的处理View
mMotionTarget = child;
return true;
}
}
}
}
}
}

boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) || (action == MotionEvent.ACTION_CANCEL);
if (isUpOrCancel) {
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}

// 之前处理ACTION_DOWN的时候,已经确定了需要处理后续touch事件的子View
final View target = mMotionTarget;

// 这里说明点击的是ViewGroup的空白区域,这时的Touch事件就需要由ViewGroup自己来处理了
if (target == null) {
ev.setLocation(xf, yf);
// ViewGroup的父类是View,所以这里其实也是调用了View的dispatchTouchEvent方法
return super.dispatchTouchEvent(ev);
}

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);
// 交给子View来处理Touch事件
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);
}


由于ViewGroup默认的onInterceptTouchEvent的返回值为false,所以ViewGroup并不对TouchEvent进行拦截。

从代码中,我们可以看到,如果ViewGroup本身不对Touch事件进行拦截,则Touch事件最终是交给相应的子View去处理的。

View响应Touch事件

从ViewGroup响应Touch事件的源码分析来看,ViewGroup在不拦截Touch事件的前提下,是将Touch事件分发给Child View实现的。假设这里的Child View是一个Button,那我们来研究一下Touch事件在View里的传递规则。

查看Button的源码,我们是找不到dispatchTouchEvent方法实现的。但是,由于android.widget.Button继承自android.widget.TextView->android.view.View,最终我们发现dispatchTouchEvent方法是在View类中实现的。源码如下:

public boolean dispatchTouchEvent(MotionEvent event) {
if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
mOnTouchListener.onTouch(this, event)) {
return true;
}
return onTouchEvent(event);
}


Android2.2.3版本,superDispatchTouchEvent方法实现非常简单,但是已经足够说明问题。接下来,我们逐个分析if语句中的代码实现。

第一,mOnTouchListener是如何赋值的?

解答:从View中我们能找到如下方法:

public void setOnTouchListener(OnTouchListener l) {
mOnTouchListener = l;
}


这个方法我们应该是非常熟悉,当开发者给控件注册touch事件响应对象的时候,mOnTouchListener就已经被赋值了。

第二,mOnTouchListener.onTouch()算是回调方法吗?

解答:必须算啊,我们在对控件注册onTouch事件对象的时候,都会重写onTouch方法,这里就是触发我们注册的ouTouch方法。并且,我们可以看到,如果我们在重写的onTouch方法中返回true,则该Touch事件就已经被处理完成,否则,就继续调用View的onTouchEvent()方法。

那接下来,我们就继续分析一下View的onTouchEvent()方法,源码如下:

public boolean onTouchEvent(MotionEvent event) {
final int viewFlags = mViewFlags;

// 被disable的View不响应Touch事件
if ((viewFlags & ENABLED_MASK) == DISABLED) {
return ((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
}

if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}

if ((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) {
switch(event.getAction()) {
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) {
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:
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();

}
break;
}
}
}


onTouchEvent()方法看起来还是挺复杂的。这里,我们关注重点,特别是对ACTION_UP的处理。其中,对ACTION_UP的处理最终会调用到PerformClick类的实例化。接下来,我们看一下该类是如何实例化的:

private final class PerformClick implements Runnable {
public void run() {
performClick();
}
}

public boolean performClick() {
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);

if (mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
mOnClickListener.onClick(this);
return true;
}
return false;
}


之前我们分析过mOnTouchListener是在onTouch事件赋值的,那mOnClickListener肯定是在onClick事件进行赋值的,所以这里是在ACTION_UP事件中回调了onClick方法。

“`

参考资料

Android Deeper-Touch事件分发响应机制

Android事件分发机制完全解析,带你从源码的角度彻底理解(上)

Android事件分发机制完全解析,带你从源码的角度彻底理解(下)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  android