本文系转载文章,浏览原文可获取源码,文章开端有原文链接
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); } ......
}
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:
<?xml version=”1.0″ encoding=”utf-8″?>
<com.xe.eventdemo.MyView
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;
}