欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

基于Android9.0,了解Android事件分发(一)

程序员文章站 2023-12-31 22:53:28
基于Android9.0,了解Android事件分发还是那句话:点成线,线成面,切勿贪心,否则一脸懵逼先记住这个事件分发的顺序:Activity->ViewGroup->View以及三个重要的方法:方法名作用是什么?什么时候调用?dispatchTouchEvent()传递(分发)事件当前View能够获取点击事件时onTouchEvent()处理点击事件在dispatchTouchEvent()内部调用onInterceptTouchEvent...

基于Android9.0,了解Android事件分发

还是那句话:点成线,线成面,切勿贪心,否则一脸懵逼

先记住这个事件分发的顺序:

Activity->ViewGroup->View

以及三个重要的方法:

方法名 作用是什么? 什么时候调用?
dispatchTouchEvent() 传递(分发)事件 当前View能够获取点击事件时
onTouchEvent() 处理点击事件 在dispatchTouchEvent()内部调用
onInterceptTouchEvent() 判断是否拦截事件
ViewGroup\color{red}{注意:只存在于ViewGroup中}
在ViewGroup的dispatchTouchEvent()中调用

脑海里大概有了这个顺序和概念,我们就从源码开始吧。

当触发点击事件时,最先响应的是

Activity的dispatchTouchEvent()

public boolean dispatchTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {//按下事件
        onUserInteraction();//这是一个空方法。activity无论分发按键事件、触摸事件或者轨迹球事件都会调用Activity#onUserInteraction()。 
    }
    if (getWindow().superDispatchTouchEvent(ev)) {//获取当前Window,Window是一个abstract类,这里它的实现是PhoneWindow
         //Window的superDispatchTouchEvent方法,也是一个abstract方法,所以要去看PhoneWindow的superDispatchTouchEvent
        return true;
    }
    return onTouchEvent(ev);//没有任何View 接收/处理事件,调用自身的onTouchEvent
}

PhoneWindow的superDispatchTouchEvent

@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
    return mDecor.superDispatchTouchEvent(event);//mDecor为DecorView实例,DecorView为PhoneWindow的顶层Window
}

DecorView继承于FrameLayout,而FrameLayout继承于ViewGroup,所以,DecorView是ViewGroup的子类。

//DecorView的superDispatchTouchEvent
public boolean superDispatchTouchEvent(MotionEvent event) {
    return super.dispatchTouchEvent(event);
}

这里super.dispatchTouchEvent(event);其实就是ViewGroup的dispatchTouchEvent。这里先不作深入分析,假设它返回false,先把第一条路走通,然后再来分析ViewGroup的dispatchTouchEvent。

最后调用onTouchEvent

public boolean onTouchEvent(MotionEvent event) {
    if (mWindow.shouldCloseOnTouch(this, event)) {
        finish();
        return true;
    }

    return false;
}

上面我们知道mWindow就是PhoneWindow,但是shouldCloseOnTouch不是abstract方法,它的实现在Window里

public boolean shouldCloseOnTouch(Context context, MotionEvent event) {
    final boolean isOutside =
            event.getAction() == MotionEvent.ACTION_UP && isOutOfBounds(context, event)
            || event.getAction() == MotionEvent.ACTION_OUTSIDE;//点击Window外面
    if (mCloseOnTouchOutside && peekDecorView() != null && isOutside) {//peekDecorView为DecorView
        return true;
    }
    return false;
}

private boolean isOutOfBounds(Context context, MotionEvent event) {
        final int x = (int) event.getX();
        final int y = (int) event.getY();
        final int slop = ViewConfiguration.get(context).getScaledWindowTouchSlop();//16像素
        final View decorView = getDecorView();
        return (x < -slop) || (y < -slop)
                || (x > (decorView.getWidth()+slop))
                || (y > (decorView.getHeight()+slop));
    }

最后返回false,就代表没有消费事件。

基于Android9.0,了解Android事件分发(一)

ViewGroup的dispatchTouchEvent

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
     ...
        // Check for interception.
        final boolean intercepted;
        if (actionMasked == MotionEvent.ACTION_DOWN
                || mFirstTouchTarget != null) {
            final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
             //disallowIntercept:是否禁用事件拦截的功能。默认为false,不禁用
            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;
        }
        ...
             if (!canceled && !intercepted) {//没有取消也没有拦截
                  ...
                    for (int i = childrenCount - 1; i >= 0; i--) {
                        ...
                        if (!child.canReceivePointerEvents()//是否响应
                                || !isTransformedTouchPointInView(x, y, child, null)) {//是否在View范围内
                            ev.setTargetAccessibilityFocus(false);
                            continue;
                        }

                        newTouchTarget = getTouchTarget(child);
                        ...
                         //注意dispatchTransformedTouchEvent方法,分发事件给子View的关键
                        if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                            ...
                            newTouchTarget = addTouchTarget(child, idBitsToAssign);
                             //在addTouchTarget里面,mFirstTouchTarget被赋值
                            alreadyDispatchedToNewTouchTarget = true;
                            break;
                        }
。。。
                    }
                    if (preorderedList != null) preorderedList.clear();
                }
