一对于普通View来说:
重要的事件分发方法有3个
1.public boolean dispatchTouchEvent(MotionEvent event);
2.public boolean onTouch(View v, MotionEvent event) ;
3.public boolean onTouchEvent(MotionEvent event);
这3个方法是从1到3执行,但是在一些条件下,有些方法可能会不执行。在普通控件dispatchTouchEvent中返回的是父类
View的dispatchTouchEvent执行的值,为boolean类型,当返回false时也就意味着接下来的动作事件就不会执行这些方法,下面是源码(源码是5.0以上的):
<span style="font-size:14px;"> public boolean dispatchTouchEvent(MotionEvent event) {
// If the event should be handled by accessibility focus first.
if (event.isTargetAccessibilityFocus()) {
// We don't have focus or no virtual descendant has it, do not handle the event.
if (!isAccessibilityFocusedViewOrHost()) {
return false;
}
// We have focus and got the event, then use normal event dispatch.
event.setTargetAccessibilityFocus(false);
}
boolean result = false;
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
final int actionMasked = event.getActionMasked();
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Defensive cleanup for new gesture
stopNestedScroll();
}
if (onFilterTouchEventForSecurity(event)) {
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
if (!result && onTouchEvent(event)) {
result = true;
}
}
if (!result && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
// Clean up after nested scrolls if this is the end of a gesture;
// also cancel it if we tried an ACTION_DOWN but we didn't want the rest
// of the gesture.
if (actionMasked == MotionEvent.ACTION_UP ||
actionMasked == MotionEvent.ACTION_CANCEL ||
(actionMasked == MotionEvent.ACTION_DOWN && !result)) {
stopNestedScroll();
}
return result;
}</span>
3到10行是判断该控件是否能获取焦点,是否能接受到事件,如果不能就直接返回false,也就是这个控件不能点击触摸事件的分发,反之就是正常的分发事件。第26行的ListenerInfo li = mListenerInfo; 中的mListenerInfo一般在控件注册监听器或者获取焦点的时候就会调用getListenerInfo的方法进行初始化,ListenerInfo 它是一个静态内部类,里面包含各种接口,用于事件的监听。
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
在这几行代码中可以看到onTouch方法的执行,但要有条件的,首先li和mOnTouchListener不能为空,并且控件是enabled,li是
mListenerInfo,而mOnTouchListener是一个接口,通过setOnTouchListener来初始化,在这些条件满足的情况下才会执行onTouch 方法。当onTouch返回true是result设为true。result 的状态会影响onTouchEvent的执行,在33行中可以看到当reslut为true时不会执行onTouchEvent,也就是说onTouch返回true时onTouchEvent不会执行。
onTouch是自己实现的一个接口方法就不用看了,再看看View类的onToucnEvent。有时候自定义滑动控件的时候会在onTouchEvent中直接返回true,本来是默认返回onTouchEvent执行结果,这样子写出来的控件也就监听不了点击事件了,因为点击事件是在父类的onTouchEvent里执行的,看看下面的源码:
<span style="font-size:14px;"> public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
final int action = event.getAction();
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (action == 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)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE);
}
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
(viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
switch (action) {
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, x, y);
}
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
// 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();
}
mIgnoreNextUpEvent = false;
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();
}
mPendingCheckForTap.x = event.getX();
mPendingCheckForTap.y = event.getY();
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
} else {
// Not inside a scrolling container, so show the feedback right away
setPressed(true, x, y);
checkForLongClick(0);
}
break;
case MotionEvent.ACTION_CANCEL:
setPressed(false);
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
break;
case MotionEvent.ACTION_MOVE:
drawableHotspotChanged(x, y);
// 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;
}</span>
都知道当手点击还没离开时是不会执行onClick的要在离开时才会执行,可以看到在ACTION_UP中可以看到performClick方法,也就是第59行。这方法里面执行了onClick方法,可以看一看在什么条件下执行的:
首先看到第一个返回判断第7行,判断该控件是否Disabled,如果是就返回时候能点击,长按以及CONTEXT_CLICKABLE的或值,CONTEXT_CLICKABLE不知道是什么,知道的希望私信博主,谢谢!
第二个返回判断在18到22行。这其实是一个代理,是把这个控件的点击触摸事件在另外一个控件上实行,如果代理控件执行成功返回true时,代码也就结束并返回true,该控件的点击事件就不会执行。
接下来是24到26行,还是那个判断,满足返回true,不满足返回false,上面说了onClik是在ACTION_UP中调用,只有在ACTION_DOWN返回true时,dispatchTouchEvent才会返回true,才会执行接下来的事件,比如ACTION_UP,这样才会调用onClick。
再接下来看51和58行的两个判断,58中的post(mPerformClick)mPerformClick是一个实现了runnabe接口的类并在run中执行了performClick方法,如果执行失败的话就在ui线程中直接调用performClick方法。
最后就是performClick中的
<span style="font-size:14px;"> if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
li.mOnClickListener.onClick(this);
result = true;
} else {
result = false;
}</span>
满足li和mOnClickListener不为空就执行onClick方法,li上面已经说了,mOnClickListener也简单是注册点击时传过来的。
简单总结:在dispatchTouchEnevt中执行时中当控件注册onTouchListener,处于Enable状态下,并且onTouch返回false时执行为dispatchTouchEnevt ->onTouch->onTouchEvent;当onTouch返回true时执行dispatchTouchEnevt ->onTouch.当控件没有设置onTouchListener,或者处于Disable状态时dispatchTouchEnevt ->onTouchEvent。当然还包括点击事件等。
接下来是ViewGroup的事件分发,ViewGroup同样是继承了View在有些事件分发方面和普通的控件是相同的,不同的在于它含有一些子控件,当点击ViewGroup中的子控件时是由谁来处理?源码看起来有点吃力,稍等!!