Android 源码分析View事件分发过程详解,小白教程
目录
前言
一、MotionEvent类 
二、事件传递对象的顺序
三、事件传递过程的方法
四、源码分析
前言
 
    记得有人这样过说:View的事件传递和分发是个看起来简单、学起来也不难、讲起来却憋死个人、用起来又需要充实的知识和编程经验。没错,事件分发机制确实就只有几个函数而已,看起来好像也不难理解,实际学习也不难。
    那接下来我们一起梳理一下Android的事件分发机制的相关知识吧!
    首先,我们来认识触摸事件封装的一个对象MotionEvent:
 
一、MotionEvent类 
这个类中包装了很多触摸事件,如按下、抬起、滑动等。而且通过这个类可以获取到触摸事件的信息,如x\y坐标值、事件类型、时间等。
1、public final float getRawX() / getRawX()   :屏幕坐标
触摸点在屏幕上的绝对坐标;坐标值相对于屏幕而言。 
2、public final float getY() / getY(int index) / getX() / getX(int index) :视图坐标
触摸点基于该View的坐标值;有参数的方法则会返回某个点的坐标值,无参数的方法返回index为0的点的坐标值;index值范围从0到getPointerCount() - 1。 
3、public final float getAction() / getActionMasked() :事件类型
getAction,4种常用类型:ACTION_DOWN、ACTION_MOVE、ACTION_UP、ACTION_CANCEL。
getActionMasked,多2种:ACTION_POINTER_DOWN、ACTION_POINTER_UP。它们代表是多点触控时有其他手指落下或抬起。某些时候,比如滚动,为了防止抬起落下多根手指时出现跳动,我们是需要检测并计算多点触控的,因此推荐直接用getActionMasked。 
事件序列,如下:
4、 public final void offsetLocation(float deltaX, float deltaY) 
将事件中的坐标值进行位移变换,如滚动。
由于滚动有两种方式,一种是改变子控件的位置,另一种就是利用方法setScrollY(int value) / setScrollX(int value),这两个方法都会影响View类中的mScrollX / mScrollY两个属性,而这两个属性又会影响View在分发事件以及绘制时的行为。
 
二、事件传递对象的顺序
 
Android的UI界面由Activity、ViewGroup、View 及其派生类组成,假如用户触发了一个点击事件后,事件会先传到Activity、再传到ViewGroup、最终再传到 View。事件在UI界面中的传递顺序如下:
    Activity    ->    ViewGroup    ->    View
 
三、事件传递过程的方法
 
1、public boolean dispatchTouchEvent(MotionEvent event)
事件分发,参数是要分发的事件。
这个方法会将事件分发下去,如果返回true表示它或者它的子view消化了这个事件;返回false表示它和它的子view都不消化。
 
2、public boolean onInterceptTouchEvent(MotionEvent ev) 
事件拦截,参数是要拦截的事件。
这个方法会在dispatchTouchEvent内部调用,判断当前 viewGroup 是否拦截了某个事件。如果拦截了某个事件,那么此方法在同一个事件序列中不会再被调用了。ViewGroup默认不拦截事件。
3、public boolean onTouchEvent(MotionEvent event) 
事件处理,参数是要处理的事件。
这个方法也会在 dispatchTouchEvent 内部调用,。判断当前View是否处理事件,如果不处理返回false,则在同一个事件序列中不再接收该事件;View默认都会处理,即返回ture;除非它是不可点击的,即clickable为false。另外,Button的clickable默认为ture,而TextView的clickable默认为false。
 
三者关系
    //伪代码
    public boolean dispatchTouchEvent(MotionEvent ev){
        boolean consume =false;
        if(onInterceptTouchEvent(ev)){
            consume = onTouchEvent(ev);
        }else{
            consume =child.dispatchTouchEvent(ev);
        }
        return consume;
    }
关系解析:
对于ViewGroup来说,点击事件产生后,首先会传递给它,这时它的dispatchTouchEvent会被调用,如果它的onInterceptTouchEvent方法返回true,表示它拦截了当前事件,接着它的onTouchEvent方法就会被调用。否则表示它不拦截当前事件,这时事件就会继续传递给它的子元素,如此反复。
如果View的onTouchEvent方法返回false,表示不消化当前事件,那么它的父容器的onTouchEvent将会被调用,依次类推。如果所有的元素都不处理这个事件,那么这个事件将会被最终传递给Activity处理,即Activity的onTouchEvent方法将会被调用。
 
