Android中dispatchTouchEvent, onInterceptTouchEvent, onTouchEvent的理解

onInterceptTouchEvent用于改变事件的传递方向。决定传递方向的是返回值,返回为false时事件会传递给子控件,返回值为true时事件会传递给当前控件的onTouchEvent(),这就是所谓的Intercept(拦截)。

[tisa ps:正确的使用方法是,在此方法内仅判断事件是否需要拦截,然后返回。即便需要拦截也应该直接返回true,然后由onTouchEvent方法进行处理。]

onTouchEvent用于处理事件,返回值决定当前控件是否消费(consume)了这个事件。尤其对于ACTION_DOWN事件,返回true,表示我想要处理后续事件;返回false,表示不关心此事件,并返回由父类进行处理。

可能你要问是否消费了又区别吗,反正我已经针对事件编写了处理代码?答案是有区别!比如ACTION_MOVE或者ACTION_UP发生的前提是一定曾经发生了ACTION_DOWN,如果你没有消费ACTION_DOWN,那么系统会认为ACTION_DOWN没有发生过,所以ACTION_MOVE或者ACTION_UP就不能被捕获。

在没有重写onInterceptTouchEvent()和onTouchEvent()的情况下(他们的返回值都是false), 对上面这个布局,MotionEvent事件的传递顺序如下:

当某个控件的onInterceptTouchEvent()返回值为true时,就会发生截断,事件被传到当前控件的onTouchEvent()。如我们将LayoutView2的onInterceptTouchEvent()返回值为true,则传递流程变成:

如果我们同时将LayoutView2的onInterceptTouchEvent()和onTouchEvent()设置成true,那么LayoutView2将消费被传递的事件,同时后续事件(如跟着ACTION_DOWN的ACTION_MOVE或者ACTION_UP)会直接传给LayoutView2的onTouchEvent(),不传给其他任何控件的任何函数。同时传递给子空间一个ACTION_CANCEL事件。传递流程变成(图中没有画出ACTION_CANCEL事件):

[tisa ps:总体来看, onInterceptTouchEvent是自rootview向下传递, onTouchEvent正好相反。]

正如命名一样,onInterceptTouchEvent用来拦截事件,onTouchEvent用来处理事件,网上大部分的文章中也都对这两类方法的使用情况进行了简单的说明。

事件传递如下图,逐级向下看onInterceptTouchEvent()是否需要截断事件,如果没发生截断,则逐级向上寻找能够处理该事件的onTouchEvent()。

源码上RelativeLayout等布局是继承ViewGroup,看源码上ViewGroup中的onInterceptTouchEvent却非常简单:

Java代码

publicbooleanonInterceptTouchEvent(MotionEvent ev) {

returnfalse;

}

如果不重写该方法直接就会return false。

如果在其中一层截断事件,让onInterceptTouchEvent()返回true,例如在上图中ChildLayout中发生截断,则事件传递则会向跳过MyView,直接从这一层进入onTouchEvent。若ChildLayout的onTouchEvent返回false,则传递流程如下图

很重要的一点:事件传递是这样的,你可以在传递到响应的方法里面做任何你想做的事情,这些事情不会改变事件传递,能影响事件传递的是这些方法的返回值。例如你在ChildLayout的onTouchEvent做了很多事情,只要返回了false,事件还是会进入到ParentLayout的onTouchEvent。

若保持ChildLayout的onInterceptTouchEvent返回true,将它的onTouchEvent返回true,则事件不会再进入其他组件的onTouchEvent,后续的时间会依次进入这个返回了true的onTouchEvent,传递则变为如下图:

自此说的内容都跟博客开头那个链接里面的文章类似。下面写一些那篇文章里面没有谈及或者不够全面的地方。下文中举例进行的操作都是拖动,即ACITON_DOWN -> ACTION_MOVE -> ACTION_UP

那篇文章里面说在事件发生截断的时候,会像子View发出ACTION_CANCEL,但是我在ChildLayout的onInterceptTouchEvent里面return true之后,却没有收到ACTION_CANCEL,经实验发现,谈谈关于ACTION_CANCEL的事情。ACTION_CANCEL的发出是有条件的:如果子一层曾经处理过事件,即事件进入到onTouchEvent中,则此时截断事件,上一层会向下一层发出ACTION_CANCEL。例如在ChildLayout的onInterceptTouchEvent中对event.getAction()进行判断,如果是ACTION_DOWN,返回false,在ACTION_MOVE的时候,返回true进行截断。则可以发现MyView接到了action为ACTION_DOWN的事件,但是当出现ACTION_MOVE在ChildLayout截断发生时,MyView则收到了ACTION_CANCEL的消息。代码如下:

ChildLayout

Java代码

@Override

publicbooleanonInterceptTouchEvent(MotionEvent ev) {

booleanresult =super.onInterceptTouchEvent(ev);

switch(ev.getAction()) {

caseMotionEvent.ACTION_DOWN:

Log.i("ZZZZ","ChildLayout onInterceptTouchEvent ACITON_DOWN:");

break;

caseMotionEvent.ACTION_MOVE:

Log.i("ZZZZ","ChildLayout onInterceptTouchEvent ACITON_MOVE:");

result =true;

break;

caseMotionEvent.ACTION_CANCEL:

Log.i("ZZZZ","ChildLayout onInterceptTouchEvent ACITON_CANCEL:");

break;

caseMotionEvent.ACTION_UP:

Log.i("ZZZZ","ChildLayout onInterceptTouchEvent ACITON_UP:");

break;

}

Log.i("ZZZZ","ChildLayout onInterceptTouchEvent return "+result);

returnresult;

}

@Override

