Android View的事件分发机制(一):View
来源:互联网 发布:php 数组函数 编辑:程序博客网 时间:2024/06/05 04:34
参考资料:http://blog.csdn.net/guolin_blog/article/details/9097463
官方View的树状图:
View的子类:ImageView、TextView、Button …… 很多
解析View的onClick和OnTouch的处理机制
以一个可点击的控件为例:button
在Activity中给button注册一个点击事件:
button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Log.e("button:", "onClick"); } });
再给button注册一个触摸的事件,即touch事件
button.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { Log.e("button:", "onTouch" + event.getAction()); return false; } });
onTouch事件会执行手指按下、手指移动、手指抬起这三个事件。
如果onClick和onTouch事件都注册了,那先执行那个呢?我们编个demo,运行一下就知道了,运行结果如下:
onTouch后面的数字含义:0–ACTION_DOWN;1–ACTION_UP;2–ACTION_MOVE。
由此可见,先执行onTouch,然后传递给onClick。(onTouch执行了多次是因为手指可能抖动了)
我们可以看到在onTouch方法里有返回值,且为false,那如果设置为true,会是什么样的结果呢,测试结果如下:
没有执行onClick方法。
总结:onTouch返回false,事件可以传递给onClick方法;onTouch返回true,事件不会传递给onClick方法。
那这是为什么呢?接下来,我们就用源码说话。
首先,我们触摸到任何一个控件,都会调用该控件的dispatchTouchEvent()方法,我们找到这个方法看一下 :
如图所示,我们在View中找到了这个方法(button继承TextView,TextView继承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(); } if (onFilterTouchEventForSecurity(event)) { //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; } } 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; }
我们主要看31–43行这段代码。我们看下这句:
if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED&& li.mOnTouchListener.onTouch(this, event))
这里面总共有四个条件,我们看后面三个:
①、li.mOnTouchListener!=null,这个只要们给控件设置了触摸监听器,即button.setOnTouchListener(new OnTouchListener()…),这就不可能是null;
②、(mViewFlags&ENABLED_MASK)==ENABLED,这个意思应该就是控件是enabled的,即可点击的,如:button是可点击的,TextView就是不可点击的(不过可以在布局里面设置 android:clickable=”true”就成可点击的了)
③li.mOnTouchListener.onTouch(this,event),这个方法就是我们自己实现的onTouch()方法,我们知道先执行onTouch,然后传递给onClick。所以这个条件的真假我们是可以控制的,我们先看源码中的19行:boolean result = false,即result的初始值为false;onTouch的返回值分析如下:
若是false,则result = true,不会执行,这时result的值还是false,则第40行 if (!result && onTouchEvent(event))中的!result就是true,然后onTouchEvent(event)就可以执行了,然后事件就传递给onClick了,即onClick方法在onTouchEvent()方法中。
若是true,则三个条件成立,result = true就会执行,这时result的值为ture,则第40行if (!result && onTouchEvent(event))中的!result就是false,那onTouchEvent(event)就不会执行了,所以onClick方法没有执行。
由上述可知,onClick方法在onTouchEvent方法中,接下来我们就看下onTouchEvent()方法的源码:
/** * Implement this method to handle touch screen motion events. * <p> * If this method is used to detect click actions, it is recommended that * the actions be performed by implementing and calling * {@link #performClick()}. This will ensure consistent system behavior, * including: * <ul> * <li>obeying click sound preferences * <li>dispatching OnClickListener calls * <li>handling {@link AccessibilityNodeInfo#ACTION_CLICK ACTION_CLICK} when * accessibility features are enabled * </ul> * * @param event The motion event. * @return True if the event was handled, false otherwise. */ 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 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; }
只看重点:在41行,我们就可以看出,如果该控件可以点击,就会进入switch()语句,当用户抬起手指时,就会进入 case MotionEvent.ACTION_UP:这个语句,经过各种判断,会执行到76行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; } return result; }
我们看12行,只要li != null && li.mOnClickListener != null,我们就会执行onClick方法,对于一个要点击的控件,我们肯定会给他设置onClickListener的,即button.setOnClickListener(new OnClickListener()…)。这样的话,li != null && li.mOnClickListener != null这个条件肯定成立,即onClick方法能够执行。
注意:
1、如果是不可点击的控件(如:TextView)注册了onTouch事件,如下:
textView.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { Log.e("TextView:", "onTouch" + event.getAction()); return false; } });
测试结果,如下:
只有一条数据,且onTouch后面的数字是0,表示是ACTION_DOWN;那为什么没有ACTION_MOVE、ACTION_UP呢?这是因为TextView是不可点击的,到onTouchEvent源码的41行时,就进不去了,直接跳到154行返回false了,即后面的ACTION_MOVE、ACTION_UP就不能执行了。
解决方法:
① 只要在TextView的布局文件中加:android:clickable=”true”就可以了,ACTION_DOWN、ACTION_MOVE、ACTION_UP都可以执行了。或者重写onTouchEvent方法
② 在onTouch方法中,返回true就可以了
2、为什么给ListView引入了一个滑动菜单的功能,ListView就不能滚动了?
你应该会知道滑动菜单的功能是通过给ListView注册了一个touch事件来实现的。如果你在onTouch方法里处理完了滑动逻辑后返回true,那么ListView本身的滚动事件就被屏蔽了,自然也就无法滑动(原理同前面例子中按钮不能点击),因此解决办法就是在onTouch方法里返回false。
- Android View的事件分发机制(一):View
- Android----View事件分发机制(一)
- View 事件的分发机制 (一)
- View 的事件分发机制(一)
- 【Android学习】View点击事件的分发机制(一)
- Android View的事件分发机制(一)
- Android自定义View的事件分发机制(一)
- Android View事件分发机制 一(View)
- 笔记:事件分发机制(一):View的事件分发
- android之View和ViewGroup事件分发机制分析(一)(View的事件分发机制)
- Android View、ViewGroup 事件分发机制(一)
- Android读书笔记---View事件的分发机制(一)
- 一图详解Android View的事件分发机制
- View的事件分发机制(一)
- android事件分发机制 VIew的事件分发机制
- Android进阶笔记(一)View事件分发机制理解
- View事件分发机制(一)
- Android事件分发机制源码解析(一)-View的事件分发机制
- 自动释放池详解
- 《Effective C++》35:考虑virtual函数以外的其他选择
- glDepthFunc
- spark&hbase integration
- 同时跑两个TOMCAT
- Android View的事件分发机制(一):View
- JVM系列五:JVM监测&工具
- iOS开发之Bug汇总
- 一个IC卡数据准备tags解析器
- Redis系列-存储篇hash主要操作函数小结
- IO和NIO
- 引用sun.management.ManagementFactory出错的解决办法
- Android之Handler用法总结
- CentOS 安装和配置OpenVPN