补充一点:
平时我们会将View设置的OnClickListener的事件监听,其内部onClick方法会被调用。如果这个onTouch的返回值是false,则当前View的onTouchEvent方法会被调用,即返回ture;否则为ture时,那么onTouchEvent将不会被调用,即返回false。即,给View设置的OnClickListener优先级要比onTouchEvent高。
 
四、源码分析
事件传递的对象顺序是从Activity    ->    ViewGroup    ->    View。接下,我们一起探究一下事件分发过程的源码。
1、Activity.dispatchTouchEvent()
     public boolean dispatchTouchEvent(MotionEvent ev) {
     
                // DOWN事件,默认true
                if (ev.getAction() == MotionEvent.ACTION_DOWN) {
     
                    onUserInteraction();
     
                }
     
                if (getWindow().superDispatchTouchEvent(ev)) {
     
                    return true;
     
                }
                return onTouchEvent(ev);
     }
     
     
        @Override
        public boolean superDispatchTouchEvent(MotionEvent event) {
           
            //等于是调用ViewGroup的superDispatchTouchEvent方法,事件从Activity传递到viewGroup
            return mDecor.superDispatchTouchEvent(event);//mDecor:顶层DecorView的实例对象
        }
     
     
        public boolean superDispatchTouchEvent(MotionEvent event) {
     
            return super.dispatchTouchEvent(event);
     
        }
     
      //事件没有子元素处理时,交由Activity处理
      public boolean onTouchEvent(MotionEvent event) {
     
            if (mWindow.shouldCloseOnTouch(this, event)) {
                finish();
                return true;
            }
            
            return false;//至此,事件分发终止
        }
     
      public boolean shouldCloseOnTouch(Context context, MotionEvent event) {
     
          if (mCloseOnTouchOutside && event.getAction() == MotionEvent.ACTION_DOWN
                && isOutOfBounds(context, event) && peekDecorView() != null) {
              return true;// 返回true:说明事件在边界外,即消费事件
          }
        return false;// 返回false:未消费
      }
2、ViewGroup.dispatchTouchEvent()
    public boolean dispatchTouchEvent(MotionEvent ev) { 
        ...
             //disallowIntercept默认是false,是否禁用事件拦截的功能,可通过调用requestDisallowInterceptTouchEvent方法修改
            // 调用onInterceptTouchEvent()是否进行拦截,默认false不拦截
            if (disallowIntercept || !onInterceptTouchEvent(ev)) {  
     
                    ev.setAction(MotionEvent.ACTION_DOWN);  
                    final int scrolledXInt = (int) scrolledXFloat;  
                    final int scrolledYInt = (int) scrolledYFloat;  
                    final View[] children = mChildren;  
                    final int count = mChildrenCount;  
     
                // 遍历ViewGroup下的子View
                for (int i = count - 1; i >= 0; i--) {  
                    final View child = children[i];  
                    if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE  
                            || child.getAnimation() != null) {  
                        child.getHitRect(frame);  
     
                        // 判断当前遍历的View是不是正在点击的View,从而找到当前被点击的View
                        if (frame.contains(scrolledXInt, scrolledYInt)) {  
                            final float xc = scrolledXFloat - child.mLeft;  
                            final float yc = scrolledYFloat - child.mTop;  
                            ev.setLocation(xc, yc);  
                            child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  
     
                           //调用了view的dispatchTouchEvent分发方法
                            if (child.dispatchTouchEvent(ev))  { 
     
                               mMotionTarget = child;  
                               return true; 
     
                            // 如果子view可点击,ViewGroup的dispatchTouchEvent()就要返回true,即直接跳出, 等于子view把ViewGroup的点击事件拦截掉了
     
                                    }  
                                }  
                            }  
                        }  
                    }  
                }  
                boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||  
                        (action == MotionEvent.ACTION_CANCEL);  
                if (isUpOrCancel) {  
                    mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;  
                } 
     
                final View target = mMotionTarget;  
     
            // 若无子View 拦截事件,或主动调用onInterceptTouchEvent方法,返回true进行拦截
            if (target == null) {  
                ev.setLocation(xf, yf);  
                if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {  
                    ev.setAction(MotionEvent.ACTION_CANCEL);  
                    mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  
                }  
                
                return super.dispatchTouchEvent(ev);
     
            } 
     
            ... 
     
    }
     
      public boolean onInterceptTouchEvent(MotionEvent ev) {  
        
        return false;//不拦截(viewGroup默认)
     
      } 
