Android|事件分发

导语

事件分发是一个老生常谈的问题,理解事件分发机制,对于解决日常开发工作中遇到的滑动冲突问题,有着至关重要的作本,这里以阅读源码的方式,对事件分发机制做一个详细的讲解,希望阅读本文后,能对事件分发机制有个深入的理解,解决日常遇到的滑动冲突问题

源码阅读

从Activity开始



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


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


public boolean superDispatchTouchEvent(MotionEvent event) {
    return super.dispatchTouchEvent(event);
}


下面就进入了View Group的流程


@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    
    boolean handled = false;
    
    if(actionMasked == MotionEvent.ACTION_DOWN){
        mFirstTouchTarget = null;
    }
    
    
    final boolean intercepted;
    
    if (actionMasked == MotionEvent.ACTION_DOWN
        || mFirstTouchTarget != null) {
        final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
        if (!disallowIntercept) {
            
            intercepted = onInterceptTouchEvent(ev);
            ev.setAction(action); 
        } else {
            intercepted = false;
        }
    } else {
        
        
        intercepted = true;
    }

    
    TouchTarget newTouchTarget = null;
    boolean alreadyDispatchedToNewTouchTarget = false;
    if (!intercepted) {
        
        if (actionMasked == MotionEvent.ACTION_DOWN) {
            if (newTouchTarget == null && childrenCount != 0) {
                
                for (int i = childrenCount - 1; i >= 0; i--) {
                    
                    if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                        
                        newTouchTarget = addTouchTarget(child, idBitsToAssign);
                        
                        alreadyDispatchedToNewTouchTarget = true;
                        break;
                    }

                }
            }

        }
    }

    
    
    if (mFirstTouchTarget == null) {
        
        
        handled = dispatchTransformedTouchEvent(ev, canceled, null,
            TouchTarget.ALL_POINTER_IDS);
    } else {
        
        
        
        TouchTarget predecessor = null;
        TouchTarget target = mFirstTouchTarget;
        while (target != null) {
            final TouchTarget next = target.next;
            
            if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                handled = true;
            } else {
                
                if (dispatchTransformedTouchEvent(ev, cancelChild,
                    target.child, target.pointerIdBits)) {
                    handled = true;
                }

            }
            predecessor = target;
            target = next;
        }
    }

    
    return handled;
}

从源码中可以得知,整体上,事件的分发是由Activity自顶向下逐级传播的,通过Activity的dispatchTouchEvent最终访问到DecorView的dispatchTouchEvent方法,

dispatchTouchEvent是事件分发的入口,这里要重点讲解,从上述代码中标注的内容来看,我们可以得知,任何一个事件,都是从Down事件开始的,从dispatchTouchEvent的源码中我们也可以了解到,有很多针对DOWN事件单独处理的逻辑,因此之类解读源码,也分量两个流程讲解,DOWN事件的流程与其他事件的流程

DOWN事件的流程
  • 第一步:确定是否需要拦截事件
    –理解:DOWN事件到来时,确认是否需要自己拦截处理,前置清空mFirstTouchTarget字段,调用自身的onInterceptTouchEvent来确认是否需要自己主动拦截处理该事件
  • 第二步:DOWN事件分发
    –若自身不需要拦截DOWN事件,则将事件分发给子VIEW,调用child.dispatchTouchEvent递归询问子view是否能处理该事件,若子view能处理,则记录mFirstTouchTarget,和alreadyDispatchedToNewTouchTarget
    –若自身拦截,则直接跳过第二步
  • 第三步:真正事件分发,将事件分发给对应View
    –根据第二步的结果,已经得知事件是自己处理还是子View处理,自身处理最终会调用自身的onTouchEvent来处理事件,若是子VIEw处理,因DOWN事件的特殊性,在第二步已经调用了child.dispatchTouchEvent,第三步则根据第二步记录的alreadyDispatchedToNewTouchTarget 字段,直接返回已经处理的结果
  • 第四步:无