...
            }
        }

        // Dispatch to touch targets.
        if (mFirstTouchTarget == null) {
            // No touch targets so treat this as an ordinary view.
             //注意dispatchTransformedTouchEvent方法,分发事件给子View的关键
            handled = dispatchTransformedTouchEvent(ev, canceled, null,
                    TouchTarget.ALL_POINTER_IDS);
        } else {
            ...
            while (target != null) {
                final TouchTarget next = target.next;
                if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                    handled = true;
                } else {
                    final boolean cancelChild = resetCancelNextUpFlag(target.child)
                            || intercepted;
                    //注意dispatchTransformedTouchEvent方法,分发事件给子View的关键
                    if (dispatchTransformedTouchEvent(ev, cancelChild,
                            target.child, target.pointerIdBits)) {
                        handled = true;
                    }
                    ...
                }
               ...
            }
        }
		...
    }
...
    return handled;
}

先来看看onInterceptTouchEvent方法,最后再看dispatchTransformedTouchEvent这个方法

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;//默认返回false
}

既然ViewGroup默认不拦截,那就来看看dispatchTransformedTouchEvent是如何分发的

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
        View child, int desiredPointerIdBits) {
    final boolean handled;

    // Canceling motions is a special case.  We don't need to perform any transformations
    // or filtering.  The important part is the action, not the contents.
    final int oldAction = event.getAction();
    if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
        event.setAction(MotionEvent.ACTION_CANCEL);
        if (child == null) {//子View为空,调用自己的dispatchTouchEvent
            handled = super.dispatchTouchEvent(event);
        } else {//子View不为空,调用子View的dispatchTouchEvent
            handled = child.dispatchTouchEvent(event);
        }
        event.setAction(oldAction);
        return handled;
    }
...
        if (child == null || child.hasIdentityMatrix()) {
            if (child == null) {
                handled = super.dispatchTouchEvent(event);
            } else {
                ...
                handled = child.dispatchTouchEvent(event);
...
            }
            return handled;
        }
        ...
    } else {
        ...
    }

    // Perform any necessary transformations and dispatch.
    if (child == null) {
        handled = super.dispatchTouchEvent(transformedEvent);
    } else {
         ...
        handled = child.dispatchTouchEvent(transformedEvent);
    }
...
    return handled;
}

其实逻辑不复杂,核心思想就是child==null,调用自身父类的dispatchTouchEvent,如果不为空,就调用子View的dispatchTouchEvent,接下来,我们就来看看View的dispatchTouchEvent。

public boolean dispatchTouchEvent(MotionEvent event) {
    ...
    if (onFilterTouchEventForSecurity(event)) {
        if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
            result = true;
        }
        //noinspection SimplifiableIfStatement
        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnTouchListener != null
                && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnTouchListener.onTouch(this, event)) {//这里OnTouchListener,就是平常我们设置的setOnTouchListener,onTouch自然就是重写的onTouch
            result = true;
        }

        if (!result && onTouchEvent(event)) {//根据需求,可以重写onTouchEvent。onTouchEvent默认返回true
            result = true;
        }
    }...

    return result;
}

到这里,事件分发基本就结束了,是不是脑子有点糊?胖子第一次也是的,不过,加上自己写个demo,会清楚很多。下面我们来敲敲demo,加深这条线的印象。


首页创建MainActivity、CustomRelativeLayout、CustomView,然后分别重新他们的dispatchTouchEvent、onInterceptTouchEvent(CustomRelativeLayout独有)、onTouchEvent,加上日志,最后看看xml和CustomView的监听

<com.example.test.CustomRelativeLayout
    android:id="@+id/relativeLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.example.test.CustomView
        android:id="@+id/textTv"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:layout_centerInParent="true"
        android:background="@color/colorAccent" />

第一种情况:事件不消费

activityMainBinding.textTv.setOnTouchListener((v,event)->{
    Log.e(TAG,"textTv --->> setOnTouchListener");
    return false;//注意这里,默认是返回false。表示不消费
});
//        activityMainBinding.textTv.setOnTouchListener(new View.OnTouchListener() {
//            @Override
//            public boolean onTouch(View v, MotionEvent event) {
//                return false;
//            }
//        });

点击CustomView看看日志

E/MainActivity: 调用dispatchTouchEvent第0//点击,从上往下传递
E/MainActivity: dispatchTouchEvent   //先调用Activity的事件分发
E/CustomRelativeLayout: dispatchTouchEvent //再调用ViewGroup的事件分发
E/CustomRelativeLayout: onInterceptTouchEvent//判断ViewGroup是否拦截事件
E/CustomView: dispatchTouchEvent//View的dispatchTouchEvent
E/MainActivity: textTv --->> setOnTouchListener//View的setOnTouchListener,onTouch返回false
E/CustomView: onTouchEvent//从下往上返回
E/CustomView: onTouchEvent ACTION_DOWN
E/CustomRelativeLayout: onTouchEvent
E/CustomRelativeLayout: onTouchEvent ACTION_DOWN
E/MainActivity: 调用onTouchEvent第0次
E/MainActivity: onTouchEvent
E/MainActivity: onTouchEvent ACTION_DOWN//最后到Activity的onTouchEvent,结束
E/MainActivity: 调用dispatchTouchEvent第1//这里是抬起
E/MainActivity: dispatchTouchEvent
E/MainActivity: 调用onTouchEvent第1次
E/MainActivity: onTouchEvent
E/MainActivity: onTouchEvent ACTION_UP

第二种情况:View消费

activityMainBinding.textTv.setOnTouchListener((v,event)->{
    Log.e(TAG,"textTv --->> setOnTouchListener");
    return true;
});
E/MainActivity:调用dispatchTouchEvent第0//点击,从上往下传递
E/MainActivity:dispatchTouchEvent
E/CustomRelativeLayout:dispatchTouchEvent
E/CustomRelativeLayout:onInterceptTouchEvent
E/CustomView:dispatchTouchEvent
E/MainActivity:textTv--->>setOnTouchListener//这里返回true,就不往上返回了
E/MainActivity:调用dispatchTouchEvent第1//这里是抬起
E/MainActivity:dispatchTouchEvent
E/CustomRelativeLayout:dispatchTouchEvent
E/CustomRelativeLayout:onInterceptTouchEvent
E/CustomView:dispatchTouchEvent
E/MainActivity:textTv--->>setOnTouchListener

第三种情况:ViewGroup拦截

CustomRelativeLayout
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
    Log.e(TAG,"onInterceptTouchEvent");
    return true;//注意这里,返回true,拦截事件
}
E/MainActivity: 调用dispatchTouchEvent第0次
E/MainActivity: dispatchTouchEvent
E/CustomRelativeLayout: dispatchTouchEvent
E/CustomRelativeLayout: onInterceptTouchEvent//这里返回true,拦截事件
E/CustomRelativeLayout: onTouchEvent//直接调用自己的onTouchEvent
E/CustomRelativeLayout: onTouchEvent ACTION_DOWN
E/MainActivity: 调用onTouchEvent第0次
E/MainActivity: onTouchEvent
E/MainActivity: onTouchEvent ACTION_DOWN
E/MainActivity: 调用dispatchTouchEvent第1次
E/MainActivity: dispatchTouchEvent
E/MainActivity: 调用onTouchEvent第1次
E/MainActivity: onTouchEvent
E/MainActivity: onTouchEvent ACTION_UP

第四种情况:ViewGroup消费

activityMainBinding.relativeLayout.setOnTouchListener((v,event)->{
    Log.e(TAG,"relativeLayout --->> setOnTouchListener");
    return true;
});

与情况二类似,onTouch返回true,就不再往上传递了。


关于总结:还是不借鉴各路大神的blog总结了。胖子觉得这样会造成部分朋友只看总结,不看内容,最后变成知其然不知其所以然(胖子就吃过很多这样的亏),还是交给朋友们自行总结吧。

胖子总结

  • 先清楚传递Activity->ViewGroup->View和返回View->ViewGroup->Activity顺序
  • 再搞清楚dispatchTouchEvent()、onInterceptTouchEvent()、onTouchEvent()的基本作用
  • 打开源码,一步一步跟下去,自己用工具画一画它们之间的关系,之后脑袋里就会有大致概念了
  • 温馨提示:点成线,线成面,切勿贪心,否则一脸懵逼
  • 胖子有什么理解错误的,欢迎大家指出来,一起讨论、学习、进步
  • 期待胖子的第三篇《Android事件分发(二)》

参考文献

图解 Android 事件分发机制

Android事件分发机制详解:史上最全面、最易懂

本文地址:https://blog.csdn.net/shine57322158/article/details/107983731

相关标签: Android 机制

上一篇:

下一篇: