Android源码:事件分发源码解析(一)

请尊重原创,转载请注明出处【tianyl_Melody】的博客

在Android中,我们经常被问到的一个问题就是事件分发,关于事件分发,感觉不论是谁都能够说那么两句,但是如果一到应用中,就又经常遇到奇奇怪怪的问题,今天,就从一个简单的事件分发的问题说起

1、onTouch和onClick

如果一个View我们同时设置onTouch和onClick的监听会怎么样呢,按照我们的想法,那就是触摸的时候调用onTouch方法,然后点击的时候,就调用onClick方法,

于是写下这两个方法:

@Override
    public boolean onTouch(View v, MotionEvent event) {
        Toast.makeText(getApplicationContext(),"onTouch",Toast.LENGTH_SHORT).show();
        return true;
    }

    @Override
    public void onClick(View v) {
        Toast.makeText(getApplicationContext(),"onClick",Toast.LENGTH_SHORT).show();
    }

分别试着在onTouch方法中返回true和false,奇怪的事情发生了,当返回true的时候,并没有调用到onClick方法,为什么会这样呢,接下来,就从源码中寻找答案。

首先,这肯定是事件分发搞的鬼,所以去dispatchTouchEvent寻找答案,然后我们寻找onTouch方法,看onTouch返回值不同会有什么影响:

public boolean dispatchTouchEvent(MotionEvent event) {
    ......
    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;
    }

如果li.mOnTouchListener.onTouch(this, event)返回结果为true,那么result就为true,结果不会调用到view中的onTouchEvent方法。

而mOnTouchListener这个则是我们自己设置的

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

而这个判断中


if (!result && onTouchEvent(event))

如果result为true,那么就不会执行onTouchEvent这个方法;

在onTouchEvent方法中

if (!post(mPerformClick)) {
      performClick();
}

会通过performClick调用到onClick这个方法。

public boolean performClick() {
    final boolean result;
    final ListenerInfo li = mListenerInfo;
    if (li != null && li.mOnClickListener != null) {
        playSoundEffect(SoundEffectConstants.CLICK);
        li.mOnClickListener.onClick(this);
        result = true;
    } else {
        result = false;
    }
    sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
    return result;
}

这里的li.mOnClickListener.onClick(this),也是我们设置的

public void setOnClickListener(@Nullable OnClickListener l) {
    if (!isClickable()) {
        setClickable(true);
    }
    getListenerInfo().mOnClickListener = l;
}

2、Activity的事件分发

接下来,就从Activity的点击开始说起

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

由native层调用到Activity的dispatchTouchEvent方法,然后在Activity中判断
如果getWindow().superDispatchTouchEvent(ev)返回true,那么就不会调用到Activity自己的onTouchEvent方法。

getWindow拿到的是PhoneWindow对象,调用的是PhoneWindow中的superDispatchTouchEvent方法。

PhoneWindow.java
public boolean superDispatchTouchEvent(MotionEvent event) {
    return mDecor.superDispatchTouchEvent(event);
}

在PhoneWindow中调用的是mDecor的superDispatchTouchEvent的方法,而在mDecor的superDispatchTouchEvent方法中,又是调用的父类的dispatchTrackballEvent方法。

class DecorView
    public boolean superDispatchTrackballEvent(MotionEvent event) {
    return super.dispatchTrackballEvent(event);
}

而DecorView其实是继承的FrameLayout的
private final class DecorView extends FrameLayout
而FrameLayout并没有实现dispatchTrackballEvent方法,其实最终调用到的,还是ViewGroup的dispatchTrackballEvent方法。

3、ViewGroup

前两行是辅助功能,这里略过,在事件分发中,首先会进行安全检查

if (onFilterTouchEventForSecurity(ev))
    然后在down事件中进行初始化
    if (actionMasked == MotionEvent.ACTION_DOWN) {
        // Throw away all previous state when starting a new touch gesture.
        // The framework may have dropped the up or cancel event for the previous gesture
        // due to an app switch, ANR, or some other state change.
        cancelAndClearTouchTargets(ev);
        resetTouchState();
    }

在cancelAndClearTouchTargets方法中,如果不是第一次点击事件(例如之前已经点击过一次),mFirstTouchTarget则不为null,那么会清空之前的点击状态,其中有一个clearTouchTargets方法:

private void clearTouchTargets() {
    TouchTarget target = mFirstTouchTarget;
    if (target != null) {
        do {
            TouchTarget next = target.next;
            target.recycle();
            target = next;
        } while (target != null);
        mFirstTouchTarget = null;
    }
}

会清空这个单向链表,并制空。

在resetTouchState方法中,会重置所有的触摸状态,为新的触摸事件做准备。

private void resetTouchState() {
    clearTouchTargets();
    resetCancelNextUpFlag(this);
    mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
    mNestedScrollAxes = SCROLL_AXIS_NONE;
}

clearTouchTargets方法,之前已经调用过,会清除mFirstTouchTarget的值,resetCancelNextUpFlag方法则会清除一些标志位。

接下来有一个变量
final boolean intercepted;
保存是否拦截的标志。

如果调用过requestDisallowInterceptTouchEvent(true)这个方法

public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
    if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
        // We're already in this state, assume our ancestors are too
        return;
    }
    if (disallowIntercept) {
        mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
    } else {
        mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
    }
    // Pass it up to our parent
    if (mParent != null) {
        mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
    }
}

那么mGroupFlags会保存FLAG_DISALLOW_INTERCEPT这个标志位。

final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
此时disallowIntercept为true。

if (!disallowIntercept) {
    intercepted = onInterceptTouchEvent(ev);
    ev.setAction(action); // restore action in case it was changed
} else {
    intercepted = false;
}

那么就不会执行onInterceptTouchEvent(ev)这个方法。

以上,就是一个Activity的点击事件,调用到ViewGroup的整个过程。

简要总结一下:
1、当事件点击到Activity上时,首先会由底层传递到Activity中,调用dispatchTouchEvent方法。
2、在Activity的dispatchTouchEvent方法中,会调用到getWindow().superDispatchTouchEvent(ev),这里的getWindow拿到的就是PhoneWindow。
3、在getWindow的superDispatchTouchEvent中,会调用到mDecor.superDispatchTouchEvent。
4、因为mDecor是继承的FrameLayout的,所以这里其实就是调用的ViewGroup的dispatchTouchEvent。

到这里,基本理清了,一个点击事件点到Activity上面,是如何传递过来的,搞清楚了从哪里来,接下来就需要理清这个事件到哪里去(篇幅有限,见下一篇)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值