Android中View事件的分发第一篇

本文系转载文章,阅读原文可获取源码,文章末尾有原文链接
ps:源码是基于android api 26 来分析的
点击事件实际上就是MotionEvent,对于MotionEvent事件的这个过程,实际上就是点击事件的这个事件发生,如果一个MotionEvent产生了以后,系统需要把这个过程事件传递给一个具体的视图,而传递的就是传递过程;分发过程由 ViewGroup 中的 3 个重要方法组成,分别是 dispatchTouchEvent、onIntercepTouchEvent 和 onTouchEvent 方法。
我们来看看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; 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; } ...... // Dispatch to touch targets. if (mFirstTouchTarget == null) { // No touch targets so treat this as an ordinary view. handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS); } else { ...... } ...... return handled; }

发现ViewGroup中的dispatchTouchEvent方法调用了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) { handled = super.dispatchTouchEvent(event); } else { handled = child.dispatchTouchEvent(event); } event.setAction(oldAction); return handled; } ...... return handled;

}
因为小孩这个参数传过来的时候是空的,所以调用的是super.dispatchTouchEvent这个方法,super是View,我们点击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)) { result = true; }if (!result && onTouchEvent(event)) { result = true; } } ...... return result;

}
这里调用了onTouchEvent方法,可以看到ViewGroup的dispatchTouchEvent、onInterceptTouchEvent和onTouchEvent方法的关系就出来了,用以下伪代码表示;
@Override public boolean dispatchTouchEvent(MotionEvent ev) { boolean isExpense = false; if (onInterceptTouchEvent(ev)) { isExpense = onTouchEvent(event); } else { isExpense = child.dispatchTouchEvent(ev); } return isExpense; }

首先事件会传递给ViewGroup,它的dispatchTouchEvent方法会被先调用,如果这个ViewGroup的onInterceptTouchEvent方法返回true就表示它要拦截当前事件,这个ViewGroup就会调用onTouchEvent方法;如果这个ViewGroup的onInterceptTouchEvent方法返回false就表示可能当前事件会继续传递给它的子元素,接下来子元素的触发事件方法会被调用,这样的触发事件被消费。从上述源中事件发生当事件由ViewGroup的子元素成功处理时,mFirstTouchTarget会也被调用方法并当处理子元素,不拦截事件触发事件交由子元素时 mFirstTouchTarget != null;所以当 mFirstTouchTarget == null 时会调用 dispatchTransformedTouchEvent,最终会调用 View 的 onTouchEvent,拦截事件的ViewGroup自己的消费事件。
事件的传递顺序是这样的,Activity->Window(实现类PhoneWindow)-->DecorView(当前界面组件容器FrameLayout)-->组件文件View(setContentView方法中的文件),好,我们先看看Activity中的dispatchTouchEvent方法的源码;
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) { onUserInteraction(); } if (getWindow().superDispatchTouchEvent(ev)) { return true; } return onTouchEvent(ev);

}
从上面的代码可以事件发生,事件总是先传递给Activity,Activity再传递给Window,Window再传递给DecorView。DecorView接收到事件后,就会按照事件传播机制去传播事件;如果一个View的onTouchEvent返回false,那么它的父容器的onTouchEvent 就会被调用,依此推导。如果所有的视图都没有处理事件,那么Activity 就会处理它,也就是Activity 的onTouchEvent 方法会被调用。我们来验证一下窗口的实现类是不是PhoneWindow,我们点击查看Activity的getWindow源码方法;
public Window getWindow() {
return mWindow;

}
发现getWindow方法返回的只是一个Window对象,发现了Activity,发现在attach方法中实例化了mWindow;
final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident, Application application, Intent intent, ActivityInfo info, CharSequence title, Activity parent, String id, NonConfigurationInstances lastNonConfigurationInstances, Configuration config, String referrer, IVoiceInteractor voiceInteractor, Window window, ActivityConfigCallback activityConfigCallback) { ...... mWindow = new PhoneWindow(this, window, activityConfigCallback); mWindow.setWindowControllerCallback(this); mWindow.setCallback(this); mWindow.setOnWindowDismissedCallback(this); mWindow.getLayoutInflater().setPrivateFactory(this); if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) { mWindow.setSoftInputMode(info.softInputMode); } if (info.uiOptions != 0) { mWindow.setUiOptions(info.uiOptions); } ......

