(四) 高级UI事件分发、事件冲突处理

1,203 阅读3分钟

一、MotionEvent介绍

image.png

二、事件的接收流程。

可根据之前的结成介绍找到入口。

viewRootImpl会对事件进行处理,首先找到DecorView,然后再找到activity再在dispatchTouchEvent()里处理。

setView@ViewRootImp.java
    --> mInputEventReceiver = new WindowInputEventReceiver(inputChannel,Looper.myLooper());//接收事件的
    -->WindowInputEventReceiver是内部类,事件在onInputEvent(InputEvent event)方法里处理
        -->enqueueInputEvent()
            -->doProcessInputEvents()
                -->deliverInputEvent(q)
                    -->stage.deliver(q)(InputStage stage;ViewPostImeStage)
                        -->onprocess()
                            -->processPointerEvent(q);
                                //mView是DecorView
                                -->mView.dispatchPointerEvent(event)//这个方法是View.java
                                    -->dispatchTouchEvent()//这个方法在DecorView.java
                                        -->dispatchTouchEvent@Activity.java
                                            -->getwindow().superDispatchTouchEvent(ev);
                                                
                                            
                                            
  superDispatchTouchEvent@PhoneWindow.java
      -->mDecor.superDispatchTouchEvent(event)
          -->最终调用的是DispatchTouchEvent@ViewGroup.java//我们处理的事件分发机制
              -->onTouchEvent()
              
//事件处理的方法
View.dispatchTouchEvent();

DecoreView.java的dispatchTouchEvent方法。

cb == activity
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    final Window.Callback cb = mWindow.getCallback();
    return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
            ? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
}

三、父布局里有一个子view,点击子view的流程打印结果。

1.viewGroup

  • dispatchtouchevent()
  • onTouchEvent()
  • OnInterceptTouchEvent():返回true,子view被拦截

2. view

  • onclick()
  • onTouch()
  • dispatchTouchEvent()
  • onTouchEvent()

3. 打印结果

结论:每个事件都会经历父容器到子view


dispatchTouchEvent: 父容器
onInterceptTouchEvent: 父容器
dispatchTouchEvent: 子View
onTouch: 0
onTouchEvent: MotionEvent.ACTION_DOWN = 0


dispatchTouchEvent: 父容器
onInterceptTouchEvent: 父容器
dispatchTouchEvent: 子View
onTouch: 1
MotionEvent.ACTION_UP = 1
onClick

四、事件处理。

1.事件消费(没重写)

当源码中result == true的时候代表这个事件被消费了。

view#disPatchTouchEvent
    -->onTouch()//此处执行onTouch方法,则后面的方法不执行。
    -->onTouchEvent()//此处是执行onclick()方法的。
       -->MotionEvent.Action_up
           -->performClick()
               -->performClickInternal()
                   -->onclick() //点击事件执行。
       -->MotionEvent.Action_move
           -->!pointInView()//当发现移动的时候移出了view,则up的时候就不会触发点击和长按的响应应。
       -->MotionEvent.Action_down
           -->checkForLongClick()//延时回调,长按的逻辑处理。当在500ms内触发up则取消长按。

2.viewGroup的dispatchTouchEvent的流程

  • 只有第一根手指按下会响应action_down。后续的所有手指都是action_Point_Down.

  • 最后抬起的那根手指是action_up。之前抬起的都是action_point_up

  • 最多识别32跟手指。int有多少位多少根手指。

viewGroup#dispatchTouchEvent
    -->1.actionMasked == MotionEvent.ACTION_DOWN//不管是单指还是多指,会进入一次。重置状态
    -->2.检测是否拦截。//OnInterceptTouchEvent
    -->3.通过条件判断决定是否要分发事件。
        -->进入循环判断子view里是否要处理这个事件。
    -->4.当子view不处理,自己判断是否处理这个事件。
        -->viewgroup进行自己处理事件是调用的view的dispatchTouchEvent()


换个角度去分析

第一块:

  • 是否拦截子view 第二块:
  • 遍历子view是否处理事件。 第三块:
  • 子view不处理,询问自己是否处理。
  • 子view处理,分情况。

大概分析一下

在第一块代码,Action_down的时候如果没有拦截子view,则会在第二个块代码遍历找到需要执行事件的view并把target.child记录下来。当后续的action_move就不会走第二块代码,之前记录的target.child去执行move事件。

五、viewgroup嵌套viewGroup事件触发分析

viewpager:横着滑动(左右滑动)。

listview:竖着滑动(上下滑动)。

1.当viewpager的oninterceptTouchEvent返回值为true。

上下不可以滑动,左右可以滑动

2.当viewpager的oninterceptTouchEvent返回值为false。

上下可以滑动,左右不可以滑动。

3.当viewpager的oninterceptTouchEvent返回值为false,当ListView的dispatchTouchEvent返回值为false。

上下不可以滑动,左右能滑动

如何实现上下可以,左右也可以滑动?

两个view叠加在一起,冲突是必然的。

冲突处理:

1.内部拦截法(子view根据条件来让事件由谁触发)要让子view拿到事件。

用此方法,必须能让子view能拿到事件。

子viewgroup

@Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        int x = (int) event.getX();
        int y = (int) event.getY();


        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN: {
            //让父控件不去拦截自己
                getParent().requestDisallowInterceptTouchEvent(true);
                break;
            }
            case MotionEvent.ACTION_MOVE: {
                int deltaX = x - mLastX;
                int deltaY = y - mLastY;
                // 这个条件由业务逻辑决定,看什么时候 子View将事件让出去
                //左右滑动,就让父容器拦截。
                if (Math.abs(deltaX) > Math.abs(deltaY)) {
                    getParent().requestDisallowInterceptTouchEvent(false);
                }
                break;
            }
            case MotionEvent.ACTION_UP: {
                break;

            }
            default:
                break;
        }

        mLastX = x;
        mLastY = y;
        return super.dispatchTouchEvent(event);
    }

父viewgroup


    // 拦截自己的孩子
    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        // down事件的时候不能拦截,因为这个时候 requestDisallowInterceptTouchEvent 无效
        if (event.getAction() == MotionEvent.ACTION_DOWN) {
            super.onInterceptTouchEvent(event);
            return false;
        }
        return true;
    }

2.外部拦截法(由父容器来根据条件让事件由谁来触发)


 // 外部拦截法:一般只需要在父容器处理,根据业务需求,返回true或者false
    public boolean onInterceptTouchEvent(MotionEvent event) {
        int x = (int) event.getX();
        int y = (int) event.getY();

        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN: {
                mLastX = (int) event.getX();
                mLastY = (int) event.getY();
                break;
            }
            case MotionEvent.ACTION_MOVE: {
                int deltaX = x - mLastX;
                int deltaY = y - mLastY;
                if (Math.abs(deltaX) > Math.abs(deltaY)) {
                    return true;
                }
                break;
            }
            case MotionEvent.ACTION_UP: {
                break;
            }
            default:
                break;
        }

        return super.onInterceptTouchEvent(event);
    }