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

Android中的事件分发机制

2014-03-03 21:45 387 查看

1. 一个小问题引发的思考

2. 通过源码探索View中的事件分发机制

3.通过源码探索ViewGroup的事件分发机制

4.结论

5.参考文献

最近的一个项目中涉及到UGC的处理,大致布局为一个RelativeLayout包含了一个EditText和一个Button,当点击EditText时,弹出软键盘,点击RelativeLayout中除了EditText和Button之外其它的地方时,收起软键盘。



实现起来很简单,为EditText和RelativeLayout分别注册一个onTouch事件,为Button注册一个click事件,一切Ok~ 但在业务层简单的实现背后,却给我带来了一些疑问:

1. EditText作为RelativeLayout的子元素,为何它的onTouch事件没有触发父元素(RelativeLayout)的onTouch事件,父子节点的同一事件的事件分发逻辑是怎样呢?

2. onClick事件和onTouch事件有和关联呢?

3. 我们既可以为控件注册onTouch事件(setOnTouchLisnter),也可以自定义控件实现onTouchEvent方法,onTouch方法和onTouchEvent方法有何区别呢,它们的执行时机是什么?

带着这三个疑问,我踏上了google, 度娘,以及Android源代码探索的不归路~

Android中,所有的操作类型事件都由如下三个部分作为基础:

按下(ACTION_DOWN)
移动(ACTION_MOVE)
抬起(ACTION_UP)

这三部分都寄生于onTouch事件中,由MontionEvent类中定义的三个常量进行区分。

Android中与Touch事件相关的方法为:

Touch事件相关方法

方法功能
ViewGroup
View(子View
Activity
public boolean dispatchTouchEvnet(MotionEvent ev)

事件分发

Yes

Yes

Yes

public boolean onInterceptTouchEvent(MotionEvent ev)

事件拦截

Yes

No

No

public boolean onTouchEvent(MotionEvent ev)
事件响应

Yes

Yes

Yes

分发逻辑:整个Touch事件的分发其实是以Activity的dispatchTouchEvent作为起点,将事件传递给最外层ViewGroup的dispatchTouchEvent方法,再由该ViewGroup进行递归分发,直至叶子节点View的dispatchTouchEvent方法中。

为了证明这一逻辑,我们对代码一层层地分析,首先看下View中的dispatchTouchEvent方法:

View.java

public boolean dispatchTouchEvent(MotionEvent event) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}

if (onFilterTouchEventForSecurity(event)) {
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
return true;
}

if (onTouchEvent(event)) {
return true;
}
}

if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
return false;
}


整个程序中有最关键的两处判断(第9行和14行),在第9行的if条件中,如果li.mOnTouchListener != null,(mViewFlags & ENABLED_MASK) == ENABLED 和 mOnTouchListener.onTouch(this, event) == true这三个条件同时满足时,就直接返回true。

第一个条件:mOnTouchListener在哪里赋值呢?CRTL+F搜索,发现了View.java中的如下方法:

View.java

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


太眼熟了,这不正是我们平时注册onTouch事件所使用的函数么,这下明白了,这要注册了onTouch事件,mOnTouchListener就一定不为空。好了,继续往下看。

第二个条件:(mViewFlags & ENABLED_MASK) == ENABLED 判断当前的View是否为ENABLED,一般控件默认都是ENABLED,因此这个条件也成立。

第三个条件:mOnTouchListener.onTouch(this, event) == true,即我们注册的onTouch方法中,如果返回true,就会使三个条件都成立,这样,就不会继续接下来的判断了。

在第14行的If判断中,只是简单的判断了onTouchEvent方法的返回是否为true。

上述短短的几行代码解开了我的第三个疑问,下面来总结一下:

在View的dispatchTouchEvent方法中,最先执行的是onTouch方法,只有当onTouch方法返回false,才有机会去执行onTouchEvent方法。
touch事件有层级传递机制,当我们为一个view注册touch事件,就会触发一系列的ACTION_DOWN,ACTION_MOVE_ACTION_UP,如果在执行ACTION_DOWN的时候返回false,则后面的ACTION_MOVE和ACTION_UP都得不到执行,即使用[b]dispatchTouchEvent进行分发的时候,只有前一个action返回false,后一个action才能得到执行。[/b]

在上述的的第二个结论中,我们可知,只有当onTouch方法和onTouchEvent都返回false,才能继续后面的操作,onTouch方法的返回一般是我们自己控制的,而onTouchEvent方法,若不自定义控件,则往往会使用它的默认实现,接下来通过源码分析一下方法:
View.java

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

if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
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 & PFLAG_PREPRESSED) != 0;
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
// take focus if we don't have it already and we should in
// touch mode.
boolean focusTaken = false;
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();
}

if (prepressed) {
// The button is being released before we actually
// showed it as pressed.  Make it show the pressed
// state now (before scheduling the click) to ensure
// the user sees it.
setPressed(true);
}

if (!mHasPerformedLongPress) {
// This is a tap, so remove the longpress check
removeLongPressCallback();

// Only perform take click actions if we were in the pressed state
if (!focusTaken) {
// Use a Runnable and post this rather than calling
// performClick directly. This lets other visual state
// of the view update before click actions start.
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClick();
}
}
}

if (mUnsetPressedState == null) {
mUnsetPressedState = new UnsetPressedState();
}

if (prepressed) {
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
} else if (!post(mUnsetPressedState)) {
// If the post failed, unpress right now
mUnsetPressedState.run();
}
removeTapCallback();
}
break;

case MotionEvent.ACTION_DOWN:
mHasPerformedLongPress = false;

if (performButtonActionOnTouchDown(event)) {
break;
}

// Walk up the hierarchy to determine if we're inside a scrolling container.
boolean isInScrollingContainer = isInScrollingContainer();

// For views inside a scrolling container, delay the pressed feedback for
// a short period in case this is a scroll.
if (isInScrollingContainer) {
mPrivateFlags |= PFLAG_PREPRESSED;
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
} else {
// Not inside a scrolling container, so show the feedback right away
setPressed(true);
checkForLongClick(0);
}
break;

case MotionEvent.ACTION_CANCEL:
setPressed(false);
removeTapCallback();
removeLongPressCallback();
break;

case MotionEvent.ACTION_MOVE:
final int x = (int) event.getX();
final int y = (int) event.getY();

// Be lenient about moving outside of buttons
if (!pointInView(x, y, mTouchSlop)) {
// Outside button
removeTapCallback();
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
// Remove any future long press/tap checks
removeLongPressCallback();

setPressed(false);
}
}
break;
}
return true;
}

return false;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: