Android事件分发机制源码解析(一)-View的事件分发机制
2017-07-27 16:22
633 查看
概述
Android的事件分发机制最开始的时候一直困扰着我,后来慢慢的有所理解,其实在我们的代码工作中肯定时时刻刻都在和事件分发机制打招呼,只不过没有去深入的研究它罢了,今天有时间就抽空就写这么一篇博客,希望和大家共同进步,我们就从View的事件分发机制开始讲解,由浅入深,从源码分析
首先写一个简单的布局,只有一个Button
这里可以看到首先执行的是onTouch方法,然后执行onClick方法,我点击的时候故意滑动了一下,打印了一个MOVE的动作,这样每个动作都打印了,0,2,1分别代表DOWN,MOVE,UP三个动作
有的童靴会发现,这个setOnTouchListener方法有一个返回值,这里是默认的false,如果我们将返回值改为true试一试呢,看下打印结果
会发现只执行了onTouch方法,没有执行onClick方法,这是为什么呢,下面我们从源码角度看一下为什么
首先你要知道,当我们触摸任何一个控件的时候,会调用这个控件的disPatchTouchEvent方法(其实是首先调用此控件所在布局的disPatchTouchEvent方法,下一篇博客会讲到),那么我们看一下这个方法的源码,Button继承自TextView,TextView继承自View,我们在View中找到了这个方法,看disPatchTouchEvent方法的源码
第一个是mOnTouchListener不为空,其实在我们设置setOnTouchListener时候就已经设置了,这里ListenerInfo其实是持有多个listener的对象
第三个条件mOnTouchListener.onTouch(this,event)方法,其实这个地方就是回调我们注册的onTouch事件的onTouch方法了,如果返回值为true就整体返回true,如果返回值为false就会继续执行onTouchEvent(event)方法
说到这里我们就会明白之前打印的结果,首先执行dispatchTouchEvent里面的onTouch方法,如果onTouch的返回值为false的话会执行onTouchEvent就会打印onClick方法,说明onClick方法是在onTouchEvent方法中执行的;如果返回了true的话后面的onTouchEvent方法就不会执行了,就不会执行onClick了
说到了onTouchEvent方法,那么我们就要看看它的源码
源码有点多,但是我们不全部看,只看重点的部分
在23行只要控件是可点击的就会进入到第26行的switch判断中去,当抬起手指经过各种判断后会进入到第58行的performClick()方法中,我们看里面的源码
现在看来是不是整个思路很清晰了,这里有一点需要注意,就是如果控件是可点击状态,执行dispatchTouchEvent方法,那么在进入onTouchEvent方法时候不管之前onTouch返回值如何,最后返回值都为true,不信看上面的源码,进入switch之后最后都是返回的true,这就有点欺骗大众的感觉;
那么如果我们将Button换为ImageView呢,打印一下结果大家看一下
仅仅执行了ACTION_DOWN动作,因为ImageView是默认不可点击的,所以在执行onTouchEvent方法时候不会进入到switch判断中,就仅仅执行了一个按下动作
如果将onTouch的返回值改为true的话就会打印如下结果
这是为什么呢,因为有一个很重要的知识点,就是Touch事件的层级传递,在dispatchTouchEvent方法执行的时候,只有前一个action返回值为true时候,才会触发下一个action
如果我们给ImageView注册了两个点击监听和onTouch监听(返回值为false)的话
如果onTouch返回值为true的话
这是因为你设置了onClick方法后,源码中得知就意味着将ImageView设置为可点击状态,就算你onTouch返回false但是可以进入onTouchEvent的switch中了,就打印出你看到的结果了;当你设置onTouch返回值为true时候就不会执行onTouchEvent方法,所以就没有打印onClick方法
今天的讲解就到这里,下一篇我会讲解ViewGroup的事件分发机制,希望对大家有所帮助,不对之处欢迎指正
Android事件分发机制源码解析(二)-ViewGroup的事件分发机制
Android的事件分发机制最开始的时候一直困扰着我,后来慢慢的有所理解,其实在我们的代码工作中肯定时时刻刻都在和事件分发机制打招呼,只不过没有去深入的研究它罢了,今天有时间就抽空就写这么一篇博客,希望和大家共同进步,我们就从View的事件分发机制开始讲解,由浅入深,从源码分析
首先写一个简单的布局,只有一个Button
<?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.oman.touchevent.view.MainActivity"> <Button android:id="@+id/btn" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello World!" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" android:src="@mipmap/ic_launcher"/> </android.support.constraint.ConstraintLayout>我们在MainActivity中对此Button设置点击监听和Touch事件
mButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Log.i(TAG, "onClick 执行了"); } }); mButton.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View view, MotionEvent event) { Log.i(TAG, "onTouch 执行了, action " + event.getAction()); return false; } });运行后点击按钮打印如下结果
这里可以看到首先执行的是onTouch方法,然后执行onClick方法,我点击的时候故意滑动了一下,打印了一个MOVE的动作,这样每个动作都打印了,0,2,1分别代表DOWN,MOVE,UP三个动作
有的童靴会发现,这个setOnTouchListener方法有一个返回值,这里是默认的false,如果我们将返回值改为true试一试呢,看下打印结果
会发现只执行了onTouch方法,没有执行onClick方法,这是为什么呢,下面我们从源码角度看一下为什么
首先你要知道,当我们触摸任何一个控件的时候,会调用这个控件的disPatchTouchEvent方法(其实是首先调用此控件所在布局的disPatchTouchEvent方法,下一篇博客会讲到),那么我们看一下这个方法的源码,Button继承自TextView,TextView继承自View,我们在View中找到了这个方法,看disPatchTouchEvent方法的源码
public boolean dispatchTouchEvent(MotionEvent event) { if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && mOnTouchListener.onTouch(this, event)) { return true; } return onTouchEvent(event); }其中有三个条件,当这三个条件都为true时候返回true,否则就会执行onTouchEvent方法,下面我们来看这三个条件
第一个是mOnTouchListener不为空,其实在我们设置setOnTouchListener时候就已经设置了,这里ListenerInfo其实是持有多个listener的对象
public void setOnTouchListener(OnTouchListener l) { getListenerInfo().mOnTouchListener = l; }第二个条件是(mViewFlags&&ENABLE_MASK)==ENABLE,判断当前控件是否是可以点击的,默认都是可以点击的,所以此处为true
第三个条件mOnTouchListener.onTouch(this,event)方法,其实这个地方就是回调我们注册的onTouch事件的onTouch方法了,如果返回值为true就整体返回true,如果返回值为false就会继续执行onTouchEvent(event)方法
说到这里我们就会明白之前打印的结果,首先执行dispatchTouchEvent里面的onTouch方法,如果onTouch的返回值为false的话会执行onTouchEvent就会打印onClick方法,说明onClick方法是在onTouchEvent方法中执行的;如果返回了true的话后面的onTouchEvent方法就不会执行了,就不会执行onClick了
说到了onTouchEvent方法,那么我们就要看看它的源码
public boolean onTouchEvent(MotionEvent event) { final float x = event.getX(); final float y = event.getY(); final int viewFlags = mViewFlags; final int action = event.getAction(); if ((viewFlags & ENABLED_MASK) == DISABLED) { if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) { setPressed(false); } // A disabled view that is clickable still consumes the touch // events, it just doesn't respond to them. return (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE); } if (mTouchDelegate != null) { if (mTouchDelegate.onTouchEvent(event)) { return true; } } if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) { switch (action) { case MotionEvent.ACTION_UP: 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; case MotionEvent.ACTION_DOWN: mHasPerformedLongPress = false; if (performButtonActionOnTouchDown(event)) { break; } // Walk up the hierarchy to determine if we're inside a scrolling container. boolean isInScrollingContainer = isInScrollingContainer(); // For views inside a scrolling container, delay the pressed feedback for // a short period in case this is a scroll. if (isInScrollingContainer) { mPrivateFlags |= PFLAG_PREPRESSED; if (mPendingCheckForTap == null) { mPendingCheckForTap = new CheckForTap(); } mPendingCheckForTap.x = event.getX(); mPendingCheckForTap.y = event.getY(); postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout()); } else { // Not inside a scrolling container, so show the cb60 feedback right away setPressed(true, x, y); checkForLongClick(0, x, y); } break; case MotionEvent.ACTION_CANCEL: setPressed(false); removeTapCallback(); removeLongPressCallback(); mInContextButtonPress = false; mHasPerformedLongPress = false; mIgnoreNextUpEvent = false; break; case MotionEvent.ACTION_MOVE: drawableHotspotChanged(x, y); // Be lenient about moving outside of buttons if (!pointInView(x, y, mTouchSlop)) { // Outside button removeTapCallback(); if ((mPrivateFlags & PFLAG_PRESSED) != 0) { // Remove any future long press/tap checks removeLongPressCallback(); setPressed(false); } } break; } return true; } return false; }
源码有点多,但是我们不全部看,只看重点的部分
在23行只要控件是可点击的就会进入到第26行的switch判断中去,当抬起手指经过各种判断后会进入到第58行的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; } sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); return result; }第4行只要mOnClickListener不为null的话就会执行onClick方法,那么mOnClickListener是在哪里赋值的呢,其实当我们设置onClick监听的时候就自动给赋值了
public void setOnClickListener(@Nullable OnClickListener l) { if (!isClickable()) { setClickable(true); } getListenerInfo().mOnClickListener = l; }
现在看来是不是整个思路很清晰了,这里有一点需要注意,就是如果控件是可点击状态,执行dispatchTouchEvent方法,那么在进入onTouchEvent方法时候不管之前onTouch返回值如何,最后返回值都为true,不信看上面的源码,进入switch之后最后都是返回的true,这就有点欺骗大众的感觉;
那么如果我们将Button换为ImageView呢,打印一下结果大家看一下
mImageView.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View view, MotionEvent event) { Log.i(TAG, "onTouch 执行了, action " + event.getAction()); return false; } });
仅仅执行了ACTION_DOWN动作,因为ImageView是默认不可点击的,所以在执行onTouchEvent方法时候不会进入到switch判断中,就仅仅执行了一个按下动作
如果将onTouch的返回值改为true的话就会打印如下结果
这是为什么呢,因为有一个很重要的知识点,就是Touch事件的层级传递,在dispatchTouchEvent方法执行的时候,只有前一个action返回值为true时候,才会触发下一个action
如果我们给ImageView注册了两个点击监听和onTouch监听(返回值为false)的话
mImageView.setOnClickListener(new View.OnClickListener() {打印结果如下
@Override
public void onClick(View view) {
Log.i(TAG, "onClick 执行了");
}
});
mImageView.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View view, MotionEvent event) { Log.i(TAG, "onTouch 执行了, action " + event.getAction()); return false; } });
如果onTouch返回值为true的话
mImageView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Log.i(TAG, "onClick 执行了"); } }); mImageView.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View view, MotionEvent event) { Log.i(TAG, "onTouch 执行了, action " + event.getAction()); return true; } });打印结果如下
这是因为你设置了onClick方法后,源码中得知就意味着将ImageView设置为可点击状态,就算你onTouch返回false但是可以进入onTouchEvent的switch中了,就打印出你看到的结果了;当你设置onTouch返回值为true时候就不会执行onTouchEvent方法,所以就没有打印onClick方法
今天的讲解就到这里,下一篇我会讲解ViewGroup的事件分发机制,希望对大家有所帮助,不对之处欢迎指正
Android事件分发机制源码解析(二)-ViewGroup的事件分发机制
相关文章推荐
- Android View 事件分发机制 源码解析 (上)
- Android View 事件分发机制 源码解析 (上)
- Android View 事件分发机制 源码解析 (上)
- Android源码解析之四:View事件分发机制
- Android View体系(五)从源码解析View的事件分发机制
- Android事件分发机制完全解析,带你从源码的角度彻底理解(上,view)
- View 相关 Android事件分发机制完全解析,带你从源码的角度彻底理解(下)
- Android事件分发机制源码解析(二)-ViewGroup的事件分发机制
- Android View 事件分发机制 源码解析 (上)
- Android View 事件分发机制 源码解析 (上)
- Android View 事件分发机制 源码解析
- Android View 事件分发机制 源码解析 (上)
- Android事件分发机制源码畅游解析(ViewGroup篇)
- Android源码解析ViewGroup的touch事件分发机制
- Android View 事件分发机制 源码解析
- Android事件分发机制完全解析,带你从源码的角度彻底理解(下,ViewGroup篇)
- Android View 事件分发机制 源码解析 (上)
- Android View 事件分发机制 && Android ViewGroup 事件分发机制 源码解析 --总结
- Android View 事件分发机制 源码解析 (上)
- Android事件分发机制源码畅游解析(View篇)