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

完。


 

发布了153 篇原创文章 · 获赞 755 · 访问量 100万+

猜你喜欢

转载自blog.csdn.net/csdn_aiyang/article/details/89949047