美文网首页Android自定义View
View触摸事件源码分析

View触摸事件源码分析

作者: 陆元伟 | 来源:发表于2018-03-19 12:00 被阅读17次

    View的dispatchTouchEvent方法的省略版源码

    public boolean dispatchTouchEvent(MotionEvent event) {    

    //dispatchTouchEvent的返回值    

    boolean result = false;   

     //...balabala省略前面部分代码   

     if (onFilterTouchEventForSecurity(event)) {        

    if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {            

    result = true;        }       

     //noinspection SimplifiableIfStatement        

    ListenerInfo li = mListenerInfo;

    //判断是否有onTouchListener监听,如果有,执行listener的

    //onTouch方法,        

    if (li != null && li.mOnTouchListener != null   && (mViewFlags & ENABLED_MASK) == ENABLED                && li.mOnTouchListener.onTouch(this, event)) {           

     result = true;       

     } //如果onTouch方法返回false,继续执行onTouchEvent方法       

     if (!result && onTouchEvent(event)) {            

    result = true;        }    }    

    return result;}

    通过上面的代码可以得出以下结论:

    [if !supportLists]1 [endif]如果View有onTouchListener,则onTouch方法优于onTouchEvent方法先执行

    [if !supportLists]2 [endif]如果View的onTouchListener的onTouch方法返回了true,则onTouchEvent方法不会执行

    [if !supportLists]3 [endif]如果View有onTouchListener,并且onTouch方法返回了true,dispatchTouchEvent方法返回true。如果onTouch返回了false或者没有onToucheListener,则dispatchTouchEvent方法和onTouchEvent的返回值一致。

    ViewGroup dispatchTouchEvent方法的省略版源码

    @Override

    public boolean dispatchTouchEvent(MotionEvent ev) {   

     //dispatchTouchEvent方法的返回值   

     boolean handled = false;   

     if (onFilterTouchEventForSecurity(ev)) {        

    // Check for interception.是否拦截标志        

    final boolean intercepted;       

     if (actionMasked == MotionEvent.ACTION_DOWN                || mFirstTouchTarget != null) {           

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

    if (!disallowIntercept) {

    //调用拦截方法,该方法默认返回false               

     intercepted = onInterceptTouchEvent(ev);                

    ev.setAction(action); // restore action in case it was chang           

     } else {               

     intercepted = false;           

     }        } else {            

    intercepted = true;        }       

     //如果没有拦截       

     if (!intercepted) {                       

    final View[] children = mChildren;                   

     for (int i = childrenCount - 1; i >= 0; i--) {                       

     final int childIndex = getAndVerifyPreorderedIndex(  childrenCount, i, customOrder);                       

    final View child = getAndVerifyPreorderedView( preorderedList, children, childIndex);                              

     if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign))

    return handled;

    }

    dispatchTransformedTouchEvent方法

    /** * Transforms a motion event into the coordinate space of a particular child view, 

    * filters out irrelevant pointer ids, and overrides its action if necessary. 

    * If child is null, assumes the MotionEvent will be sent to this ViewGroup instead. */

    private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,        View child, int desiredPointerIdBits) {   

     final boolean handled;   

     // Canceling motions is a special case.  We don't need to perform any transformations   

     // or filtering.  The important part is the action, not the contents.    

    final int oldAction = event.getAction();      

     // Calculate the number of pointers to deliver.    

    final int oldPointerIdBits = event.getPointerIdBits();    

    final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;           

     // Perform any necessary transformations and dispatch.   

    if (child == null) {

    //调用View的dispatchTouchEvent方法,View的dispatchTouchEvent方法如果执行则看前面分析View的dispatchTouchEvent方法源码        

    handled = super.dispatchTouchEvent(transformedEvent);   

     } else {       

     final float offsetX = mScrollX - child.mLeft;       

     final float offsetY = mScrollY - child.mTop;       

     transformedEvent.offsetLocation(offsetX, offsetY);       

     if (! child.hasIdentityMatrix()) {           

     transformedEvent.transform(child.getInverseMatrix());       

     }

    //调用子view的dispatchTouchEvent方法       

      handled = child.dispatchTouchEvent(transformedEvent);   

     }    // Done.    transformedEvent.recycle();    

    return handled;}

    ViewGroup的dispatchTouchEvent方法比较复杂,主要是它是个控件容器,可能有很多子控件,要考虑事件如何传递,传递给哪个子控件,该不该传递给子控件等等多种情况。

    结论:

    [if !supportLists]1 [endif]ViewGroup里面新增了一个拦截方法onInterceptTouchEvent,控制该事件要不要传递给它的子控件。

    [if !supportLists]2 [endif]若onInterceptTouchEvent方法返回true,则子控件接收不到事件即dispatchTouchEvent方法不会调用,而会调用父类View 的dispatchTouchEvent方法。若返回false,则会子控件的dispatchTouchEvent方法

    现测试检验查看

    MyView继承View,里面添加onTouchListener监听.MyViewGroup 继承ViewGroup.在相应的方法里面添加打印语句,返回值全都默认值,经上面分析可知道的,猜想得知执行顺序MyViewGroup.dispatchTouchEvent->MyViewGroup.onInterceptTouchEvent->MyView.dispatchTouchEvent->MyView.onTouch->MyView.onTouchEvent

    打印结果如下图,符合猜想;

    1小结:

    View和ViewGroup的dispatchTouchEvent默认返回都为false;

    ViewGroup默认onInterceptTouchEvent返回也为false;

    action为0表示为当前事件为ACTION_DOWN事件;

    but,MOVE和UP事件跑哪去了....慢慢往下看;

    其他都是默认值 现在改变View的监听onTouch返回值为true;

    这时候View的onTouchEvent应该就得不到执行了;

    打印结果如下图,发现打印日志就多了很多,action为2表示ACTION_MOVE事件,等于1表示ACTION_UP事件;

    2小结:

    经上面分享源码得知,此时MyView的dispatchTouchEvent方法返回的也是true,由日志也可看出;

    由打印日志可以看出,MyViewGroup的dispatchTouchEvent方法也返回了true;

    可以看出MyViewGroup的dispatchTouchEvent方法返回了true,才有了后续的ACTION_MOVE和ACTION_UP事件;

    注意看,此时MyViewGroup的onTouchEvent方法也不会执行了....Why...继续往下慢慢分析

    其他都是默认值改变View的dispatchTouchEvent方法返回true;

    这个和上面那个日志的区别就是会执行View的onTouchEvent方法,其他的都是一样的;

    打印如下;

    其他都是默认值,View的onTouchEvent返回true;

    日志和上面的差不多,差别就是前面View的onTouchEvent返回false,这里都是true;

    没有设置dispatchTouchEvent的返回值,但是它返回的是true;

    前面分析过了,这种情况dispatchTouchEvent的返回值和onTouchEvent是一样的;

    依然ViewGroup的onTouchEvent方法没有执行;

    3小结

    通过小结1和后面的对比发现,如果View的dispatchTouchEvent返回值为true,则ViewGroup的onTouchEvent就不会执行了;

    而想要View的dispatchTouchEvent返回值为true,除了复写该方法在dispatchTouchEvent里面返回true之外,在则可以设置一个监听onTouchListener返回true,或者在onTouchEvent里面返回true;

    平常我们用的最多的是复写onTouchEvent方法;

    再看View onTouchEvent方法源码;

    public boolean onTouchEvent(MotionEvent event) {        

    //前面省略balabala代码....

    final int action = event.getAction();

    //还可以设置touch代理            

     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) !=                 if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {                            

     if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {                        

    // This is a tap, so remove the longpress check                       

     removeLongPressCallback();                       

     if (!focusTaken) {                           

     if (mPerformClick == null) {                                

    mPerformClick = new PerformClick();                            }

    //调用onClickListener回调                           

     if (!post(mPerformClick)) {                                

    performClick();                            }                        }                    }                      

     break;            

    case MotionEvent.ACTION_DOWN:               

     mHasPerformedLongPress = false;               

    if (isInScrollingContainer) {                 

     //检测是否长按                  

      postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());               

     } else {                  

      // Not inside a scrolling container, so show the feedback right away                   

     setPressed(true, x, y);

    //检测是否长按                    

    checkForLongClick(0, x, y);                }                

    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;        }

    //这里直接返回true了       

     return true;    }   

     return false;}

    初看一下既然返回false了,那么switch里面应该就不会走了;

    突发奇想,既然switch都不会走了,难道onClickListener也不会走了???

    MyView和MyViewGroup所有返回值都默认,添加如下代码,设置了onClick监听

    结果Toast神奇的弹出来了!!!

    和上面预期的不一样啊,既然switch都不走了,那为啥onClick方法会被调用?

    打印如下;

    View的onTouchEvent,dispatchTouchEvent返回值为true;

    ViewGroup的dispatchTouchEvent返回为true了。

    就是一个加setOnclickListener和不加的区别;

    点进去这个方法看;

    前面有个setClickable顿时恍然大悟,如梦出醒;

    再看onTouchEvent方法

    也就是默认这个值是false,如果这个值是false,那onTouchEvent方法也就直接返回false.这也是导致了dispatchTouchEvent方法返回了false;

    google工程师用一个标志viewFlags判断当前是否可点击或者长按或者是contextClickable;

    点击和长按是在onTouchEvent里面处理的,这个onContextClickListener在onTouchEvent里面没看到被调用,用的也比较少,暂时略过,以后发现了用处再来补充

    [if !supportLists]4 [endif]小结

    在不设置setClickable,或者setLongClickable情况下,onTouchEvent返回false;

    若设置setClickable,或者setLongClickable为true,则onTouchEvent也返回true;

    可以通过代码设置,也可以在布局文件里面直接设置Clickable和LongClickable;

    继续测试,所有值默认,ViewGroup的dispatchTouchEvent返回true

    日志打印如下图

    5小结

    由于View的onTouchEvent返回了false,所以ViewGroup的onTouchEvent才会被调用;

    由于View的onTouchEvent在ACTION_DOWN就返回了false,所以后续的ACTION_MOVE和ACTION_UP也就没有接收到;

    即时ViewGroup的onTouchEvent在ACTION_DOWN返回了true,他的后续MOVE和UP也能接收到;

    现改成如下方式;

    输出日志如下:

    [if !supportLists]6 [endif]小结

    ViewGroup的dispatchTouchEvent方法里面只要ACTION_DOWN返回了true,其他的MOVE和UP事件不受影响

    继续测试,onInterceptTouchEven返回true,代码如下

    由前面分析ViewGroup源码得知,此时View接收不到任何事件的;

    输入日志如下

    View里面onTouchEvent返回true

    ViewGroup onInterceptTouchEvent的MOVE事件里面返回true

    打印日志如下

    这个时候View onTouchEvent方法只接收到了DOWN 和CANCEL事件;

    ViewGroup接收到了MOVE和UP事件

    小结:

    ViewGroup若在MOVE方法返回true,子控件只能接收到DOWN和CANCEL事件。ViewGroup可以接收到MOVE和UP事件

    现在在ViewGroup的UP事件上拦截

    输出日志如下

    View接收到onTouchEvent 方法接收到DOWN和MOVE和CANCEL事件;

    ViewGroup的dispatchTouEvent里面可以接收DOWN,MOVE,和UP事件,onTouchEvent没有执行;

    总结

    [if !supportLists]1 [endif]如果一个控件能够接受到事件则最新执行的方法肯定是dispatchTouEvent方法;

    [if !supportLists]2 [endif]如果一个控件设置了onTouchLister,则onTouchListener优于onTouchEvent方法执行;

    3有onTouchListener的情况下,如果onTouchListener返回true,则dispatchTouEvent的返回值也为true,并且onTouchEvent方法不会再执行;

    4没onTouchListener或者onTouchListener返回false的情况下,dispatchTouEvent的返回值和onTouchEvent是一样的;

    5 ACTION_DOWN,ACTION_MOVE,ACTION_UP事件是一系列连续的,如果某个事件返回了false,则后续事件也不再调用

    6父容器控件的onInterceptTouchEvent方法也是如此,如果某个事件返回了true,则子控件会接收到ACTION_CANCEL事件,并且子控件后续事件也不在会执行。

    7字控件的dispatchTouEvent方法如果能够执行,并且返回true.则父容器控件的onTouchEvent方法就不会执行。

    参考网址 http://www.bubuko.com/infodetail-1466509.html

    相关文章

      网友评论

        本文标题:View触摸事件源码分析

        本文链接:https://www.haomeiwen.com/subject/lqwuqftx.html