Title: Android Touch事件分发机制系列一基本流程梳理 · Issue #11 · HankCoder/BlogAndCode · GitHub
Open Graph Title: Android Touch事件分发机制系列一基本流程梳理 · Issue #11 · HankCoder/BlogAndCode
X Title: Android Touch事件分发机制系列一基本流程梳理 · Issue #11 · HankCoder/BlogAndCode
Description: Touch事件分发中只有两个主角:ViewGroup和View。Activity的Touch事件事实上是调用它内部的ViewGroup的Touch事件,可以直接当成ViewGroup处理。 View在ViewGroup内,ViewGroup也可以在其他ViewGroup内,这时候把内部的ViewGroup当成View来分析。 ViewGroup的相关事件有三个:onInterceptTouchEvent、dispatchTouchEvent、onTouchEvent。V...
Open Graph Description: Touch事件分发中只有两个主角:ViewGroup和View。Activity的Touch事件事实上是调用它内部的ViewGroup的Touch事件,可以直接当成ViewGroup处理。 View在ViewGroup内,ViewGroup也可以在其他ViewGroup内,这时候把内部的ViewGroup当成View来分析。 ViewGroup的相关事件有三个:onInterceptTouc...
X Description: Touch事件分发中只有两个主角:ViewGroup和View。Activity的Touch事件事实上是调用它内部的ViewGroup的Touch事件,可以直接当成ViewGroup处理。 View在ViewGroup内,ViewGroup也可以在其他ViewGroup内,这时候把内部的ViewGroup当成View来分析。 ViewGroup的相关事件有三个:onInterceptTouc...
Opengraph URL: https://github.com/HankCoder/BlogAndCode/issues/11
X: @github
Domain: github.com
{"@context":"https://schema.org","@type":"DiscussionForumPosting","headline":"Android Touch事件分发机制系列一基本流程梳理","articleBody":"Touch事件分发中只有两个主角:ViewGroup和View。Activity的Touch事件事实上是调用它内部的ViewGroup的Touch事件,可以直接当成ViewGroup处理。\r\n\r\nView在ViewGroup内,ViewGroup也可以在其他ViewGroup内,这时候把内部的ViewGroup当成View来分析。\r\n\r\nViewGroup的相关事件有三个:onInterceptTouchEvent、dispatchTouchEvent、onTouchEvent。View的相关事件只有两个:dispatchTouchEvent、onTouchEvent。\r\n\r\n先分析ViewGroup的处理流程:首先得有个结构模型概念:ViewGroup和View组成了一棵树形结构,最顶层为Activity的ViewGroup,下面有若干的ViewGroup节点,每个节点之下又有若干的ViewGroup节点或者View节点,依次类推。如图:\r\n\r\n当一个Touch事件(触摸事件为例)到达根节点,即Acitivty的ViewGroup时,它会依次下发,下发的过程是调用子View(ViewGroup)的dispatchTouchEvent方法实现的。简单来说,就是ViewGroup遍历它包含着的子View,调用每个View的dispatchTouchEvent方法,而当子View为ViewGroup时,又会通过调用ViwGroup的dispatchTouchEvent方法继续调用其内部的View的dispatchTouchEvent方法。上述例子中的消息下发顺序是这样的:①-②-⑤-⑥-⑦-③-④。dispatchTouchEvent方法只负责事件的分发,它拥有boolean类型的返回值,当返回为true时,顺序下发会中断。在上述例子中如果⑤的dispatchTouchEvent返回结果为true,那么⑥-⑦-③-④将都接收不到本次Touch事件。来个简单版的代码加深理解:\r\n\r\n```\r\n /**\r\n * ViewGroup\r\n * @param ev\r\n * @return\r\n */\r\n public boolean dispatchTouchEvent(MotionEvent ev){\r\n ....//其他处理,在此不管\r\n View[] views=getChildView();\r\n for(int i=0;i\u003cviews.length;i++){\r\n //判断下Touch到屏幕上的点在该子View上面 \r\n if(...){\r\n if(views[i].dispatchTouchEvent(ev))\r\n return true;\r\n }\r\n }\r\n ...//其他处理,在此不管\r\n }\r\n /**\r\n * View\r\n * @param ev\r\n * @return\r\n */\r\n public boolean dispatchTouchEvent(MotionEvent ev){\r\n ....//其他处理,在此不管\r\n return false;\r\n }\r\n```\r\n在此可以看出,ViewGroup的dispatchTouchEvent是真正在执行“分发”工作,而View的dispatchTouchEvent方法,并不执行分发工作,或者说它分发的对象就是自己,决定是否把touch事件交给自己处理,而处理的方法,便是onTouchEvent事件,事实上子View的dispatchTouchEvent方法真正执行的代码是这样的\r\n\r\n```\r\n/**\r\n * View\r\n * @param ev\r\n * @return\r\n */\r\n public boolean dispatchTouchEvent(MotionEvent ev){\r\n ....//其他处理,在此不管\r\n return onTouchEvent(event);\r\n }\r\n\r\n```\r\n一般情况下,我们不该在普通View内重写dispatchTouchEvent方法,因为它并不执行分发逻辑。当Touch事件到达View时,我们该做的就是是否在onTouchEvent事件中处理它。\r\n\r\n那么,ViewGroup的onTouchEvent事件是什么时候处理的呢?当ViewGroup所有的子View都返回false时,onTouchEvent事件便会执行。由于ViewGroup是继承于View的,它其实也是通过调用View的dispatchTouchEvent方法来执行onTouchEvent事件。\r\n\r\n \r\n\r\n在目前的情况看来,似乎只要我们把所有的onTouchEvent都返回false,就能保证所有的子控件都响应本次Touch事件了。但必须要说明的是,这里的Touch事件,只限于Acition_Down事件,即触摸按下事件,而Aciton_UP和Action_MOVE却不会执行。事实上,一次完整的Touch事件,应该是由一个Down、一个Up和若干个Move组成的。Down方式通过dispatchTouchEvent分发,分发的目的是为了找到真正需要处理完整Touch请求的View。当某个View或者ViewGroup的onTouchEvent事件返回true时,便表示它是真正要处理这次请求的View,之后的Aciton_UP和Action_MOVE将由它处理。当所有子View的onTouchEvent都返回false时,这次的Touch请求就由根ViewGroup,即Activity自己处理了。\r\n\r\n看看改进后的ViewGroup的dispatchTouchEvent方法\r\n\r\n```\r\nView mTarget=null;//保存捕获Touch事件处理的View\r\n public boolean dispatchTouchEvent(MotionEvent ev) {\r\n\r\n //....其他处理,在此不管\r\n \r\n if(ev.getAction()==KeyEvent.ACTION_DOWN){\r\n //每次Down事件,都置为Null\r\n\r\n if(!onInterceptTouchEvent()){\r\n mTarget=null;\r\n View[] views=getChildView();\r\n for(int i=0;i\u003cviews.length;i++){\r\n if(views[i].dispatchTouchEvent(ev))\r\n mTarget=views[i];\r\n return true;\r\n }\r\n }\r\n }\r\n //当子View没有捕获down事件时,ViewGroup自身处理。这里处理的Touch事件包含Down、Up和Move\r\n if(mTarget==null){\r\n return super.dispatchTouchEvent(ev);\r\n }\r\n //...其他处理,在此不管\r\n if(onInterceptTouchEvent()){\r\n //...其他处理,在此不管 \r\n }\r\n//这一步在Action_Down中是不会执行到的,只有Move和UP才会执行到。\r\n return mTarget.dispatchTouchEvent(ev);\r\n\r\n }\r\n ```\r\n\r\nViewGroup还有个onInterceptTouchEvent,看名字便知道这是个拦截事件。这个拦截事件需要分两种情况来说明:\r\n\r\n1.假如我们在某个ViewGroup的onInterceptTouchEvent中,将Action为Down的Touch事件返回true,那便表示将该ViewGroup的所有下发操作拦截掉,这种情况下,mTarget会一直为null,因为mTarget是在Down事件中赋值的。由于mTarge为null,该ViewGroup的onTouchEvent事件被执行。这种情况下可以把这个ViewGroup直接当成View来对待。\r\n\r\n2.假如我们在某个ViewGroup的onInterceptTouchEvent中,将Acion为Down的Touch事件都返回false,其他的都返回True,这种情况下,Down事件能正常分发,若子View都返回false,那mTarget还是为空,无影响。若某个子View返回了true,mTarget被赋值了,在Action_Move和Aciton_UP分发到该ViewGroup时,便会给mTarget分发一个Action_Delete的MotionEvent,同时清空mTarget的值,使得接下去的Action_Move(如果上一个操作不是UP)将由ViewGroup的onTouchEvent处理。\r\n\r\n情况一用到的比较多,情况二个人还未找到使用场景。\r\n\r\n从头到尾总结一下:\r\n\r\n1.Touch事件分发中只有两个主角:ViewGroup和View。ViewGroup包含onInterceptTouchEvent、dispatchTouchEvent、onTouchEvent三个相关事件。View包含dispatchTouchEvent、onTouchEvent两个相关事件。其中ViewGroup又继承于View。\r\n\r\n2.ViewGroup和View组成了一个树状结构,根节点为Activity内部包含的一个ViwGroup。\r\n\r\n3.触摸事件由Action_Down、Action_Move、Aciton_UP组成,其中一次完整的触摸事件中,Down和Up都只有一个,Move有若干个,可以为0个。\r\n\r\n4.当Acitivty接收到Touch事件时,将遍历子View进行Down事件的分发。ViewGroup的遍历可以看成是递归的。分发的目的是为了找到真正要处理本次完整触摸事件的View,这个View会在onTouchuEvent结果返回true。\r\n\r\n5.当某个子View返回true时,会中止Down事件的分发,同时在ViewGroup中记录该子View。接下去的Move和Up事件将由该子View直接进行处理。由于子View是保存在ViewGroup中的,多层ViewGroup的节点结构时,上级ViewGroup保存的会是真实处理事件的View所在的ViewGroup对象:如ViewGroup0-ViewGroup1-TextView的结构中,TextView返回了true,它将被保存在ViewGroup1中,而ViewGroup1也会返回true,被保存在ViewGroup0中。当Move和UP事件来时,会先从ViewGroup0传递至ViewGroup1,再由ViewGroup1传递至TextView。\r\n\r\n6.当ViewGroup中所有子View都不捕获Down事件时,将触发ViewGroup自身的onTouch事件。触发的方式是调用super.dispatchTouchEvent函数,即父类View的dispatchTouchEvent方法。在所有子View都不处理的情况下,触发Acitivity的onTouchEvent方法。\r\n\r\n7.onInterceptTouchEvent有两个作用:1.拦截Down事件的分发。2.中止Up和Move事件向目标View传递,使得目标View所在的ViewGroup捕获Up和Move事件。\r\n\r\n \r\n\r\n另外,上文所列出的代码并非真正的源码,只是概括了源码在事件分发处理中的核心处理流程,真正源码各位可以自己去看,包含了更丰富的内容。\r\n\r\n 补充:\r\n\r\n“触摸事件由Action_Down、Action_Move、Aciton_UP组成,其中一次完整的触摸事件中,Down和Up都只有一个,Move有若干个,可以为0个。”,这里补充下其实UP事件是可能为0个的。\r\n\r\n \r\n\r\n最近刚好在做一个手势放大缩小移动图片的Demo,对此有了更多的理解。对于onInterceptTouchEvent事件,它的应用场景在很多带scroll效果的ViewGroup中都有体现。设想一下再一个ViewPager中,每个Item都是个ImageView,我们需要对这些ImageView做Matrix操作,这不可避免要捕获掉Touch事件,但是我们又需要做到不影响ViewPager翻页效果,这又必须保证ViewPager能捕获到Move事件,于是,ViewPager的onInterceptTouchEvent会对Move事件做一个过滤,当适当条件的Move事件(持续若干事件或移动若干距离,这里我没读源码只是猜测)触发时,并会拦截掉,返回子View一个Action_Cancel事件。这个时候子View就没有Up事件了,很多需要在Up中处理的事物要转到Cancel中处理。","author":{"url":"https://github.com/HankCoder","@type":"Person","name":"HankCoder"},"datePublished":"2017-10-20T04:13:57.000Z","interactionStatistic":{"@type":"InteractionCounter","interactionType":"https://schema.org/CommentAction","userInteractionCount":0},"url":"https://github.com/11/BlogAndCode/issues/11"}
| route-pattern | /_view_fragments/issues/show/:user_id/:repository/:id/issue_layout(.:format) |
| route-controller | voltron_issues_fragments |
| route-action | issue_layout |
| fetch-nonce | v2:d77924f7-b569-cf9c-2280-0bb36623869b |
| current-catalog-service-hash | 81bb79d38c15960b92d99bca9288a9108c7a47b18f2423d0f6438c5b7bcd2114 |
| request-id | 8E8C:8CD90:C4E802:11887D9:6A589027 |
| html-safe-nonce | ed873912d091e381ec849663ac967e266c2d6844ffbd1b31de1e4926b7769211 |
| visitor-payload | eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiI4RThDOjhDRDkwOkM0RTgwMjoxMTg4N0Q5OjZBNTg5MDI3IiwidmlzaXRvcl9pZCI6IjYxNjg1OTU4OTgxODY2MzMyNTUiLCJyZWdpb25fZWRnZSI6ImlhZCIsInJlZ2lvbl9yZW5kZXIiOiJpYWQifQ== |
| visitor-hmac | 7f16884150117947c5216dcf09f160dad46340bc9689a43ffeebe7ed189dccff |
| hovercard-subject-tag | issue:267059131 |
| github-keyboard-shortcuts | repository,issues,copilot |
| google-site-verification | Apib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I |
| octolytics-url | https://collector.github.com/github/collect |
| analytics-location | / |
| fb:app_id | 1401488693436528 |
| apple-itunes-app | app-id=1477376905, app-argument=https://github.com/_view_fragments/issues/show/HankCoder/BlogAndCode/11/issue_layout |
| twitter:image | https://opengraph.githubassets.com/6dda679d9353b939431f1bb33b81586452156cc39eceb54b527f4a45e14f327a/HankCoder/BlogAndCode/issues/11 |
| twitter:card | summary_large_image |
| og:image | https://opengraph.githubassets.com/6dda679d9353b939431f1bb33b81586452156cc39eceb54b527f4a45e14f327a/HankCoder/BlogAndCode/issues/11 |
| og:image:alt | Touch事件分发中只有两个主角:ViewGroup和View。Activity的Touch事件事实上是调用它内部的ViewGroup的Touch事件,可以直接当成ViewGroup处理。 View在ViewGroup内,ViewGroup也可以在其他ViewGroup内,这时候把内部的ViewGroup当成View来分析。 ViewGroup的相关事件有三个:onInterceptTouc... |
| og:image:width | 1200 |
| og:image:height | 600 |
| og:site_name | GitHub |
| og:type | object |
| og:author:username | HankCoder |
| hostname | github.com |
| expected-hostname | github.com |
| None | 5f2a0c7865178af3d91dd9f13b0cdfc3c73a2529c873d2780bb4c01271a57ec6 |
| turbo-cache-control | no-preview |
| go-import | github.com/HankCoder/BlogAndCode git https://github.com/HankCoder/BlogAndCode.git |
| octolytics-dimension-user_id | 10301539 |
| octolytics-dimension-user_login | HankCoder |
| octolytics-dimension-repository_id | 83747204 |
| octolytics-dimension-repository_nwo | HankCoder/BlogAndCode |
| octolytics-dimension-repository_public | true |
| octolytics-dimension-repository_is_fork | false |
| octolytics-dimension-repository_network_root_id | 83747204 |
| octolytics-dimension-repository_network_root_nwo | HankCoder/BlogAndCode |
| turbo-body-classes | logged-out env-production page-responsive |
| disable-turbo | false |
| browser-stats-url | https://api.github.com/_private/browser/stats |
| browser-errors-url | https://api.github.com/_private/browser/errors |
| release | 8aae7b8d6caacacf5c66eaeb2702d8dc88d85b4a |
| ui-target | full |
| theme-color | #1e2327 |
| color-scheme | light dark |
Links:
Viewport: width=device-width