• Android 事件分发机制


     

           点击事件的分发过程其实是对MotionEvent事件分发过程,当一个MotionEvent产生以后,系统需要把这个事件传递给一个具体的View,而这个传递过程就是分发过程。点击事件的分发由三个重要的方法共同完成:dispatchTouchEvent,onInterceptTOuchEvent,onTouchEvent。

    View里,有两个回调函数 :

    public boolean dispatchTouchEvent(MotionEvent ev);
    public boolean onTouchEvent(MotionEvent ev);

    ViewGroup里,有三个回调函数 :

    public boolean dispatchTouchEvent(MotionEvent ev);
    public boolean onInterceptTouchEvent(MotionEvent ev);
    public boolean onTouchEvent(MotionEvent ev);

    在Activity里,有两个回调函数 :

    public boolean dispatchTouchEvent(MotionEvent ev);
    public boolean onTouchEvent(MotionEvent ev);

    Android中默认情况下事件传递是由最终的view的接收到,传递过程是从父布局到子布局,也就是从Activity到ViewGroup到View的过程,默认情况,ViewGroup起到的是透传作用。

             触摸事件是一连串ACTION_DOWN,ACTION_MOVE..MOVE…MOVE、最后ACTION_UP,触摸事件还有ACTION_CANCEL事件。事件都是从ACTION_DOWN开始的,Activity的dispatchTouchEvent()首先接收到ACTION_DOWN,执行super.dispatchTouchEvent(ev),事件向下分发。

           dispatchTouchEvent()返回true,后续事件(ACTION_MOVE、ACTION_UP)会再传递,如果返回false,dispatchTouchEvent()就接收不到ACTION_UP、ACTION_MOVE

    1 不消费ACTION_DOWN,后续收不到ACTION_MOVE、ACTION_UP

    View中没有拦截器,只能调用onTouchEvent选择消费或者不消费该事件。上图中View的onTouchEvent方法返回false,一直将false返回最顶层的ViewGrouop,该事件后续的ACTION_MOVE,ACTION_UP都不会再调用。

    注: 如果View是可点击的(Button),onTouchEvent默认返回True,ImageView 则默认返回false。

    2 消费ACTION_DOWN

    后续ACTION_MOVE 和 UP 在不被拦截的情况下都会找到这个View

    3 ACTION_MOVE 和 UP 被上层拦截

    4 全部被上层拦截(包括ACTION_DOWN,ACTION_MOVE,ACTION_UP)

    android中的Touch事件都是从ACTION_DOWN开始的:

    单手指操作:ACTION_DOWN---ACTION_MOVE----ACTION_UP

    多手指操作:ACTION_DOWN---ACTION_POINTER_DOWN---ACTION_MOVE--ACTION_POINTER_UP---ACTION_UP.

    参考:http://www.eoeandroid.com/thread-319301-1-1.html?_dsign=495a5374

    5 源码解析

    1/ dispatchTouchEvent 用来进行事件分发,如果事件能够传递到当前的View,那么此方法一定会被调用,返回结果受当前View的OnTouchEvent和下级View的,onInterceptTOuchEvent方法影响,表示是否消耗当前事件。

    /**
         * Pass the touch screen motion event down to the target view, or this
         * view if it is the target.
         *
         * @param event The motion event to be dispatched.
         * @return True if the event was handled by the view, false otherwise.
         */
        public boolean dispatchTouchEvent(MotionEvent event)

    2/ onInterceptTouchEvent是ViewGroup提供的方法,默认返回false,返回true表示拦截。

     * @param ev The motion event being dispatched down the hierarchy.
         * @return Return true to steal motion events from the children and have
         * them dispatched to this ViewGroup through onTouchEvent().
         * The current target will receive an
    ACTION_CANCEL
     event, and no further
         * messages will be delivered here.
         */
        public boolean onInterceptTouchEvent(MotionEvent ev) {
            if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
                    && ev.getAction() == MotionEvent.ACTION_DOWN
                    && ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
                    && isOnScrollbarThumb(ev.getX(), ev.getY())) {
                return true;
            }
            return false;
        }

    3/ onTouchEvent  在 dispatchTouchEvent中被调用,用来处理点击事件,返回结果表示是否消耗此事件。如果不消耗,在同一事件序列中,当前View无法再次接受到事件。

    6 事件分发与onClick/onTouch 的关系

    为同一个button同时设置click和touch的Listener

    button.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View v) {
            Log.d("TAG", "onClick execute");
        }
    });
    button.setOnTouchListener(new OnTouchListener() {
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            Log.d("TAG", "onTouch execute, action " + event.getAction());
            
    return false;
        }
    });
    

    当点击这个Button时的log如下:

    可以看到,onTouch是优先于onClick执行的,并且onTouch执行了两次,一次是ACTION_DOWN,一次是ACTION_UP(你还可能会有多次ACTION_MOVE的执行,如果你手抖了一下)。因此事件传递的顺序是先经过onTouch,再传递到onClick

    问题1 : 当把onTouch的方法返回值改成true时,则onClick不再执行

    原因:  当事件分发到该Button时,会调用它的onDispatchTouchEvent方法,该方法源码中大致有一个这样的判断:如果onTouch方法返回true时,表示onTouch消耗了改事件直接返回true,onTouchEvent方法则不再被调用(当ACTION_UP事件传递进来时,onClick在该方法中被调用,所以onClick也不会被调用)

    public boolean dispatchTouchEvent(MotionEvent event) {
        if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
                mOnTouchListener.onTouch(this, event)) {
            return true;
        }
        return onTouchEvent(event);
    }

    注: 不同版本的可能上述代码实现不相同,但是思想一致。

    问题2:onTouch返回false时,在ACTION_DOWN事件中onDispatchTouchEvent应该返回false,后续的ACTION_UP应该不会被调用,为什么还有log。

    原因: 分析onTouchEvent源码,如果view是可点击的,则默认返回true消耗该事件。如果view是不可点击的则返回false,例如ImageView。所以对于BUTTON第一次ACTION_DOWN的onDispatchTouchEvent返回的是true,而对于ImageView返回值为false,它的后续ACTION_MOVE/ACTION_UP不会再被调用。

      1   public boolean onTouchEvent(MotionEvent event) {
      2         final float x = event.getX();
      3         final float y = event.getY();
      4         final int viewFlags = mViewFlags;
      5         final int action = event.getAction();
      6 
      7         if ((viewFlags & ENABLED_MASK) == DISABLED) {
      8             if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
      9                 setPressed(false);
     10             }
     11             // A disabled view that is clickable still consumes the touch
     12             // events, it just doesn't respond to them.
     13             return (((viewFlags & CLICKABLE) == CLICKABLE
     14                     || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
     15                     || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE);
     16         }
     17         if (mTouchDelegate != null) {
     18             if (mTouchDelegate.onTouchEvent(event)) {
     19                 return true;
     20             }
     21         }
     22 
     23         if (((viewFlags & CLICKABLE) == CLICKABLE ||
     24                 (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
     25                 (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
     26             switch (action) {
     27                 case MotionEvent.ACTION_UP:
     28                     boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
     29                     if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
     30                         // take focus if we don't have it already and we should in
     31                         // touch mode.
     32                         boolean focusTaken = false;
     33                         if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
     34                             focusTaken = requestFocus();
     35                         }
     36 
     37                         if (prepressed) {
     38                             // The button is being released before we actually
     39                             // showed it as pressed.  Make it show the pressed
     40                             // state now (before scheduling the click) to ensure
     41                             // the user sees it.
     42                             setPressed(true, x, y);
     43                        }
     44 
     45                         if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
     46                             // This is a tap, so remove the longpress check
     47                             removeLongPressCallback();
     48 
     49                             // Only perform take click actions if we were in the pressed state
     50                             if (!focusTaken) {
     51                                 // Use a Runnable and post this rather than calling
     52                                 // performClick directly. This lets other visual state
     53                                 // of the view update before click actions start.
     54                                 if (mPerformClick == null) {
     55                                     mPerformClick = new PerformClick();
     56                                 }
     57                                 if (!post(mPerformClick)) {
     58                                     performClick();
     59                                 }
     60                             }
     61                         }
     62 
     63                         if (mUnsetPressedState == null) {
     64                             mUnsetPressedState = new UnsetPressedState();
     65                         }
     66 
     67                         if (prepressed) {
     68                             postDelayed(mUnsetPressedState,
     69                                     ViewConfiguration.getPressedStateDuration());
     70                         } else if (!post(mUnsetPressedState)) {
     71                             // If the post failed, unpress right now
     72                             mUnsetPressedState.run();
     73                         }
     74 
     75                         removeTapCallback();
     76                     }
     77                     mIgnoreNextUpEvent = false;
     78                     break;
     79 
     80                 case MotionEvent.ACTION_DOWN:
     81                     mHasPerformedLongPress = false;
     82 
     83                     if (performButtonActionOnTouchDown(event)) {
     84                         break;
     85                     }
     86 
     87                     // Walk up the hierarchy to determine if we're inside a scrolling container.
     88                     boolean isInScrollingContainer = isInScrollingContainer();
     89 
     90                     // For views inside a scrolling container, delay the pressed feedback for
     91                     // a short period in case this is a scroll.
     92                     if (isInScrollingContainer) {
     93                         mPrivateFlags |= PFLAG_PREPRESSED;
     94                         if (mPendingCheckForTap == null) {
     95                             mPendingCheckForTap = new CheckForTap();
     96                         }
     97                         mPendingCheckForTap.x = event.getX();
     98                         mPendingCheckForTap.y = event.getY();
     99                         postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
    100                     } else {
    101                         // Not inside a scrolling container, so show the feedback right away
    102                         setPressed(true, x, y);
    103                         checkForLongClick(0, x, y);
    104                     }
    105                     break;
    106 
    107                 case MotionEvent.ACTION_CANCEL:
    108                     setPressed(false);
    109                     removeTapCallback();
    110                     removeLongPressCallback();
    111                     mInContextButtonPress = false;
    112                     mHasPerformedLongPress = false;
    113                     mIgnoreNextUpEvent = false;
    114                     break;
    115 
    116                 case MotionEvent.ACTION_MOVE:
    117                     drawableHotspotChanged(x, y);
    118 
    119                     // Be lenient about moving outside of buttons
    120                     if (!pointInView(x, y, mTouchSlop)) {
    121                         // Outside button
    122                         removeTapCallback();
    123                         if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
    124                             // Remove any future long press/tap checks
    125                             removeLongPressCallback();
    126 
    127                             setPressed(false);
    128                         }
    129                     }
    130                     break;
    131             }
    132 
    133             return true;
    134         }
    135 
    136         return false;
    137     }
    View Code

    参考:http://blog.csdn.net/guolin_blog/article/details/9097463

    7 ViewGroup的事件拦截

          一般情况下,如果Layout定义了onTouch事件,同时在Layout中添加了两个按钮。当点击按钮时,onTouch事件不会被调用(ViewGroup中源码可知,调用子View中Button的dispatchTouchEvent返回true,后面的onTouch事件不再被调用)。 但是点击除按钮之外的区域则onTouch事件会被调用。

         当重写Layout中的onIntercreptTouchEvent函数,返回true时,则事件不会被传递到子View,直接调用Viewgroup的onTouchEvent处理事件,不管点击哪里,它的onTouch都会被执行。

    http://blog.csdn.net/guolin_blog/article/details/9153747

    https://mp.weixin.qq.com/s/5zrZOVQlV6LAOgpD9DF35g

    总结:

        1、 一个事件序列,ACTIVON_DOWN,ACTION_MOVE,ACTION_UP。 当DOWN事件时顶层ViewGroup的dispatchTouchEvent返回false时,后续事件都不再执行。

       2、可点击View的onTouchEvent默认返回true,其它返回false

       3、ViewGroup中dispatchTouchEvent和View中的该函数实现不同,View中该函数的onTouch都会执行,如果返回false,再执行onTouchEvnet。 ViewGroup中,如果子View消耗了这次事件则它的onTouch事件不会被执行。 

  • 相关阅读:
    报表引擎API开发入门— EJB程序数据源
    真正意义上下一代 Windows Embedded:有关 Windows 10 "Athens" 的事
    如何将网格式报表打印成其它样式
    报表引擎API开发入门—带参程序数据集
    wifi漫游测试过程
    前端,Java,产品经理,微信小程序,Python等资源合集大放送
    bootstrap+fileinput插件实现可预览上传照片功能
    Jquery前端分页插件pagination同步加载和异步加载
    Jquery前端分页插件pagination使用
    最简单的混合开发教程资料汇总
  • 原文地址:https://www.cnblogs.com/NeilZhang/p/8145959.html
Copyright © 2020-2023  润新知