Android开发之散记

Android-事件分发机制

2022-03-07  本文已影响0人  流失的语言

讲讲Android的事件分发机制

一般情况下,一个事件流的产生基本上都是从用户的ACTION_DOWN开始和后续的ACTION_MOVE(多个)、ACTION_UP以及ACTION_CACEL,这些事件形成了一个事件流的过程。

这里会涉及到三个非常重要的方法

当用户点击屏幕的那一刻,事件首先会传递到当前的Activity,会调用Activity的dispatchTouchEvent()方法

getWindow().superDispatchTouchEvent(ev),而window是PhoneWindow, mDecorView.dispatchTouchEvent()

DecorView是根布局,里面调用了super.dispatchTouchEvent(),而decorView继承FrameLayout,且FrameLayout是ViewGroup的子类,所以最终会传递到ViewGroup的dispatchTouchEvent()

final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
        || mFirstTouchTarget != null) {
    final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
    if (!disallowIntercept) {
        intercepted = onInterceptTouchEvent(ev);
        ev.setAction(action); // restore action in case it was changed
    } else {
        intercepted = false;
    }
} else {
    // There are no touch targets and this action is not an initial down
    // so this view group continues to intercept touches.
    intercepted = true;
}

上面这段代码非常重要,intercept代码是否要拦截事件的向下分发,而影响intercept的因数有actionMasked == MotionEvent.ACTION_DOWN 用户按下操作的事件

还有mFirstTouchTarget != null这个mFirstTouchTarget是在子View能处理事件后才赋值的

(mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0是另外一个很重要的方法,写过自定义View并且遇到事件冲突的同学一定知道getPatent().requestDisallowInterceptTouchEvent(value:Boolean)这个值会影响到if (!disallowIntercept) {是否让父View去拦截事件的入口条件,但是父容器是不是需要拦截,还得需要看onInterceptTouchEvent()有没有重写拦截的逻辑,默认是不拦截的 ,当然系统的控件比如ViewPager帮我们实现了事件冲突的处理。

intercept接下来做了什么处理呢

boolean alreadyDispatchedToNewTouchTarget = false;
if (!canceled && !intercepted) {
    .......
    //遍历子View
    for (int i = childrenCount - 1; i >= 0; i--) {
    
     //判断当前点击的xy是否在当期child的区域上,如果不在当然就不用去处理事件
     //接着下一次遍历
    if (!child.canReceivePointerEvents()
            || !isTransformedTouchPointInView(x, y, child, null)) {
        continue;
    }
    
    //这里是处理当前child是否有能力处理事件
    if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {

     //有能力处理后,将mFirstTouchTarget赋值
    newTouchTarget = addTouchTarget(child, idBitsToAssign);
    //赋值后续事件
    alreadyDispatchedToNewTouchTarget = true;
    

}


上面的流程是有子View的情况,最后还是会执行到下面的else,因为alreadyDispatchedToNewTouchTarget赋值为true ,所以最后会将handled返回到上层,代表消费了事件

if (mFirstTouchTarget == null) {
    handled = dispatchTransformedTouchEvent(ev, canceled, null,
            TouchTarget.ALL_POINTER_IDS);
} else {
    while (target != null) {
        if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
            handled = true;
        } 
    }
}

以上都是ACTION_DOWN的事件分发和事件处理的逻辑,接下来就是ACTION_MOVE的事件继续传递,当然事件最终会传递到View的dispatchTouchEvent,这里我们最后去分析。

当用户手指在屏幕上滑动,这里的第二个事件就会传递来,这里的事件类型为ACTION_MOVE

事件还是会传递到ViewGroup的dispatchTouchEvent里面来

if (actionMasked == MotionEvent.ACTION_DOWN) {
    //清除变量等标识
    cancelAndClearTouchTargets(ev);
    resetTouchState();
}

因为这一次是MOVE事件,所以这里的mFirstTarget不会被重新置为null的,还是属于一个事件流里面,由于mFirstTouchTarget不为null,所以还是会去看看父容器是不是要去拦截事件,假如还是默认不拦截,那么intercepted还是为false

if (actionMasked == MotionEvent.ACTION_DOWN
        || mFirstTouchTarget != null) {
    final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
    if (!disallowIntercept) {
        intercepted = onInterceptTouchEvent(ev);
        ev.setAction(action); // restore action in case it was changed
    } else {
        intercepted = false;
    }
}

由于intercepted为false还是会进入if里面,但是由于是MOVE事件,所以事件是不会继续向下分发的

if (!canceled && !intercepted) {
    //不会执行
    if (actionMasked == MotionEvent.ACTION_DOWN
            || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
            || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
 
}

最后还是会执行到dispatchTransformedTouchEvent,这里需要注意的是child参数传递的是target.child,这里的target就是在addTouchTarget里面赋值的

if (dispatchTransformedTouchEvent(ev, cancelChild,
        target.child, target.pointerIdBits)) {
    handled = true;
}

这里最后将handled = true 返回说明move事件处理事件了 当然会有多个,一般来说是第二个move事件才会确定move的事件处理逻辑,这里我们可以做一些滑动冲突的拦截,来解决滑动冲突

最后我们看一下这个dispatchTransformedTouchEvent方法,这个是很重要的,里面判断是事件的流向,如果有child就会传递到子View,如果子View是一个ViewGroup的话,会继续之前的流程分发,如果是View的话,就会看View会不会处理消费事件,有没有发现,事件分发的传递其实就是一个递归。

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
        View child, int desiredPointerIdBits) {」


    if (child == null) {
        handled = super.dispatchTouchEvent(transformedEvent);
    } else {
         handled = child.dispatchTouchEvent(transformedEvent);
    }


}

如果child为null,就要看当前View是否能处理事件,能处理就消费事件了,不为null,就会看下一层view是否能处理消费事件

接下来就会切换类了

View.java

public boolean dispatchTouchEvent(MotionEvent event) {
 
    boolean result = false;


    if (onFilterTouchEventForSecurity(event)) {
        //noinspection SimplifiableIfStatement
        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnTouchListener != null
                && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnTouchListener.onTouch(this, event)) {
            result = true;
        }

        if (!result && onTouchEvent(event)) {
            result = true;
        }
    }

    return result;
}

这里我们看到li != null&&li.mOnTouchListener != null&&(mViewFlags & ENABLED_MASK) == ENABLED&&li.mOnTouchListener.onTouch(this, event) 缺一不可的条件result才是true代表事件消费了,这里也是我们设置setOnTouchListener的就会赋值到这里面,所以onTouch事件是优先于onTouchEvent事件了,并且返回值最终会影响onTouchEvent事件会不会执行

只有onTouch方法返回false,onTouchEvent才会被执行

public boolean onTouchEvent(MotionEvent event) {

    UP事件
    
    
    if (!post(mPerformClick)) {
        performClickInternal();
    }
    

}

private boolean performClickInternal() {

    return performClick();
}

public boolean performClick() {

  final boolean result;
    final ListenerInfo li = mListenerInfo;
    if (li != null && li.mOnClickListener != null) {
        playSoundEffect(SoundEffectConstants.CLICK);
        li.mOnClickListener.onClick(this);
        result = true;
    } else {
        result = false;
    }

    return result;
}

一个事件的结束必然是手指离开屏幕的时候,除异常cacel事件外,所以我们在ACTION_UP里面找到li != null && li.mOnClickListener != null这里指的是我们设置的点击事件,如果点击事件做了事情那么result就会被返回,代表事件消费了

这里我们可以看到onTouch>onTouchEvent>onClick

最终事件的返回变量会回到Acitvity的dispatchTouchEvent()

public boolean dispatchTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        onUserInteraction();
    }
    if (getWindow().superDispatchTouchEvent(ev)) {
        return true;
    }
    return onTouchEvent(ev);
}

如果事件消费了 if (getWindow().superDispatchTouchEvent(ev))为ture

就不会执行的onTouchEvent,事件分发结束,否者传递到activity的onTouchEvent

如果activity的onTouchEvent也没有消费事件,这个事件就会被丢弃,整个事件分发结束

到这里我们的分析就基本结束了

如果我们遇到事件冲突,就可以考虑通过onInterceptTouchEventdispatchTouchEvent以及requestDiaAllowInterceptTouchEvent()来通过滑动距离以及具体逻辑来拦截和处理冲突事件

上一篇 下一篇

猜你喜欢

热点阅读