zoukankan      html  css  js  c++  java
  • view之onInterceptTouchEvent和onTouchEvent调用关系详解

     http://blog.csdn.net/lvxiangan/article/details/9309927

    老实说,这两个小东东实在是太麻烦了,很不好懂,我自己那api文档都头晕,在网上找到很多资料,才知道是怎么回事,这里总结一下,记住这个原则就会很清楚了:

    理解:

    1.在viewGroup里含有view时候,一个事件的序列: down-->move....move..... up 事件。

    2. 在一个事件序列中,如果一个子view的onTouchEvent返回true, 下一个move事件会不会走其父view的onIterceptTouchEvent()方法?

      答案:会的,move事件走onInterceptTounchEvent 是否返回--返回true,抢夺子view的move事件......。

            --返回false,走子view的onTouchEvent();

    本帖最后由 sun.shine 于 2013-3-22 18:17 编辑
    
    1.首先明白一个常识:View 没有onInterceptTouchEvent事件,而ViewGroup这三个事件都有,是viewgroup继承View之后才加了一个方法叫onIntercepTouchEvent。
    从字面意思可以看出,onInterceptTouchEvent是拦截器,用来拦截事件用的,dispatchTouchEvent是用来分发事件的,onTouchEvent是用来处理事件的。
    大家不难看出,应该是先走dispatchTouchEvent然后走onTouchEvent。那OnInterceptTouchEven的调用时机是什么时候呢?
    为了更好的理解这三个事件,我们从简单到复杂,先从一个子view,一个viewgroup,然后viewgroup里有子view。 2.针对一个View来讲,事件是先走该View的dispatchTouchEvent,然后再走onTouchEvent(也有可能不走)。 什么时候不会走onTouchEvent呢? 当重写dispatchTouchEvent,不走super.dispatchTouchEvent直接返回false,它就不会走onTouchEvent。 当然这样做是违反android架构常理的,一般的dispatchTouchEvent是不建议重写的。不过通过这个案例我们可以总结出这么一个结论. 在事件到达view的时候,先走dispatchTouchEvent,在系统的dispatchTouchEvent中它会调用该view的Ontouch方法,
    如果此onTouch方法的down事件里返回true,则dispatchTouchEvent方法也返回true,且把以后的move事件,up事件都传给onTouch。
    之后的move事件及up事件的返回值,onTouch返回什么dispatchTouchEvent也返回什么。 相反如果传第一个down事件给ontouch的时候,ontouch返回的是false,从此事件不再会传过来,也就是不会走dispatchTouchEvent。更不会走ontouchevent。
    3.针对一个ViewGroup来讲(没有子view的时候): 事件的走向是dispatchTouchEvent->onInterceptTouchEvent->onTouchEvent 我们会发现它们的逻辑跟view 的没什么两样,只是在走down事件的时候onInterceptTouchEvent会在中间,而这里不管onInterceptTouchEvent返回什么
    都不会干扰它像2.形容的那样运行,难道onInterceptTouchEvent这个方法没用?
    4.当Viewgroup里有子view的时候 down事件走向:viewgroup.dispatchTouchEvent->viewgroup.onInterceptTouchEvent
    ->如果返回true->viewgroup.onTouch---------------------- -分支1
    |->如果返回false->view.dispatchTouchEvent----------------分支2 分支1:之后的move或up事件的走向是:viewgroup.dispatchTouchEvent->viewgroup.ontouch 这里不管ontouch返回的是什么都是这个走向 分支2:down事件到了view.dispatchTouchEvent->view.onTouch->返回true->-----分支3 |->返回false->viewgroup.ontouch->返回true->move,up等事件viewgroup.dispatchTouchEvent-> viewgroup.ontouch |->返回false,则该viewgroup不会再收到后续事件了 分支3:子view的onTouch返回true了,表示子view能接受该事件,今后的事件走向是 Move: viewgroup.dispatchTouchEvent->viewgroup.onInterceptTouchEvent返回? 如果返回的是false,以后的move,up都这么走viewgroup.dispatchTouchEvent->viewgroup.onInterceptTouchEvent->view.dispatchTouchEvent->view.ontouch; 如果返回的是true,抢夺子view的move事件。 接下来的走向是:强制传Cancel事件和UP事件给view,view.dispatchTouchEvent->view.ontouch(无视它返回什么)
    ->然后把Move事件留给viewgroup:viewgroup.dispatchTouchEvent->viewgroup.ontouch。
    这个现象大家应该在listview或是scrollview里见过,就是当用户在scrollview里按住一个按钮,发现按钮做了相应反应(按钮高亮了),
    但当按住不放拖它时,发现界面在滚动,这就是因为onInterceptTouchEvent抢事件了!

    ViewGroup里的onInterceptTouchEvent默认值是false,这样才能把事件传给View里的onTouchEvent.

    ViewGroup里的onTouchEvent默认值是false。

    View里的onTouchEvent返回默认值是true.这样才能执行多次touch事件。

    概念介绍

    1、onInterceptTouchEvent()是用于处理事件(类似于预处理,当然也可以不处理)并改变事件的传递方向,也就是决定是否允许Touch事件继续向下(子控件)传递,一但返回True(代表事件在当前的viewGroup中会被处理),则向下传递之路被截断(所有子控件将没有机会参与Touch事件),同时把事件传递给当前的控件的onTouchEvent()处理;

    2、onTouchEvent() 用于处理事件,返回值决定当前控件是否消费了这个事件,也就是说在当前控件在处理完Touch事件后,是否还允许Touch事件继续向上(父控件)传递,一但返回True,则父控件不用操心自己来处理Touch事件。

    返回false,则向上传递给父控件

    1、onInterceptTouchEvent()是用于处理事件(重点onInterceptTouchEvent这个事件是从父控件开始往子控件传的,直到有拦截 或者到没有这个事件的view,然后就往回从子到父控件,这次是onTouch的)(类似于预处理,当然也可以不处理)并改变事件的传递方向,

    也就是决定是否允许Touch事件继续向下(子控件)传递,一但返回True(代表事件在当前的viewGroup中会被处理),则向下传递之路被截断(所有子控件将没有机会参与Touch事件),同时把事件传递给当前的控件的onTouchEvent()处理;

    返回false,则把事件交给子控件的dispatchTouchEvent()

    2、onTouchEvent()用于处理事件(重点onTouch这个事件是从子控件回传到父控件的,一层层向下传),返回值决定当前控件是否消费(consume)了这个事件,也就是说在当前控件在处理完Touch事件后,是否还允许Touch事件继续向上(父控件)传递。

    返回false,则向上传递给父控件,详细一点就是这个touch事件就给了父控件,那么后面的up事件就是到这里touch触发,不会在传给它的子控件。

           如果父控件依然是false,那touch的处理就给到父控件的父控件,那么up的事件处理都在父控件的父控件,不会触发下面的。

    返回true,如果是子控件返回true,那么它的touch事件都在这里处理,父控件是处理不了,因为它收不到子控件传给他的touch,被子控件给拦截了。(这里啰嗦了这么多就是为了加深记忆,这个两个事件理解起来都这么麻烦了,更何况去记, )

    (注:可能你会觉得是否消费了有关系吗,反正我已经针对事件编写了处理代码?答案是有区别!

    比如ACTION_MOVE或者ACTION_UP发生的前提是一定曾经发生了ACTION_DOWN,如果你没有消费ACTION_DOWN,那么系统会认为ACTION_DOWN没有发生过,所以ACTION_MOVE或者ACTION_UP就不能被捕获。)

    详细介绍

    onInterceptTouchEvent()是ViewGroup的一个方法,目的是在系统向该ViewGroup及其各个childView触发onTouchEvent()之前对相关事件进行一次拦截,

    Android这么设计的想法也很好理解,由于ViewGroup会包含若干childView,因此需要能够统一监控各种touch事件的机会,因此纯粹的不能包含子view的控件是没有这个方法的,如LinearLayout就有,TextView就没有。

     如果在ViewGroup里覆写了该方法,那么就可以对各种touch事件加以拦截。但是如何拦截,是否所有的touch事件都需要拦截则是比较复杂的,touch事件在onInterceptTouchEvent()和onTouchEvent以及各个childView间的传递机制完全取决于onInterceptTouchEvent()和onTouchEvent()的返回值。

    并且,针对down事件处理的返回值, 直接影响到后续move和up事件的接收和传递。

    关于返回值的问题,基本规则很清楚,如果return true,那么表示该方法消费了此次事件,如果return false,那么表示该方法并未处理完全,该事件仍然需要以某种方式传递下去继续等待处理。

      

    onInterceptTouchEvent()是ViewGroup的一个方法,目的是在系统向该ViewGroup及其各个childView触发onTouchEvent()之前对相关事件进行一次拦截.

    1. down事件首先会传递到onInterceptTouchEvent()方法

    2. 如果该ViewGroup的onInterceptTouchEvent()在接收到down事件处理完成之后return false,那么后续的move, up等事件将继续会先传递给该ViewGroup,之后才和down事件一样传递给最终的目标view的 onTouchEvent()处理。

    3. 如果该ViewGroup的onInterceptTouchEvent()在接收到down事件处理完成之后return true,那么后续的move, up等事件将不再传递给onInterceptTouchEvent(),而是和down事件一样传递给该ViewGroup的onTouchEvent()处理,注意,目标view将接收不到任何事件。

    4. 如果最终需要处理事件的view的onTouchEvent()返回了false,那么该事件将被传递至其上一层次的view的onTouchEvent()处理。

    5. 如果最终需要处理事件的view 的onTouchEvent()返回了true,那么后续事件将可以继续传递给该view的onTouchEvent()处理。 

      仅仅看这个官方文档解释,就能理解清楚这两个函数关系以及用途的绝对是富有经验的framework高手。
      否则,一定需要一个案例来阐释。假设我们有这样一个layout,非常典型的

      1. <com.test.LayoutView1 xmlns:android="http://schemas.android.com/apk/res/android"  
      2.     android:orientation="vertical" android:layout_width="fill_parent"  
      3.     android:layout_height="fill_parent">  
      4.     <com.test.LayoutView2  
      5.         android:orientation="vertical" android:layout_width="fill_parent"  
      6.         android:layout_height="fill_parent" android:gravity="center">  
      7.         <com.test.MyTextView  
      8.             android:layout_width="wrap_content"   android:layout_height="wrap_content"  
      9.       />  
      10.     </com.test.LayoutView2>  
      11. </com.test.LayoutView1>

      用一个示例图来解释这个layout:


      通常外围的layoutview1,layoutview2,只是布局的容器不需要响应触屏的点击事件,仅仅Mytextview需要相应点击。但这只是一般情况,一些特殊的布局可能外围容器也要响应,甚至不让里面的mytextview去响应。更有特殊的情况是,动态更换响应对象。
      那么首先看一下默认的触屏事件的在两个函数之间的传递流程。如下图:

      如果仅仅想让MyTextView来响应触屏事件,让MyTextView的OnTouchEvent返回true,那么事件流就变成如下图,可以看到layoutview1,layoutview2已经不能进入OnTouchEvent:

      另外一种情况,就是外围容器想独自处理触屏事件,那么就应该在相应的onInterceptTouchEvent函数中返回true,表示要截获触屏事件,比如layoutview1作截获处理,处理流变成如下图:

      以此类推,我们可以得到各种具体的情况,整个layout的view类层次中都有机会截获,而且能看出来外围的容器view具有优先截获权。

      当我们去做一些相对来讲具有更复杂的触屏交互效果的应用时候,经常需要动态变更touch event的处理对象,比如launcher待机桌面和主菜单(见下图),从滑动屏幕开始到停止滑动过程当中,只有外围的容器view才可以处理touch event,否则就会误点击上面的应用图标或者widget.

    6. 反之在静止不动的状态下则需要能够响应图标(子view)的touch事件。摘取framework中abslistview代码如下

      public boolean onInterceptTouchEvent(MotionEvent ev) {
      int action = ev.getAction();
      switch (action & MotionEvent.ACTION_MASK) {
      case MotionEvent.ACTION_DOWN: {
      if (touchMode == TOUCH_MODE_FLING) {
      return true; //fling状态,截获touch,因为在滑动状态,不让子view处理
      }
      break;
      }
      case MotionEvent.ACTION_MOVE: {
      switch (mTouchMode) {
      case TOUCH_MODE_DOWN:
      final int pointerIndex = ev.findPointerIndex(mActivePointerId);
      final int y = (int) ev.getY(pointerIndex);
      if (startScrollIfNeeded(y - mMotionY)) {
      return true;//开始滑动状态,截获touch事件,不让子view处理
      }
      break;
      }
      break;
      }
      }

      总结:

      仅仅通过概览性的官方文档是很难理解onInterceptTouchEvent函数的用途的,只有通过演绎这个抽象的规则,配以图文才能获取这个重要的知识。很显然,默认是返回false,不做截获。返回true之后,事件流的后端控件就没有机会处理touch事件了,把默认的事件流中每个处理函数看作一个节点,这个节点只要返回true, 后续的事件就被截止了。

      onInterceptTouchEvent是在ViewGroup里面定义的。Android中的layout布局类一般都是继承此类的。onInterceptTouchEvent是用于拦截手势事件的,每个手势事件都会先调用onInterceptTouchEvent。 
      onInterceptTouchEvent()用于处理事件并改变事件的传递方向。

    返回值为false时事件会传递给子控件的onInterceptTouchEvent();

    返回值为true时事件会传递给当前控件的onTouchEvent(),而不在传递给子控件,这就是所谓的Intercept(截断)。

      onTouchEvent() 用于处理事件,返回值决定当前控件是否消费(consume)了这个事件。可能你要问是否消费了又区别吗,反正我已经针对事件编写了处理代码?答案是有区别!比如ACTION_MOVE或者ACTION_UP发生的前提是一定曾经发生了ACTION_DOWN,如果你没有消费ACTION_DOWN,那么系统会认为ACTION_DOWN没有发生过,所以ACTION_MOVE或者ACTION_UP就不能被捕获。

     阻止事件和分发事件:

    复制代码
    public class MyLinearLayout extends LinearLayout {
    
    
        public MyLinearLayout(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
        /**
         * 中断事件
         */
        @Override
        public boolean onInterceptTouchEvent(MotionEvent ev) {
            return true;
        }
    
        /**
         * 分发事件
         */
        @Override
        public boolean dispatchTouchEvent(MotionEvent ev) {
            return super.dispatchTouchEvent(ev);
        }
        
        /**
         * 实现多个ListView控件同时触发事件
         */
        @Override
        public boolean onTouchEvent(MotionEvent event) {
            
            int width=getWidth()/getChildCount();
            int height = getHeight();
            int count=getChildCount();
            
            float eventX = event.getX();
            
            if (eventX<width){    // 滑动左边的 listView
                event.setLocation(width/2, event.getY());
                getChildAt(0).dispatchTouchEvent(event);//移动位置后,分发事件
                return true;
                
            } else if (eventX > width && eventX < 2 * width) { //滑动中间的 listView  
                float eventY = event.getY();
                if (eventY < height / 2) {
                    event.setLocation(width / 2, event.getY());
                    for (int i = 0; i < count; i++) {
                        View child = getChildAt(i);
                        try {
                            child.dispatchTouchEvent(event);
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                        
                    }
                    return true;
                } else if (eventY > height / 2) {
                    event.setLocation(width / 2, event.getY());
                    try {
                        getChildAt(1).dispatchTouchEvent(event);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    return true;
                }
            }else if (eventX>2*width){
                event.setLocation(width/2, event.getY());
                getChildAt(2).dispatchTouchEvent(event);
                return true;
            }
      
            return true;
        }
        
    }
  • 相关阅读:
    [k8s微服务作业]-day2-Docker基础
    运维常用命令记录
    【莫比乌斯反演】学习笔记
    2021牛客OI赛前集训营-提高组(第一场)
    NOIP 计划 · 模拟赛 #10
    2021牛客OI赛前集训营-提高组(第二场)
    10.5 模拟赛题解报告
    组合数学
    线段树合并
    2021, 9,26 模拟赛
  • 原文地址:https://www.cnblogs.com/awkflf11/p/12635429.html
Copyright © 2011-2022 走看看