3、View.dispatchTouchEvent()
     public boolean dispatchTouchEvent(MotionEvent event) {  
     
            if (mOnTouchListener != null &&    //是否设置监听
                 (mViewFlags & ENABLED_MASK) == ENABLED &&   //默认enable 为ture
                    mOnTouchListener.onTouch(this, event)) { //onTouch 为ture 
     
                return true;  //分发结束
            } 
            return onTouchEvent(event);  //自己消化,源码分析
      }
     
     
     // 注册了Touch事件,mOnTouchListener就被赋值,不为null
      public void setOnTouchListener(OnTouchListener l) { 
        mOnTouchListener = l;       
      } 
     
      View.setOnTouchListener(new OnTouchListener() {  
            @Override  
            public boolean onTouch(View v, MotionEvent event) {  
         
                return false;  // 使上面if判断不成立,执行onTouchEvent()
            }  
      });
     
     
    public boolean onTouchEvent(MotionEvent event) {  
        final int viewFlags = mViewFlags;  
     
        if ((viewFlags & ENABLED_MASK) == DISABLED) {  
             
            return (((viewFlags & CLICKABLE) == CLICKABLE ||  
                    (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));  
        }  
     
        if (mTouchDelegate != null) {  
            if (mTouchDelegate.onTouchEvent(event)) {  
                return true;  
            }  
        }  
     
        // clickable为ture
        if (((viewFlags & CLICKABLE) == CLICKABLE ||  
                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {  
     
                    switch (event.getAction()) { 
                        // 手势抬起事件
                        case MotionEvent.ACTION_UP:  
                            boolean prepressed = (mPrivateFlags & PREPRESSED) != 0; 
     
                                performClick();  //源码分析
     
                                break;  
     
                        // 手势按下事件
                        case MotionEvent.ACTION_DOWN:  
                            if (mPendingCheckForTap == null) {  
                                mPendingCheckForTap = new CheckForTap();  
                            }  
                            mPrivateFlags |= PREPRESSED;  
                            mHasPerformedLongPress = false;  
                            postDelayed(mPendingCheckForTap,ViewConfiguration.getTapTimeout());  
                            break;  
     
                        // 结束事件
                        case MotionEvent.ACTION_CANCEL:  
                            mPrivateFlags &= ~PRESSED;  
                            refreshDrawableState();  
                            removeTapCallback();  
                            break;
     
                        // 滑动事件
                        case MotionEvent.ACTION_MOVE:  
                            final int x = (int) event.getX();  
                            final int y = (int) event.getY();  
            
                            int slop = mTouchSlop;  
                            if ((x < 0 - slop) || (x >= getWidth() + slop) ||  
                                    (y < 0 - slop) || (y >= getHeight() + slop)) {  
                                // Outside button  
                                removeTapCallback();  
                                if ((mPrivateFlags & PRESSED) != 0) {  
                                    // Remove any future long press/tap checks  
                                    removeLongPressCallback();  
                                    // Need to switch from pressed to not pressed  
                                    mPrivateFlags &= ~PRESSED;  
                                    refreshDrawableState();  
                                }  
                            }  
                            break;  
                    }  
                   
                    return true;   // 控件可点击返回true
                }  
               
                return false;    // 控件不可点击返回false
            }
     
     
        public boolean performClick() {  
     
            if (mOnClickListener != null) {  //设置了点击事件的监听
     
                playSoundEffect(SoundEffectConstants.CLICK);  
     
                mOnClickListener.onClick(this);  //绑定当前view的事件
     
                return true;  //调用onClick()
               
            }  
            return false;  
        }  
完。
--------------------- 
作者:艾阳丶 
来源:CSDN 
原文:https://blog.csdn.net/csdn_aiyang/article/details/89949047 
版权声明:本文为博主原创文章,转载请附上博文链接!
 
                    
                
 
                
            
         浙公网安备 33010602011771号
浙公网安备 33010602011771号