View触摸事件源码分析2
View触摸事件源码分析
View的dispatchTouchEvent方法的省略版源码
public boolean dispatchTouchEvent(MotionEvent event) {
//dispatchTouchEvent的返回值
boolean result = false;
//...balabala省略前面部分代码
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
//判断是否有onTouchListener监听,如果有,执行listener的
//onTouch方法,
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
//如果onTouch方法返回false,继续执行onTouchEvent方法
if (!result && onTouchEvent(event)) {
result = true;
}
}
return result;
}
通过上面的代码可以得出以下结论:
1 如果View
有onTouchListener
,则onTouch
方法优于onTouchEvent
方法先执行
2 如果 View
的onTouchListener
的onTouch
方法返回了true
,则onTouchEvent
方法不会执行
3 如果View
有onTouchListener
,并且onTouch
方法返回了true
,dispatchTouchEvent
方法返回true
。如果onTouch
返回了false
或者没有onToucheListener
,则dispatchTouchEvent
方法和onTouchEvent
的返回值一致。
ViewGroup dispatchTouchEvent
方法的省略版源码
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
//dispatchTouchEvent方法的返回值
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
// Check for interception.是否拦截标志
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
//调用拦截方法,该方法默认返回false
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was chang
} else {
intercepted = false;
}
} else {
intercepted = true;
}
//如果没有拦截
if (!intercepted) {
final View[] children = mChildren;
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
}
}
}
}
return handled;
}
dispatchTransformedTouchEvent
方法
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
final int oldAction = event.getAction();
final int oldPointerIdBits = event.getPointerIdBits();
final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;
if (child == null) {
//调用View的dispatchTouchEvent方法,View的dispatchTouchEvent方法如果执行则看前面分析View的dispatchTouchEvent方法源码
handled = super.dispatchTouchEvent(transformedEvent);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
transformedEvent.offsetLocation(offsetX, offsetY);
if (! child.hasIdentityMatrix()) {
transformedEvent.transform(child.getInverseMatrix());
}
//调用子view的dispatchTouchEvent方法
handled = child.dispatchTouchEvent(transformedEvent);
}
// Done.
transformedEvent.recycle();
return handled;
}
ViewGroup
的dispatchTouchEvent
方法比较复杂,主要是它是个控件容器,可能有很多子控件,要考虑事件如何传递,传递给哪个子控件,该不该传递给子控件等等多种情况。
结论:
1 ViewGroup
里面新增了一个拦截方法onInterceptTouchEvent
,控制该事件要不要传递给它的子控件。
2 若onInterceptTouchEvent
方法返回true,则子控件接收不到事件即dispatchTouchEvent
方法不会调用,而会调用父类View 的dispatchTouchEvent
方法。若返回false,则会子控件的dispatchTouchEvent
方法
现测试检验查看
MyView
继承View
,里面添加onTouchListener
监听.MyViewGroup
继承ViewGroup
.在相应的方法里面添加打印语句,返回值全都默认值,经上面分析可知道的,猜想得知执行顺序MyViewGroup.dispatchTouchEvent->MyViewGroup.onInterceptTouchEvent->MyView.dispatchTouchEvent->MyView.onTouch->MyView.onTouchEvent
打印结果如下图,
符合猜想;
1 小结:
View和ViewGroup的dispatchTouchEvent默认返回都为false;
ViewGroup默认onInterceptTouchEvent返回也为false;
action为0表示为当前事件为ACTION_DOWN事件;
but,MOVE和UP事件跑哪去了....慢慢往下看;
其他都是默认值 现在改变View
的监听onTouch
返回值为true
;
这时候View的onTouchEvent
应该就得不到执行了;
打印结果如下图,
发现打印日志就多了很多,action
为2表示ACTION_MOVE
事件,等于1表示ACTION_UP
事件;
2小结:
经上面分享源码得知,此时MyView
的dispatchTouchEvent
方法返回的也是true
,由日志也可看出;
由打印日志可以看出,MyViewGroup
的dispatchTouchEvent
方法也返回了true
;
可以看出MyViewGroup
的dispatchTouchEvent
方法返回了true,才有了后续的ACTION_MOVE
和ACTION_UP
事件;
注意看,此时MyViewGroup
的onTouchEvent
方法也不会执行了....Why...继续往下慢慢分析
其他都是默认值 改变View
的dispatchTouchEvent
方法返回true;
这个和上面那个日志的区别就是会执行View的onTouchEvent方法,其他的都是一样的;
打印如下;
其他都是默认值,View的onTouchEvent
返回true;
日志和上面的差不多,差别就是前面View的onTouchEvent
返回false,这里都是true;
没有设置dispatchTouchEvent
的返回值,但是它返回的是true;
前面分析过了,这种情况dispatchTouchEvent
的返回值和onTouchEvent
是一样的;
依然ViewGroup
的onTouchEvent
方法没有执行;
3小结
通过小结1 和后面的对比发现,如果View的dispatchTouchEvent
返回值为true,则ViewGroup
的onTouchEvent
就不会执行了;
而想要View
的dispatchTouchEvent
返回值为true
,除了复写该方法在dispatchTouchEvent
里面返回true之外,在则可以设置一个监听onTouchListener
返回true
,或者在onTouchEven
t里面返回true;
平常我们用的最多的是复写onTouchEvent
方法;
再看View onTouchEvent方法源码;
public boolean onTouchEvent(MotionEvent event) {
//前面省略balabala代码....
final int action = event.getAction();
//还可以设置touch代理
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) !=
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
// This is a tap, so remove the longpress check
removeLongPressCallback();
if (!focusTaken) {
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
//调用onClickListener回调
if (!post(mPerformClick)) {
performClick();
}
}
}
break;
case MotionEvent.ACTION_DOWN:
mHasPerformedLongPress = false;
if (isInScrollingContainer) {
//检测是否长按
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_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;
}
//这里直接返回true了
return true;
}
return false;
}
初看一下既然返回false了,那么switch里面应该就不会走了;
突发奇想,既然switch都不会走了,难道onClickListener
也不会走了???
MyView
和MyViewGroup
所有返回值都默认,添加如下代码,设置了onClick
监听
结果Toast
神奇的弹出来了!!!
和上面预期的不一样啊,既然switch
都不走了,那为啥onClick
方法会被调用?
打印如下;
View
的onTouchEvent,dispatchTouchEvent
返回值为true
;
ViewGroup
的dispatchTouchEvent
返回为true
了。
就是一个加setOnclickListener
和不加的区别;
点进去这个方法看;
前面有个setClickable
顿时恍然大悟,如梦出醒;
再看onTouchEvent
方法
也就是默认这个值是false
,如果这个值是false
,那onTouchEvent
方法也就直接返回false
.这也是导致了dispatchTouchEvent
方法返回了false
;
google工程师用一个标志viewFlags
判断当前是否可点击或者长按或者是contextClickable
;
点击和长按是在onTouchEvent
里面处理的,这个onContextClickListener
在onTouchEvent
里面没看到被调用,用的也比较少,暂时略过,以后发现了用处再来补充
4 小结
在不设置setClickable
,或者setLongClickable
情况下,onTouchEvent
返回false
;
若设置setClickable
,或者setLongClickable
为true
,则onTouchEvent
也返回true
;
可以通过代码设置,也可以在布局文件里面直接设置Clickable
和LongClickable
;
继续测试,所有值默认,ViewGroup
的dispatchTouchEvent
返回true
日志打印如下图
图片15.png5 小结
由于View
的onTouchEvent
返回了false
,所以ViewGroup
的onTouchEvent
才会被调用;
由于View
的onTouchEvent
在ACTION_DOWN
就返回了false
,所以后续的ACTION_MOVE
和ACTION_UP
也就没有接收到;
即时ViewGroup
的onTouchEvent
在ACTION_DOWN
返回了true
,他的后续MOVE
和UP
也能接收到;
现改成如下方式;
图片16.png输出日志如下:
图片17.png6 小结
ViewGroup
的dispatchTouchEvent
方法里面只要ACTION_DOWN
返回了true,其他的MOVE和UP
事件不受影响
继续测试,onInterceptTouchEven
返回true
,代码如下
由前面分析ViewGroup
源码得知,此时View
接收不到任何事件的;
输入日志如下
View里面onTouchEvent
返回true
ViewGroup onInterceptTouchEvent
的MOVE
事件里面返回true
打印日志如下
图片22.png
图片23.png
这个时候View onTouchEvent
方法只接收到了DOWN
和CANCEL
事件;
ViewGroup
接收到了MOVE
和UP
事件
小结:
ViewGroup若在MOVE
方法返回true
,子控件只能接收到DOWN
和CANCEL
事件。ViewGroup
可以接收到MOVE
和UP
事件
现在在ViewGroup
的UP
事件上拦截
输出日志如下
图片25.png 图片26.png
View
接收到onTouchEvent
方法接收到DOWN
和MOVE和CANCEL
事件;
ViewGroup
的dispatchTouEvent
里面可以接收DOWN,MOVE
,和UP事件,onTouchEvent
没有执行;
总结
1 如果一个控件能够接受到事件则最新执行的方法肯定是dispatchTouEvent
方法;
2 如果一个控件设置了onTouchLister
,则onTouchListener
优于onTouchEvent
方法执行;
3 有onTouchListener
的情况下,如果onTouchListener
返回true,则dispatchTouEvent
的返回值也为true,并且onTouchEvent
方法不会再执行;
4 没onTouchListener或者
onTouchListener返回false的情况下,
dispatchTouEvent的返回值和onTouchEvent是一样的; 5
ACTION_DOWN,ACTION_MOVE,ACTION_UP事件是一系列连续的,如果某个事件返回了false,则后续事件也不再调用 6 父容器控件的
onInterceptTouchEvent方法也是如此,如果某个事件返回了true,则子控件会接收到
ACTION_CANCEL事件,并且子控件后续事件也不在会执行。 7 字控件的
dispatchTouEvent方法如果能够执行,并且返回true.则父容器控件的
onTouchEvent`方法就不会执行。