Android View 事件分发机制 源码解析(View篇)
2016-12-07 17:08
661 查看
本期三篇文章目录(可点击跳转)
一. Android TouchEvent事件传递机制初识
二. Android View 事件分发机制 源码解析(ViewGroup篇)
三.Android View 事件分发机制 源码解析(View篇)
跟ViewGroup的dispatchTouchEvent方法比起来,View的dispatchTouchEvent方法真是简单太多了。
1)前面都是一些跟主流程无关的代码,我们直接看第31行
onFilterTouchEventForSecurity(event)判断当前View是否没被遮住,如果没有被遮挡住,则执行if语句块中代码。
2)继续看第33-39行,onTuouch事件触发
ListenerInfo是View的静态内部类,里面是一些回调接口,比如:OnClickListener,OnLongClickListener等等。
这个判断比较长,li是ListenerInfo的对象,已经初始化过了,必然不为null,li.mOnTouchListener是否为null呢?全局搜一下,在哪里初始化或者赋值。在View中搜索到如下代码:
这个方法,我相信大部分都很熟悉,不就是TextView或者Button在监听touch事件的时候,需要增加的监听方法吗?没错,就是他。也就是说我们设置了我们的view 设置了setOnTouchListener(OnTouchListener l)方法后,li.mOnTouchListener则不为空。反之,如果不设置setOnTouchListener方法,则不执行if语句块中代码,则result=false。
接着通过位与运算确定控件(View)是不是enabled的,默认控件都是enabled的,也就是说,如果你调用一个view,设置成view.setEnabled(false),则view的onTouch方法将不会被触发,onTouchEvent方法则不受影响。
最后,调用li.mOnTouchListener.onTouch(this, event),如果onTouch方法返回true,则result=true,就不执行onTouchEvent(event)方法,反之,则执行onTouchEvent(event)方法。所以一般如果我们在自己的view中设置了setOnTouchListener方法后,并且返回值设置成true后,则当前view就接受不到onTouchEvent(event)方法。
3)继续看第40-43行,onTouchEvent事件触发
当result=false是,onTouchEvent被调用,根据result的赋值,可以得到一个结论,dispatchTouchEvent的返回值由onTouch和onTouchEvent返回值决定。
关于onTouchEvent的源码,继续跟踪下去。
onTouchEvent的源码如下所示:
又是一百多行代码,继续看吧。
1)前面都是一些跟主流程无关的代码,我们直接看第6-17行
这段代码主要就是判断view的enabled为false还是true,通过源码可以知道,view的enable不管为true或者false都不影响onTouchEvent方法的返回,但是影响执行过程,onTouchEvent的返回跟clickable跟longclickable有关,但是如果enabled为false是,则直接退出了该方法,则onClick无法触发。
2)继续看24-28行
当clickable跟longclickable都为true是,则执行switch方法块,可知onTouchEvent的执行跟clickable跟longclickable有关,其实跟enable也有关系,因为enabled为false时直接退出方法,下面这段代码则无法执行。所以有的时候,你的view的onClick事件监听不到,看看是否设置了这三个属性其中之一为false。
3)继续看54-62行,通知回调onClick方法
使用Runnable并发布,而不是直接调用performClick。 这使得视图的其他可视状态在点击操作开始之前更新。performClick源码如下所示:
跟前面一样,li不为空,而且不难理解li.mOnClickListener肯定是通过setOnClickListener(@Nullable OnClickListener l)设置的。
当view设置了setOnClickListener方法,则performClick()方法会回调onClick方法。而且performClick()只在MotionEvent.ACTION_UP点击事件中执行,也就是手指离开屏幕时才执行。
2.如果onTouch方法返回true,就不执行onTouchEvent(event)方法,反之,则执行onTouchEvent(event)方法。所以一般如果我们在自己的view中设置了setOnTouchListener方法后,并且返回值设置成true后,则当前view就接受不到onTouchEvent(event)方法。
3.view的enable不管为true或者false都不影响onTouchEvent方法的返回,但是影响执行过程,onTouchEvent的返回跟clickable跟longclickable有关,但是如果enabled为false是,则直接退出了dispatchTouchEvent方法,则onClick无法触发,所以有的时候,你的view的onClick事件监听不到,看看是否设置了这三个属性其中之一为false。
4.我们主动调用performClick触发点击时,其实就是做的MotionEvent.ACTION_UP执行的事情,作用就是接口回调而已。
如有错误欢迎指出来,一起学习。
交流讨论群
群号:469890293
一. Android TouchEvent事件传递机制初识
二. Android View 事件分发机制 源码解析(ViewGroup篇)
三.Android View 事件分发机制 源码解析(View篇)
1. 前言
上一篇介绍了ViewGroup中点击事件传递过程,这篇主要介绍View中如何传递点击事件以及一些注意点。2.源码解析
上文中讲到,dispatchTransformedTouchEvent方法会调用dispatchTouchEvent方法,点击事件从ViewGroup成功传递到View中,接下来看下dispatchTouchEvent方法的源码:/** * Pass the touch screen motion event down to the target view, or this * view if it is the target. * * @param event The motion event to be dispatched. * @return True if the event was handled by the view, false otherwise. */ public boolean dispatchTouchEvent(MotionEvent event) { // If the event should be handled by accessibility focus first. if (event.isTargetAccessibilityFocus()) { // We don't have focus or no virtual descendant has it, do not handle the event. if (!isAccessibilityFocusedViewOrHost()) { return false; } // We have focus and got the event, then use normal event dispatch. event.setTargetAccessibilityFocus(false); } boolean result = false; if (mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onTouchEvent(event, 0); } final int actionMasked = event.getActionMasked(); if (actionMasked == MotionEvent.ACTION_DOWN) { // Defensive cleanup for new gesture stopNestedScroll(); } //当前View是否没被遮住等 if (onFilterTouchEventForSecurity(event)) { //noinspection SimplifiableIfStatement ListenerInfo li = mListenerInfo; //调用onTouch方法,它其实就是OnTouchListener接口的方法 if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) { result = true; } //onTouch方法返回true,则不执行onTouchEvent(event)方法 //onTouch方法返回false,则执行onTouchEvent(event)方法 if (!result && onTouchEvent(event)) { result = true; } } if (!result && mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onUnhandledEvent(event, 0); } // Clean up after nested scrolls if this is the end of a gesture; // also cancel it if we tried an ACTION_DOWN but we didn't want the rest // of the gesture. if (actionMasked == MotionEvent.ACTION_UP || actionMasked == MotionEvent.ACTION_CANCEL || (actionMasked == MotionEvent.ACTION_DOWN && !result)) { stopNestedScroll(); } return result; }
跟ViewGroup的dispatchTouchEvent方法比起来,View的dispatchTouchEvent方法真是简单太多了。
1)前面都是一些跟主流程无关的代码,我们直接看第31行
onFilterTouchEventForSecurity(event)判断当前View是否没被遮住,如果没有被遮挡住,则执行if语句块中代码。
2)继续看第33-39行,onTuouch事件触发
ListenerInfo是View的静态内部类,里面是一些回调接口,比如:OnClickListener,OnLongClickListener等等。
if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event))
这个判断比较长,li是ListenerInfo的对象,已经初始化过了,必然不为null,li.mOnTouchListener是否为null呢?全局搜一下,在哪里初始化或者赋值。在View中搜索到如下代码:
/** * Register a callback to be invoked when a touch event is sent to this view. * @param l the touch listener to attach to this view */ public void setOnTouchListener(OnTouchListener l) { getListenerInfo().mOnTouchListener = l; }
这个方法,我相信大部分都很熟悉,不就是TextView或者Button在监听touch事件的时候,需要增加的监听方法吗?没错,就是他。也就是说我们设置了我们的view 设置了setOnTouchListener(OnTouchListener l)方法后,li.mOnTouchListener则不为空。反之,如果不设置setOnTouchListener方法,则不执行if语句块中代码,则result=false。
接着通过位与运算确定控件(View)是不是enabled的,默认控件都是enabled的,也就是说,如果你调用一个view,设置成view.setEnabled(false),则view的onTouch方法将不会被触发,onTouchEvent方法则不受影响。
最后,调用li.mOnTouchListener.onTouch(this, event),如果onTouch方法返回true,则result=true,就不执行onTouchEvent(event)方法,反之,则执行onTouchEvent(event)方法。所以一般如果我们在自己的view中设置了setOnTouchListener方法后,并且返回值设置成true后,则当前view就接受不到onTouchEvent(event)方法。
3)继续看第40-43行,onTouchEvent事件触发
当result=false是,onTouchEvent被调用,根据result的赋值,可以得到一个结论,dispatchTouchEvent的返回值由onTouch和onTouchEvent返回值决定。
关于onTouchEvent的源码,继续跟踪下去。
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(); //view是否为enabled,不管为true或者false都不影响onTouchEvent方法的返回,但是影响执行过程 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. //onTouchEvent的返回跟clickable跟longclickable有关,但是如果enabled为false是,则直接退出了该方法,则onClick无法触发 return (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE); } if (mTouchDelegate != null) { if (mTouchDelegate.onTouchEvent(event)) { return true; } } //onTouchEvent的执行跟clickable跟longclickable有关, //其实跟enable也有关系,因为enabled为false时直接退出方法,下面这段代码则无法执行 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();//调用onClick方法 } } } 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 feedback right away setPressed(true, x, y); checkForLongClick(0); } 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; }
又是一百多行代码,继续看吧。
1)前面都是一些跟主流程无关的代码,我们直接看第6-17行
这段代码主要就是判断view的enabled为false还是true,通过源码可以知道,view的enable不管为true或者false都不影响onTouchEvent方法的返回,但是影响执行过程,onTouchEvent的返回跟clickable跟longclickable有关,但是如果enabled为false是,则直接退出了该方法,则onClick无法触发。
2)继续看24-28行
当clickable跟longclickable都为true是,则执行switch方法块,可知onTouchEvent的执行跟clickable跟longclickable有关,其实跟enable也有关系,因为enabled为false时直接退出方法,下面这段代码则无法执行。所以有的时候,你的view的onClick事件监听不到,看看是否设置了这三个属性其中之一为false。
3)继续看54-62行,通知回调onClick方法
使用Runnable并发布,而不是直接调用performClick。 这使得视图的其他可视状态在点击操作开始之前更新。performClick源码如下所示:
/** * Call this view's OnClickListener, if it is defined. Performs all normal * actions associated with clicking: reporting accessibility event, playing * a sound, etc. * * @return True there was an assigned OnClickListener that was called, false * otherwise is returned. */ 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; }
跟前面一样,li不为空,而且不难理解li.mOnClickListener肯定是通过setOnClickListener(@Nullable OnClickListener l)设置的。
public void setOnClickListener(@Nullable OnClickListener l) { if (!isClickable()) { setClickable(true);//默认调用该方法,clickable都设置成为了true } getListenerInfo().mOnClickListener = l; }
当view设置了setOnClickListener方法,则performClick()方法会回调onClick方法。而且performClick()只在MotionEvent.ACTION_UP点击事件中执行,也就是手指离开屏幕时才执行。
3.总结
1.dispatchTouchEvent方法中,设置了setOnTouchListener跟setOnClickListener方法的时候,先执行onTouch方法,再执行onClick方法。2.如果onTouch方法返回true,就不执行onTouchEvent(event)方法,反之,则执行onTouchEvent(event)方法。所以一般如果我们在自己的view中设置了setOnTouchListener方法后,并且返回值设置成true后,则当前view就接受不到onTouchEvent(event)方法。
3.view的enable不管为true或者false都不影响onTouchEvent方法的返回,但是影响执行过程,onTouchEvent的返回跟clickable跟longclickable有关,但是如果enabled为false是,则直接退出了dispatchTouchEvent方法,则onClick无法触发,所以有的时候,你的view的onClick事件监听不到,看看是否设置了这三个属性其中之一为false。
4.我们主动调用performClick触发点击时,其实就是做的MotionEvent.ACTION_UP执行的事情,作用就是接口回调而已。
如有错误欢迎指出来,一起学习。
交流讨论群
群号:469890293
相关文章推荐
- 使用C++实现JNI接口需要注意的事项
- Android IPC进程间通讯机制
- Android Manifest 用法
- [转载]Activity中ConfigChanges属性的用法
- Android之获取手机上的图片和视频缩略图thumbnails
- Android之使用Http协议实现文件上传功能
- Android学习笔记(二九):嵌入浏览器
- android string.xml文件中的整型和string型代替
- i-jetty环境搭配与编译
- android之定时器AlarmManager
- android wifi 无线调试
- Android Native 绘图方法
- Android java 与 javascript互访(相互调用)的方法例子
- android 代码实现控件之间的间距
- android FragmentPagerAdapter的“标准”配置
- Android"解决"onTouch和onClick的冲突问题
- android:installLocation简析
- android searchView的关闭事件