其他事件的分发流程
  • 第一步:确定是否需要拦截事件
    –理解:根据mFirstTouchTarget字段,判断,若存在能处理事件的子VIEW,调用自身的onInterceptTouchEvent来确认是否需要自己主动拦截处理该事件
  • 第二步:无
  • 第三步:真正事件分发,将事件分发给对应View
    –根据DOWN事件时记录的结果,已经得知事件是自己处理还是子View处理,自身处理最终会调用自身的onTouchEvent来处理事件,若是子VIEw处理,则调用了child.dispatchTouchEvent
  • 第四步:返回第三步得到的结果给上层。

需要重点理解几个字段
mFirstTouchTarget:在Down事件时记录,子view能否处理事件,能的话后面的新事件交个子view处理
alreadyDispatchedToNewTouchTarget:为了防止多次触发子view的dispatchTouchEvent,用于记录的一个成员变量

总结

在理解上述流程时,是不是发现了我们在处理滑动冲突时,经常会遇到的几个方法
dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent
结合源码阅读,我们可以对这几个函数有更深入的理解


@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    Log.e("whqq", "父类dispatchTouchEvent" );
    boolean result = super.dispatchTouchEvent(ev);
    Log.e("whqq", "父类dispatchTouchEvent结果" + result);
    return result;
}

@Override
public boolean onInterceptTouchEvent(MotionEvent e) {
    Log.e("whqq", "父类onInterceptTouchEvent" );
    boolean result = super.onInterceptTouchEvent(e);
    Log.e("whqq", "父类onInterceptTouchEvent结果" + result);
    return result;
}


@Override
public boolean onTouchEvent(MotionEvent e) {
    Log.e("whqq", "父类onTouchEvent" );
    boolean result = super.onTouchEvent(e);
    Log.e("whqq", "父类onTouchEvent结果" + result);
    return result;
}

思考

  • 在解决滑动冲突问题时,最应该复写的是onTouchEvent方法,去处理滑动事件,
  • 若复写了onInterceptTouchEvent方法,也要注意,既然选择拦截了,就代表要么你已经确认要处理该事件在onTouchEvent中返回true,要么代表问题是因为事件交给子view处理引入了问题,才复写onInterceptTouchEvent事件传递给子view,不然都是错误复写
  • 若复写了dispatchTouchEvent方法,记的调用super
阅读全文
下载说明:
1、本站所有资源均从互联网上收集整理而来,仅供学习交流之用,因此不包含技术服务请大家谅解!
2、本站不提供任何实质性的付费和支付资源,所有需要积分下载的资源均为网站运营赞助费用或者线下劳务费用!
3、本站所有资源仅用于学习及研究使用,您必须在下载后的24小时内删除所下载资源,切勿用于商业用途,否则由此引发的法律纠纷及连带责任本站和发布者概不承担!
4、本站站内提供的所有可下载资源,本站保证未做任何负面改动(不包含修复bug和完善功能等正面优化或二次开发),但本站不保证资源的准确性、安全性和完整性,用户下载后自行斟酌,我们以交流学习为目的,并不是所有的源码都100%无错或无bug!如有链接无法下载、失效或广告,请联系客服处理!
5、本站资源除标明原创外均来自网络整理,版权归原作者或本站特约原创作者所有,如侵犯到您的合法权益,请立即告知本站,本站将及时予与删除并致以最深的歉意!
6、如果您也有好的资源或教程,您可以投稿发布,成功分享后有站币奖励和额外收入!
7、如果您喜欢该资源,请支持官方正版资源,以得到更好的正版服务!
8、请您认真阅读上述内容,注册本站用户或下载本站资源即您同意上述内容!
原文链接:https://www.dandroid.cn/archives/22373,转载请注明出处。
0

评论0

显示验证码
没有账号?注册  忘记密码?