publicbooleanonTouchEvent(MotionEvent ev) {

booleanresult =super.onTouchEvent(ev);

switch(ev.getAction()) {

caseMotionEvent.ACTION_DOWN:

Log.i("ZZZZ","ChildLayout onTouchEvent ACTION_DOWN");

break;

caseMotionEvent.ACTION_MOVE:

Log.i("ZZZZ","ChildLayout onTouchEvent ACTION_MOVE");

break;

caseMotionEvent.ACTION_CANCEL:

Log.i("ZZZZ","ChildLayout onTouchEvent ACTION_CANCEL");

caseMotionEvent.ACTION_UP:

Log.i("ZZZZ","ChildLayout onTouchEvent ACTION_UP");

break;

}

result =true;

Log.i("ZZZZ","ChildLayout onTouchEvent return "+result);

returnresult;

}

为了增加事件的复杂性,挑选了有滚动效果的GridView在ChildLayout下一层的MyView作为实验对象

MyGridView

Java代码

@Override

publicbooleanonInterceptTouchEvent(MotionEvent ev) {

booleanresult =super.onInterceptTouchEvent(ev);

switch(ev.getAction()) {

caseMotionEvent.ACTION_DOWN:

Log.d("ZZZZ","MyGridView onInterceptTouchEvent ACITON_DOWN:");

break;

caseMotionEvent.ACTION_MOVE:

Log.d("ZZZZ","MyGridView onInterceptTouchEvent ACITON_MOVE:");

break;

caseMotionEvent.ACTION_CANCEL:

Log.d("ZZZZ","MyGridView onInterceptTouchEvent ACITON_CANCEL:");

break;

caseMotionEvent.ACTION_UP:

Log.d("ZZZZ","MyGridView onInterceptTouchEvent ACITON_UP:");

break;

}

Log.d("ZZZZ","MyGridView onInterceptTouchEvent return "+result);

returnresult;

}

@Override

publicbooleanonTouchEvent(MotionEvent ev) {

booleanresult =super.onTouchEvent(ev);

switch(ev.getAction()) {

caseMotionEvent.ACTION_DOWN:

Log.d("ZZZZ","MyGridView onTouchEvent ACTION_DOWN");

break;

caseMotionEvent.ACTION_MOVE:

Log.d("ZZZZ","MyGridView onTouchEvent ACTION_MOVE");

break;

caseMotionEvent.ACTION_CANCEL:

Log.d("ZZZZ","MyGridView onTouchEvent ACTION_CANCEL");

break;

caseMotionEvent.ACTION_UP:

Log.d("ZZZZ","MyGridView onTouchEvent ACTION_UP");

break;

}

Log.d("ZZZZ","MyGridView onTouchEvent return "+result);

returnresult;

运行日志:

再次修改代码,取消ChildLayout里onInterceptTouchEvent对事件的拦截返回false,让MyGridView的onTouchEvent方法return super.onTouchEvent(ev)默认返回true。

在同样尝试拖动效果的时候,按照上面的说法,理论上在ParentLayout,ChildLayout,MyGridView都不拦截事件return false的状态下,事件会通过依次通过ParentLayout,ChildLayout,MyGridView的onInterceptTouchEvent,然后到达MyGridView的onTouchEvent,但是我们发现事实并不是如此,移动一段距离后,就没有任何onInterceptTouchEvent执行了。运行效果如下:

说好的依次传递并没有发生,无奈只能去源码中寻找原因,GridView继承于AbsListView,与之前ChildLayout,ParentLayout继承与ViewGroup不同,在AbsListView中的onTouchEvent的ACTION_MOVE的这个case中看到有个startScrollIfNeeded方法,点进去才发现有方法说明“Check if we have moved far enough that it looks more like a scroll than a tap”, 在我们tap屏幕的时候有一段距离touchSlop,小于这个距离的ACTION_MOVE是会被判定成为tap效果的,所以在这段源码里面能看到

Java代码

if(overscroll || distance > mTouchSlop) {

createScrollingCache();

if(overscroll) {

mTouchMode = TOUCH_MODE_OVERSCROLL;

mMotionCorrection =0;

}else{

mTouchMode = TOUCH_MODE_SCROLL;

mMotionCorrection = deltaY >0? mTouchSlop : -mTouchSlop;

}

finalHandler handler = getHandler();

// Handler should not be null unless the AbsListView is not attached to a

// window, which would make it very hard to scroll it... but the monkeys

// say it's possible.

if(handler !=null) {

handler.removeCallbacks(mPendingCheckForLongPress);

}

setPressed(false);

View motionView = getChildAt(mMotionPosition - mFirstPosition);

if(motionView !=null) {

motionView.setPressed(false);

}

reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);

// Time to start stealing events! Once we've stolen them, don't let anyone

// steal from us

finalViewParent parent = getParent();

if(parent !=null) {

parent.requestDisallowInterceptTouchEvent(true);

}

scrollIfNeeded(y);

returntrue;

}

里面有代码,对parent判空了执行requestDisallowInterceptTouchEvent(true);看到注释则说明的很清楚了,如果满足了条件,被判定成为looks more like a scroll than a tap,则start stealing events, once we've stolen them, don't let anyone steal from us,好傲娇的样子,直接屏蔽了各种有可能阻截这些事件的情况,然后能够阻截这个MotionEvent的就只有onInterceptTouchEvent了,这也就是为什么在有GridView的情况下,"ACTION_DOWN -> ACTION_MOVE -> ACTION_UP"操作一小距离之后却看不到任何onInterceptTouchEvent被执行的原因了

参考:http://blog.csdn.net/guitk/article/details/7057155

http://waynehu16.iteye.com/blog/1926741

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,723评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,080评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,604评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,440评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,431评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,499评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,893评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,541评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,751评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,547评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,619评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,320评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,890评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,896评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,137评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,796评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,335评论 2 342

推荐阅读更多精彩内容