Android中的view全解析(四)
来源:互联网 发布:办公室软件2013下载 编辑:程序博客网 时间:2024/06/05 19:14
最后,我们来看一下View的事件分发机制。
当我们对一个View进行点击时(Button也好,ImageView也好),首先会调用View的dispatchTouchEvent方法,方法的代码如下:public boolean dispatchTouchEvent(MotionEvent event) {
if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
mOnTouchListener.onTouch(this, event)) {
return true;
}
return onTouchEvent(event);
}
先看if中的语句,
1. mOnTouchListener != null View被设置了OnTouchListener
2. (mViewFlags & ENABLED_MASK) == ENABLED View的状态是enabled,enabled的状态表明View可以接收事件
3. mOnTouchListener.onTouch(this, event) onTouch方法返回值为true
当以上3个条件都成立时,返回值为true。否则,就执行onTouchEvent方法,返回该方法的返回值。
下面是onTouchEvent方法的具体代码:
public boolean onTouchEvent(MotionEvent event) { final int viewFlags = mViewFlags; if ((viewFlags & ENABLED_MASK) == DISABLED) { // 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)); } if (mTouchDelegate != null) { if (mTouchDelegate.onTouchEvent(event)) { return true; } } if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) { switch (event.getAction()) { case MotionEvent.ACTION_UP: boolean prepressed = (mPrivateFlags & PREPRESSED) != 0; if ((mPrivateFlags & 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 (!mHasPerformedLongPress) { // 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) { mPrivateFlags |= PRESSED; refreshDrawableState(); postDelayed(mUnsetPressedState, ViewConfiguration.getPressedStateDuration()); } else if (!post(mUnsetPressedState)) { // If the post failed, unpress right now mUnsetPressedState.run(); } removeTapCallback(); } break; case MotionEvent.ACTION_DOWN: if (mPendingCheckForTap == null) { mPendingCheckForTap = new CheckForTap(); } mPrivateFlags |= PREPRESSED; mHasPerformedLongPress = false; postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout()); break; case MotionEvent.ACTION_CANCEL: mPrivateFlags &= ~PRESSED; refreshDrawableState(); removeTapCallback(); break; case MotionEvent.ACTION_MOVE: final int x = (int) event.getX(); final int y = (int) event.getY(); // Be lenient about moving outside of buttons int slop = mTouchSlop; if ((x < 0 - slop) || (x >= getWidth() + slop) || (y < 0 - slop) || (y >= getHeight() + slop)) { // Outside button removeTapCallback(); if ((mPrivateFlags & PRESSED) != 0) { // Remove any future long press/tap checks removeLongPressCallback(); // Need to switch from pressed to not pressed mPrivateFlags &= ~PRESSED; refreshDrawableState(); } } break; } return true; } return false; }
在该方法中,首先判断View是否是可点击的,或者可长点击的,如果是,进入switch判断,并在MotionEvent_UP事件中调用了performClick方法,在performClick方法中判断,如果View设置了OnClickListener,就会回调执行了onClick方法。
但要注意一点,如果View是可点击的或者是可长点击的,一旦进入了if的语句段,最终的返回值为true。这是什么意思呢?如果我们在onTouch中的返回值为false,那么只要这个View是可点击的,最终的返回值也为true。
这里就要区分一下,onTouch返回值中的含义了。如果onTouch返回为true,表明View的onTouch方法将此事件消费掉了,不再向下传递,并且将继续监听View的其他事件。由于onTouch方法将事件消费掉了,那么onClick方法和onLongClick方法都无法接受到该事件了。如果onTouch方法返回为false,表明onTouch方法没有消费该事件,事件将会向下继续传递给onClick和onLongClick方法,事件将会被onClick或者onLongClick方法消费,于是返回true,表明View的onClick和onClick方法还将继续接收事件,而由于onTouch方法接收事件在onClick和onLongClick之前,所以它也会再继续接受事件。
总结一下,这个最终的返回值为true或者false并不是由onTouch方法决定的,而是由事件是否被View所消费决定的。如果View是可点击的,那么它总能消费事件,返回值也始终为true。每个ENABLED状态的View都可接收事件,默认均可被触摸,所以要在onTouch方法中返回值来判断是否要消费该事件。此时要注意区分接收事件和消费事件的区别。
上面分析了View的事件分发机制,下面来看一看ViewGroup中的事件分发。
首先,ViewGroup中也有dispatchTouchEvent方法,我们来看一看它的具体实现。
public boolean dispatchTouchEvent(MotionEvent ev) { final int action = ev.getAction(); final float xf = ev.getX(); final float yf = ev.getY(); final float scrolledXFloat = xf + mScrollX; final float scrolledYFloat = yf + mScrollY; final Rect frame = mTempRect; boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; if (action == MotionEvent.ACTION_DOWN) { if (mMotionTarget != null) { mMotionTarget = null; } if (disallowIntercept || !onInterceptTouchEvent(ev)) { ev.setAction(MotionEvent.ACTION_DOWN); final int scrolledXInt = (int) scrolledXFloat; final int scrolledYInt = (int) scrolledYFloat; final View[] children = mChildren; final int count = mChildrenCount; for (int i = count - 1; i >= 0; i--) { final View child = children[i]; if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) { child.getHitRect(frame); if (frame.contains(scrolledXInt, scrolledYInt)) { final float xc = scrolledXFloat - child.mLeft; final float yc = scrolledYFloat - child.mTop; ev.setLocation(xc, yc); child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT; if (child.dispatchTouchEvent(ev)) { mMotionTarget = child; return true; } } } } } } boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) || (action == MotionEvent.ACTION_CANCEL); if (isUpOrCancel) { mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT; } final View target = mMotionTarget; if (target == null) { ev.setLocation(xf, yf); if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) { ev.setAction(MotionEvent.ACTION_CANCEL); mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT; } return super.dispatchTouchEvent(ev); } if (!disallowIntercept && onInterceptTouchEvent(ev)) { final float xc = scrolledXFloat - (float) target.mLeft; final float yc = scrolledYFloat - (float) target.mTop; mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT; ev.setAction(MotionEvent.ACTION_CANCEL); ev.setLocation(xc, yc); if (!target.dispatchTouchEvent(ev)) { } mMotionTarget = null; return true; } if (isUpOrCancel) { mMotionTarget = null; } final float xc = scrolledXFloat - (float) target.mLeft; final float yc = scrolledYFloat - (float) target.mTop; ev.setLocation(xc, yc); if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) { ev.setAction(MotionEvent.ACTION_CANCEL); target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT; mMotionTarget = null; } return target.dispatchTouchEvent(ev); }
注意到,代码中有这么一个判断句:
if (disallowIntercept || !onInterceptTouchEvent(ev))
if中有两个条件,只要满足其一,就会进入下面的代码段。
1. disallowIntercept ViewGroup是否禁用掉事件拦截功能,true表示禁用,false表示不禁用。
2. !onInterceptTouchEvent(ev) ViewGroup是否拦截该事件。该条件对onInterceptTouchEvent方法的返回值进行了取反操作,如果onInterceptTouchEvent方法的返回值为false,即不拦截,那么该条件成立。
总结一下,如果ViewGroup禁用掉了事件拦截功能,或者ViewGroup没有对该事件进行拦截,那么将进入下面的代码段。下面的代码段是在做什么呢?
for (int i = count - 1; i >= 0; i--) { final View child = children[i]; if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) { child.getHitRect(frame); if (frame.contains(scrolledXInt, scrolledYInt)) { final float xc = scrolledXFloat - child.mLeft; final float yc = scrolledYFloat - child.mTop; ev.setLocation(xc, yc); child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT; if (child.dispatchTouchEvent(ev)) { mMotionTarget = child; return true; } } }看这个for循环应该就很明白了,它获取了ViewGroup中可以接收该事件的childView,将事件向下传递了。childView接收到了该事件后,该怎么办呢?下面是for循环中的if语句。
if (child.dispatchTouchEvent(ev)) { mMotionTarget = child; return true; }
有if中的条件可见,如果childView将事件消费掉了,那么ViewGroup的dispatchTouchEvent方法直接返回true,ViewGroup不再处理该事件。否则,将继续执行下面的代码段,由ViewGroup来处理该事件。
总结:
1. Android中的事件传递是由ViewGroup开始在dispatchTouchEvent方法中向下传递,先传递到ViewGroup,再由ViewGroup决定是否传递到View。
2. 如果ViewGroup的onInterceptTouchEvent将事件拦截了,或者childView没有将事件消费掉,ViewGroup将处理该事件。
3. 如果childView处理了该事件,那么ViewGroup便不会处理该事件。
0 0
- Android中的view全解析(四)
- Android中的View全解析(一)
- Android中的View全解析(二)
- Android中的View全解析(三)
- android中的动画全解析
- Android动画全解析(四)
- android自定义控件(四) View中的方法
- android自定义控件(四) View中的方法
- android自定义控件(四) View中的方法
- android自定义控件(四) View中的方法
- android自定义控件(四) View中的方法
- Android事件分发、View事件Listener全解析
- Android事件分发、View事件Listener全解析
- Android View 全解析(一) -- 窗口管理系统
- Android中的广播使用全解析
- Android中的Service使用全解析
- Android中的串口通讯全解析
- zencart全解析(四)
- Node — Koa2
- [Linux][PHP] Shell 命令清除error_log
- 喵哈哈村的秘境探险(三)-(记忆化搜索)->经典滑雪问题
- 基于Ubantu系统下tensorflow安装可能出现的问题
- linux里的mysql源码安装
- Android中的view全解析(四)
- git与github基础使用之--从远程库克隆
- Python+Selenium 持续化传递Cookie登陆淘宝 实践
- Unreal Engin_画廊制作笔记 _005<灯光处理,平行光的设置>
- 797E
- html5 video标签
- Web server, WSGI和Web framework
- 有关百度编辑器的内容设置
- mybatis原始dao开发改进(dao接口和dao实现类)