【Android中View事件的分发第一篇】}
Window Window的实现类是PhoneWindow;“DecorView是当前的界面组件FrameLayout”不会在查源码中证明了,吸引的读者可以自己阅读源码。
我们回顾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)) { result = true; }if (!result && onTouchEvent(event)) { result = true; } } ...... return result;

}
从 View中的dispatchTouchEvent方法,我们不能得出一些结论,如果一个View需要处理事件时,如果它使用了setOnTouchListener语句,那么OnTouchListener中的onTouch会被调用;如果onTouch方法的返回值返回false ,那么这个 View 的 onTouchEvent 方法会被调用;如果返回 true,那么 onTouchEvent 方法将不会被调用,所以给 View 使用 setOnTouchListener 语句,它的优先级比 onTouchEvent 方法要高;在当前的 View 中,如果有 OnClickListener语句,它的 onClick 方法会被调用,前提是 onTouchEvent 返回值为 super.onTouchEvent(event),所以 setOnClickEvent(event),所以 setOnClickEventListener 的优先级,它是高那么高后才触发。
下面我们用demo验证一下上面的结论;
(1)新建一个 kt 语言的类 MyView 并继承于 View:
class MyView: View {
companion object { var TAG:String = "Activity" } constructor(context: Context): super(context){ }constructor(context: Context, attrs: AttributeSet): super(context, attrs){}constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int): super(context, attrs, defStyleAttr){}override fun onTouchEvent(event: MotionEvent?): Boolean { Log.d(TAG,"------------onTouchEvent--") return super.onTouchEvent(event) }

}
(2)新建一个组件文件activity_main.xml:

android:id="@+id/btn" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#00FF00" xmlns:android="http://schemas.android.com/apk/res/android" />

(3)新建一个kt语言的类MainActivity并继承于AppCompatActivity:
class MainActivity: AppCompatActivity() {
var mMyView: View? = null ; companion object { var TAG: String = "Activity" } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) mMyView = findViewById(R.id.btn) mMyView!!.setOnTouchListener(object : View.OnTouchListener { override fun onTouch(v: View?, event: MotionEvent?): Boolean { Log.d(TAG,"------------onTouch--") return true } }) mMyView!!.setOnClickListener(object : View.OnClickListener { override fun onClick(v: View?) { Log.d(TAG,"------------onClick--") } })}

}
程序一开始运行的界面如下所示:
图片
当我点击一下时,日志打印如下所示(说明没有调用 onTouchEvent 方法和 onClick 方法):
图片
当我把onTouch方法的返回值改成假,再运行程序轻轻点击一下时,日志打印如下所示(说明调用了的onTouchEvent方行业释义法律的onClick状语从句:方法):
图片
当我把 的onTouchEvent方行业释义法律的报道查看值改成真,运行再程序轻轻点击一下时,日志打印如下所示(说明没有调用 的onClick方法)
图片
为什么 onTouchEvent 方法的返回值只有超级。onTouchEvent(event) 时会调用 onClick 方法呢,我们点击 super. onTouchEvent(event)语句的代码,发现是View的 onTouchEvent方法,它的实现如下所示:
public boolean onTouchEvent(MotionEvent event) {
...... switch (action) { case MotionEvent.ACTION_UP: mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN; if ((viewFlags & TOOLTIP) == TOOLTIP) { handleTooltipUp(); } if (!clickable) { removeTapCallback(); removeLongPressCallback(); mInContextButtonPress = false; mHasPerformedLongPress = false; mIgnoreNextUpEvent = false; break; } boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0; if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) { // take focus if we don't have it already and we should in // touch mode. boolean focusTaken = false; if (isFocusable() && isFocusableInTouchMode() && !isFocused()) { focusTaken = requestFocus(); }if (prepressed) { // The button is being released before we actually // showed it as pressed.Make it show the pressed // state now (before scheduling the click) to ensure // the user sees it. setPressed(true, x, y); }if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) { // This is a tap, so remove the longpress check removeLongPressCallback(); // Only perform take click actions if we were in the pressed state if (!focusTaken) { // Use a Runnable and post this rather than calling // performClick directly. This lets other visual state // of the view update before click actions start. if (mPerformClick == null) { mPerformClick = new PerformClick(); } if (!post(mPerformClick)) { performClick(); } } }if (mUnsetPressedState == null) { mUnsetPressedState = new UnsetPressedState(); }if (prepressed) { postDelayed(mUnsetPressedState, ViewConfiguration.getPressedStateDuration()); } else if (!post(mUnsetPressedState)) { // If the post failed, unpress right now mUnsetPressedState.run(); }removeTapCallback(); } mIgnoreNextUpEvent = false; break; ...... } ...... return false;

}
从这里可以发出,当手指提高来的时候,查看方法的 onTouchEvent 方法又会调用 performClick 方法,performClick 方法接口又会调用 OnClickListener 的 onClick 方法:
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; }sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); notifyEnterOrExitForAutoFillIfNeeded(true); return result;

}

    推荐阅读