Android事件分发(2)
来源:互联网 发布:学生双肩包推荐 知乎 编辑:程序博客网 时间:2024/05/29 14:31
上一篇主要讲了onTouch和onTouchEvent区别:
1、优先判断onTouch要不要执行
2、如果onTouch执行,返回ture则消费了事件,onTouchEvent不再执行
3、onTouch默认是null的,所以系统源代码 是在onTouchEvent里面识别和处理 点击,滑动,长按等事件的。
以上,是分析 View中dispatchTouchEvent方法的源码得等的结论
这次,看看View的onTouchEvent 里面都做了些什么事情。
Android事件分发机制完全解析,带你从源码的角度彻底理解(上) 中分析的源码是老一点版本的源码,我的是api22中的源码
一、View的onTouchEvent源码分析
public boolean onTouchEvent(MotionEvent event) { final float x = event.getX(); final float y = event.getY(); final int viewFlags = mViewFlags;//判断控件是否是 不可用 DISABLED 的,若果是,就返回一个结果。 //return语句判断了是否是CLICKABLE 或者 LONG_CLICKABLE的,如果是这两者,返回true,消耗掉了事件。换句话说,如果View不可用,并且 View可点击或者可长按,则消费掉事件, //这样会 不响应click或者 long click事件。 if ((viewFlags & ENABLED_MASK) == DISABLED) { if (event.getAction() == 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)); }//如果当前View设置了TouchDelegate 对象mTouchDelegate,则执行mTouchDelegate 的onTouchEvent事件,根据mTouchDelegate的返回结果,决定要不要继续执行自己的onTouchEvent逻辑。//Delegate 是代理的意思,简单来说,ViewA 代理了ViewB //如果是点击事件,你点击了ViewA ,就和点击了ViewB一样,ViewA 走的都是ViewB的代码判断逻辑。 if (mTouchDelegate != null) { if (mTouchDelegate.onTouchEvent(event)) { return true; } } //View是可点击或可长按的状态。判断为真 if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) { //View是可点击或可长按的状态,进入了switch判断 switch (event.getAction()) { case MotionEvent.ACTION_UP: boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0; //判断了按压press状态和焦点状态, 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); } //如果只是个tap,非长按事件移除了removeLongPressCallback也就是对于长按事件的检查线程。 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(); } //没有直接执行点击performClick ,而是使用post runable的方式执行 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(); } break; case MotionEvent.ACTION_DOWN: //先把执行长按的标记mHasPerformedLongPress 设置为false 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 延迟了一小段时间按下事件的检查反馈,延迟事件 getTapTimeout()是100毫秒 postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout()); } else { // Not inside a scrolling container, so show the feedback right away //没有在一个可滑动的容器内:设置按下状态和位置,马上显示反馈结果(延迟时间为0) setPressed(true, x, y); checkForLongClick(0); } break; case MotionEvent.ACTION_CANCEL: setPressed(false); removeTapCallback(); removeLongPressCallback(); break; case MotionEvent.ACTION_MOVE: //记录位置变化,传递给子view drawableHotspotChanged(x, y); // Be lenient about moving outside of buttons //判断了是否移动到了View以外,如果仍然在View区域内,移除了点击和长按的检查线程 if (!pointInView(x, y, mTouchSlop)) { // Outside button removeTapCallback(); if ((mPrivateFlags & PFLAG_PRESSED) != 0) { // Remove any future long press/tap checks removeLongPressCallback(); setPressed(false); } } break; } //View是可点击或可长按的状态,if为真. //可以看到,整个if的最终返回值是true代表处理了此次事件。 //也就是说,事件分发到我这个View的时候,如果我这个View是可点击或者可长按的,我就会消费此次事件,停止分发 return true; } //如果View是不可点击状态,整个onTouchEvent 会返回false。 //也就是,我这个View是不可点击也不可长按的,那么我这个View不关心发生了什么事情,我也不做处理。 return false; }
分部分分析和解释:
1、第一个if
if ((viewFlags & ENABLED_MASK) == DISABLED) { if (event.getAction() == 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)); }
判断控件是否是 不可用 DISABLED 的,若果是,就返回一个结果。
return语句判断了是否是CLICKABLE 或者 LONG_CLICKABLE的,如果是这两者,返回true,消耗掉了事件。换句话说,如果View不可用,并且 View可点击或者可长按,则消费掉事件,
这样会 不响应click或者 long click事件。
2.第二个if,对于TouchDelegate的判断。
if (mTouchDelegate != null) { if (mTouchDelegate.onTouchEvent(event)) { return true; } }
如果当前View设置了TouchDelegate 对象mTouchDelegate,则执行mTouchDelegate 的onTouchEvent事件,根据mTouchDelegate的返回结果,决定要不要继续执行自己的onTouchEvent逻辑。TouchDelegate 的使用可以看这里
Delegate 是代理的意思,简单来说,ViewA 代理了ViewB
如果是点击事件,你点击了ViewA ,就和点击了ViewB一样,ViewA 走的都是ViewB的代码判断逻辑,并返回结果,根据返回结果判断是否消费事件。
3、第三个大的 if判断是对View 的点击状态进行的判断
if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE))
代码运行到这里,View一定是Enable的状态,此时又进行了,是否可点击状态的判断
A、先分析 if为false的情况,可以看到,如果View是不可点击状态,整个onTouchEvent 会返回false。也就是,我这个View是不可点击也不可长按的,那么我这个View不关心发生了什么事情,我也不做处理。
B、if为真,View是可点击或可长按的状态。
可以看到,整个if的最终返回值是true代表处理了此次事件。
也就是说,事件分发到我这个View的时候,如果我这个View是可点击或者可长按的,我就会消费此次事件,停止分发
C、if为true,进入了switch判断。
①case MotionEvent.ACTION_UP:
判断了按压press状态和焦点状态,如果只是个tap,移除了removeLongPressCallback也就是对于长按事件的检查线程。
没有直接执行点击performClick ,而是使用post runable的方式执行 performClick
②case MotionEvent.ACTION_DOWN:
先把执行长按的标记mHasPerformedLongPress 设置为false
// 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()); }
为了防止这个是滑动的操作,postDelayed 延迟了一小段时间按下事件的检查反馈,延迟事件 getTapTimeout()是100毫秒
没有在一个可滑动的容器内:
else { // Not inside a scrolling container, so show the feedback right away setPressed(true, x, y); checkForLongClick(0); }
设置按下状态和位置,马上显示反馈结果(延迟时间为0)
③case MotionEvent.ACTION_MOVE:
drawableHotspotChanged(x, y);记录位置变化,传递给子view
// 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); } }
判断了是否移动到了View以外,如果仍然在View区域内,移除了点击和长按的检查线程
以上就是View的onTouchEvent的源码分析,有些乱,但大体都解释到了
二、View的onTouchEvent的总结
View的onTouchEvent首先判断了View是否是可用的。
A.不可用但可点击,返回true,消费了事件
B.不可用但可长按,返回true,消费了事件View是可用的状态,进行了View是否设置了TouchDelegate 的判断
A. 如果设置了TouchDelegate ,则执行TouchDelegate 对象的onTouchEvent方法,根据返回值决定要不要继续执行- View 是可用状态,可点击或者可长按的状态,对MotionEvent对象进行判断。
A. 在MotionEvent.ACTION_UP 动作中,进行是长按还是点击的判断,并执行点击performClick()
B. MotionEvent.ACTION_DOWN动作中,判断了是否是一个可滑动容器
C.MotionEvent.ACTION_MOVE动作中,判断了是否仍在View区域内
三、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.mOnClickListener不为null,然后执行setOnClickListener 设置的点击事件。
public void setOnClickListener(@Nullable OnClickListener l) { if (!isClickable()) { setClickable(true); } getListenerInfo().mOnClickListener = l; }
而且可以看到,在setOnClickListener 里面,已经把View设置为Clickable可点击的了。
以上就是View的onTouchEvent执行的逻辑。
- Android事件分发(2)
- android事件分发(一)
- Android事件分发(三)
- android事件分发(二)
- Android事件分发 (一)
- Android事件分发(二)
- Android事件分发(二)
- android基础(事件分发)
- Android事件分发(1)
- Android事件分发机制(三)事件分发和消费
- Android事件分发(事件传递机制)
- Android 事件分发机制详解(2)-ViewGroup
- Android事件分发机制完全解析(2)
- android 事件分发传递机制(转)
- Android 事件分发机制(下)
- Android 事件分发机制(上)
- Android事件分发机制(资料收集)
- android 事件分发机制(View)
- IC系统验证分析概述
- 【maven】pom配置:用overlays合并多个war
- spring 学习个概念总结:
- 系统延时与定时任务
- EffectiveC++学习笔记-条款41|42
- Android事件分发(2)
- 实训--day22
- 抽象类和接口之一
- 【转】ionicLoading,ionic-spinner SVG旋转加载
- ajax中文乱码问题
- NOIP2015提高组简单题
- js闭包
- java学习初探2之语言基础
- js 之 in 运算符