View事件分发
来源:互联网 发布:惧内之滥觞乎 编辑:程序博客网 时间:2024/06/06 01:00
注:本文基于自己的理解,水平比较有限,欢迎指出不对的地方,不喜勿喷。
上文讲了ViewGroup事件分发,http://blog.csdn.net/zxd_android/article/details/78019600,本文讲下View的事件分发。
我们先来看下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)) { 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; } } 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; }
方法不长,我们看主要的代码。
if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) { result = true; }
如果我们设置了onTouhListener,并且当前View处于ENABLED状态,并且onTouch 返回true,设置result 为true,看上面的代码,如果result为true,onTouchEvent就不会执行,我们也能看到,onTouch监听事件或者onTouchEvent其中之一返回true,就表示该view处理事件分发。
接下来,我们看下View的onTouchEnent方法:
/** * 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, 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; }
1、第6行 if ((viewFlags & ENABLED_MASK) == DISABLED) 如果是DISABLED状态,直接return。
2、第13行 mTouchDelegate是代理事件处理的类,这里我们不做讨论。
3、 if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
(viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
这个if判断有点长,满足CLICLABLE或者LONG_CLICKABLE以及CONTEXT_CLICKABLE其中之一就进入方法内部,我们一般自定义View往往是不可以CLICKABLE 的,所以Touch事件接收不到的原因就在这里,像我们的原生控件Button是因为主题中设置android:clickable为true,所以我们自定义View时候必要的时候加上。
4、先看ACTION_DOWN事件,首先设置mHasPerformedLongPress标志位,表示是否长按,然后postDelayed一个runnable,延迟100ms执行,我们看下这个CheckForTap实现。
private final class CheckForTap implements Runnable { public float x; public float y; @Override public void run() { mPrivateFlags &= ~PFLAG_PREPRESSED; setPressed(true, x, y); checkForLongClick(ViewConfiguration.getTapTimeout(), x, y); } }
设置setPressed 为true,调用checkForLongClick方法。
private void checkForLongClick(int delayOffset, float x, float y) { if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) { mHasPerformedLongPress = false; if (mPendingCheckForLongPress == null) { mPendingCheckForLongPress = new CheckForLongPress(); } mPendingCheckForLongPress.setAnchor(x, y); mPendingCheckForLongPress.rememberWindowAttachCount(); postDelayed(mPendingCheckForLongPress, ViewConfiguration.getLongPressTimeout() - delayOffset); } }
首先new 一个Runnable,又推迟一个任务执行。我们再看下
private final class CheckForLongPress implements Runnable { private int mOriginalWindowAttachCount; private float mX; private float mY; @Override public void run() { if (isPressed() && (mParent != null) && mOriginalWindowAttachCount == mWindowAttachCount) { if (performLongClick(mX, mY)) { mHasPerformedLongPress = true; } } }
如果手指没有松开,isPressed()一直 为true,最终会触发onLongClick回调。
5、我们再看下ACTION_UP事件, if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) 会remove刚才的长按的runnable。
if (mPerformClick == null) { mPerformClick = new PerformClick(); }
这里面又是一个runnable,最终触发performClick方法,回调onclick。
- android 事件分发 View
- view 事件分发机制
- View Touch 事件分发
- View事件分发
- View 事件分发机制
- 事件分发之View
- View事件分发机制
- android View 事件分发
- View事件分发
- view事件分发机制
- View的事件分发
- View的事件分发
- android view 事件分发
- view事件分发
- View事件分发机制
- view事件分发机制
- view的分发事件
- view事件分发机制
- Python 爬虫-爬取pixiv特定搜索结果的所有作品-抓取 分析 下载
- Work Like Alibaba系列分享回顾整理(含演讲幻灯片、视频):持续更新中
- python连接redis单例模式
- 【Java基础】Math包之BigDecimal
- JVM自带性能分析工具介绍——jstat
- View事件分发
- C++ 字符、字符串大小写的转换
- 第一天
- php unset引用变量后不会删除值
- 论文理解 A Bayesian Hierarchical Model for Learning Natural Scene Categories
- eclipse不自动编译终极解决方法
- 用CombineFileInputFormat优化Hadoop小文件
- 详解css hack
- 人人都可以轻松使用的轻量服务器的评测