尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
Android 事件分发机制总结与实战:从 Activity 到 View 的传递链路、处理优先级与滑动实现(YCBlogs)
教程技术博客文档【免费下载链接】YCBlogs技术博客笔记大汇总包括Java基础线程并发数据结构Android技术博客等等常用设计模式常见的算法网络协议知识点部分flutter笔记还包括平时开发中遇到的bug汇总当然也在工作之余收集了大量的面试题长期更新维护并且修正持续完善……开源的文件是markdown格式的转载请注明出处谢谢项目地址https://gitcode.com/gh_mirrors/yc/YCBlogs点击查看免费下载本篇以 YCBlogs 仓库中《View 事件总结案例》为核心主线系统梳理 Android 触摸事件分发的完整链路Activity → Window → ViewGroup → View、dispatchTouchEvent/onInterceptTouchEvent/onTouchEvent三个核心方法的协作关系、onTouch/onTouchEvent/onClick的执行优先级并汇总 View 滑动的七种实现方式与滑动冲突的外部拦截法、内部拦截法。读完本篇你可以快速建立事件分发的整体认知框架同时直接复用文中给出的源码级伪代码与实战代码方案。文中源码级细节可结合仓库内的 事件机制源码分析 与 事件基础介绍 交叉阅读。01. Android 事件分发机制总览1.1 分发顺序与核心方法Android 的触摸事件分发遵循固定的顺序事件分发顺序Activity → ViewGroup → View核心参与方法有三个dispatchTouchEvent负责分发事件onInterceptTouchEvent当前 View仅 ViewGroup 拥有判断是否拦截该事件onTouchEvent负责真正处理事件。一次典型的分发流程可以概括为四步父 View 调用dispatchTouchEvent开启事件分发父 View 调用onInterceptTouchEvent判断是否拦截该事件一旦拦截该事件序列的后续事件DOWN 之后的 MOVE、UP都直接拦截不再进行判断如果父 View 拦截事件则由父 View 调用onTouchEvent处理如果父 View 不拦截会调用子 View 的dispatchTouchEvent进行事件的层层分发直到事件被最终处理。1.2 事件分发的对象MotionEvent事件分发的对象是触摸事件本身。当用户触摸屏幕时触摸位置、时间、手势动作等细节会被封装为MotionEvent对象其中四种核心动作贯穿整个事件序列动作含义ACTION_DOWN手指按下一次触摸事件的开始任何事件序列都以它开头ACTION_MOVE手指按压后移动超过一定阈值即判定为滑动通常会触发一系列 MOVEACTION_UP手指抬起一次触摸事件的结束ACTION_CANCEL非人为原因如父容器中途拦截结束本次事件一次完整的触摸过程称为一个事件序列从手指接触屏幕到离开屏幕以ACTION_DOWN开始、ACTION_UP结束中间夹杂数量不定的ACTION_MOVE。此外通过MotionEvent的getX()/getY()获取的是相对当前 View 左上角的坐标getRawX()/getRawY()获取的是相对手机屏幕左上角的坐标。1.3 三个方法的执行顺序与协作关系在 ViewGroup 中重写三个方法并打印日志可以得到清晰的执行顺序Override public boolean onInterceptTouchEvent(MotionEvent e) { LogUtils.e(yc----------事件拦截----------); return super.onInterceptTouchEvent(e); } Override public boolean dispatchTouchEvent(MotionEvent ev) { LogUtils.e(yc----------事件分发----------); return super.dispatchTouchEvent(ev); } Override public boolean onTouchEvent(MotionEvent e) { LogUtils.e(yc----------事件触摸----------); return super.onTouchEvent(e); }执行结果yc----------事件分发---------- yc----------事件拦截---------- yc----------事件触摸----------三个方法之间的逻辑关系可用如下伪代码概括// 点击事件产生后会直接调用 dispatchTouchEvent 分发方法 public boolean dispatchTouchEvent(MotionEvent ev) { // 代表是否消耗事件 boolean consume false; if (onInterceptTouchEvent(ev)) { // onInterceptTouchEvent() 返回 true当前 View 拦截事件 // 交给当前 View 的 onTouchEvent() 处理 consume onTouchEvent(ev); } else { // onInterceptTouchEvent() 返回 false不拦截 // 事件继续传递给子元素调用子元素的 dispatchTouchEvent() consume child.dispatchTouchEvent(ev); } return consume; }从源码结构看dispatchTouchEvent是分发入口onInterceptTouchEvent是拦截分叉点只做分流本身不消费事件onTouchEvent才是最终处理事件的落点。02. View 与 ViewGroup 的事件分发差异2.1 伪代码层面的差异ViewGroup 事件分发伪代码在dispatchTouchEvent()中先判断是否拦截再决定是自己处理还是向下分发核心逻辑即 1.3 节所示View 事件分发伪代码View没有子 ViewdispatchTouchEvent()直接决定事件如何被自身处理核心区别ViewGroup 在dispatchTouchEvent()中会进行事件的分发分发给自己或子 ViewView 在dispatchTouchEvent()中只对该事件进行处理。2.2 源码级验证ViewGroup 的拦截判断结合 事件机制源码分析 中对ViewGroup.dispatchTouchEvent()的剖析拦截判断的核心片段如下// 发生 ACTION_DOWN 事件或者已经发生过 ACTION_DOWN 且 mFirstTouchTarget 已被赋值才进入此区域 final boolean intercepted; if (actionMasked MotionEvent.ACTION_DOWN || mFirstTouchTarget ! null) { // disallowIntercept是否禁用事件拦截默认 false即不禁用 // 子 View 可通过 requestDisallowInterceptTouchEvent 修改该值阻止父 View 拦截 final boolean disallowIntercept (mGroupFlags FLAG_DISALLOW_INTERCEPT) ! 0; if (!disallowIntercept) { intercepted onInterceptTouchEvent(ev); ev.setAction(action); } else { intercepted false; } } else { // 没有触摸 target 且不是 DOWN 事件时开始持续拦截 intercepted true; }这段逻辑解释了两个关键现象只有ACTION_DOWN或已经存在处理事件的子 ViewmFirstTouchTarget ! null时才会调用onInterceptTouchEvent如果子 View 的onTouchEvent()返回 falsemFirstTouchTarget null那么后续 MOVE/UP 事件会被 ViewGroup 直接拦截不再交给该子 View。2.3 源码级验证ViewGroup 向下分发子事件当 ViewGroup 不拦截事件时会从最底层的父视图开始倒序遍历子 View寻找接收事件的 targetfor (int i childrenCount - 1; i 0; i--) { ... // View 不可见或触摸坐标不在 View 范围内则跳过 if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) { continue; } // 触摸点在 child 区域内把事件分发给子 View if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { ... // 添加 TouchTarget则 mFirstTouchTarget ! null newTouchTarget addTouchTarget(child, idBitsToAssign); alreadyDispatchedToNewTouchTarget true; break; } }其中dispatchTransformedTouchEvent()实际就是调用子元素的dispatchTouchEvent()if (child null) { handled super.dispatchTouchEvent(event); } else { handled child.dispatchTouchEvent(event); }如果子元素的dispatchTouchEvent()返回 trueaddTouchTarget会为mFirstTouchTarget赋值并跳出循环如果遍历所有子 View 都没有消费事件ViewGroup 最终会自己处理事件。2.4 源码级验证View.dispatchTouchEvent()View 侧的dispatchTouchEvent()源码非常精简public boolean dispatchTouchEvent(MotionEvent event) { if (mOnTouchListener ! null (mViewFlags ENABLED_MASK) ENABLED mOnTouchListener.onTouch(this, event)) { return true; } return onTouchEvent(event); }只有以下三个条件同时成立dispatchTouchEvent()才返回 true事件被onTouch消费否则进入onTouchEvent(event)mOnTouchListener ! null通过setOnTouchListener()注册过触摸监听(mViewFlags ENABLED_MASK) ENABLED当前 View 处于 enable 状态mOnTouchListener.onTouch(this, event)返回 true。这一结论直接影响了onTouch()与onTouchEvent()的调用关系详见下一节。03. onTouch()、onTouchEvent() 与 onClick() 的关系3.1 执行顺序与优先级当一个 View 需要处理事件时三者的执行顺序是onTouch() - onTouchEvent() - onClick()具体规则如果 View 设置了OnTouchListener其onTouch方法会被回调此时事件如何处理取决于onTouch的返回值返回false当前 View 的onTouchEvent方法会被调用返回trueonTouchEvent方法将不会被调用。由此可见OnTouchListener 的优先级比 onTouchEvent 高如果 View 设置了OnClickListener则会在onTouchEvent处理过程中performClick()内部回调onClick。OnClickListener 的优先级别最低处于事件传递的尾端。3.2 源码层面的完整印证onTouchEvent()内部对可点击 View 的处理逻辑节选public boolean onTouchEvent(MotionEvent event) { final int viewFlags mViewFlags; if ((viewFlags ENABLED_MASK) DISABLED) { // 禁用但可点击的 View 仍然会消费触摸事件只是不响应 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: ... // 经过种种判断后执行到 performClick() 方法 performClick(); ... break; ... } // 可点击的 View 一定返回 true return true; } // 不可点击的 View 返回 false return false; }而performClick()内部才会回调onClickpublic boolean performClick() { sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); if (mOnClickListener ! null) { playSoundEffect(SoundEffectConstants.CLICK); mOnClickListener.onClick(this); return true; } return false; }mOnClickListener通过setOnClickListener()赋值该方法还会顺带把 View 设置为可点击public void setOnClickListener(OnClickListener l) { if (!isClickable()) { setClickable(true); } mOnClickListener l; }3.3 日志验证在 事件机制源码分析 中给出了完整验证实验包名org.yczbj.ycrefreshviewonTouch() 返回 true 时只输出 onTouch 日志onClick 不会执行事件被 onTouch 消费D/小杨逗比: 执行了onTouch(), 动作是:0 // ACTION_DOWN D/小杨逗比: 执行了onTouch(), 动作是:2 // ACTION_MOVE D/小杨逗比: 执行了onTouch(), 动作是:2 D/小杨逗比: 执行了onTouch(), 动作是:1 // ACTION_UPonTouch() 返回 false 时onTouch 之后继续执行 onTouchEvent最终触发 onClickD/小杨逗比: 执行了onTouch(), 动作是:0 D/小杨逗比: 执行了onTouch(), 动作是:2 D/小杨逗比: 执行了onTouch(), 动作是:2 D/小杨逗比: 执行了onTouch(), 动作是:1 D/小杨逗比: 执行了onClick()注意 action 数值0 为 ACTION_DOWN1 为 ACTION_UP2 为 ACTION_MOVE。结论onTouch() 返回 true 即认为事件被消费不会继续向下传递onClick() 也就不会执行。3.4 设置了 onClickListener 但 onClick() 不触发的常见原因排查设置了点击监听却不生效的问题时可以按下面清单逐一核对父 View 拦截了事件事件没有传递到当前 ViewView 处于不可用状态setEnabled(false)后 View 直接返回不会响应View 不可点击clickable与longClickable同时为 false或调用了setClickable(false)不会执行onClickView 设置了 onTouchListener 且消费了事件onTouch返回 true 会提前返回阻断onTouchEventView 设置了 TouchDelegate 且消费了事件TouchDelegate.onTouchEvent返回 true 同样会提前返回。04. 事件的传递规则与 View 处理事件的优先级4.1 事件的传递规则点击事件产生后会先传递给根 ViewGroup并调用dispatchTouchEvent之后通过onInterceptTouchEvent判断是否拦截返回 true 表示拦截事件交给该 ViewGroup 的onTouchEvent处理不拦截时事件传递给子元素调用子元素的dispatchTouchEvent如此反复直到事件被处理。4.2 View 处理事件的优先级View 内部处理事件遵循一条完整的优先级链OnTouchListener.onTouch() onTouchEvent() TouchDelegate.onTouchEvent() performClick() OnClickListener.onClick()先调用OnTouchListener的onTouch返回 true 表示处理完成不再调用onTouchEvent返回 false 才进入onTouchEvent可见onTouchEvent优先级低于OnTouchListeneronTouchEvent没有消耗事件时会交给TouchDelegate的onTouchEvent处理最后事件若未被提前消费会在onTouchEvent中执行performClick()内部回调OnClickListener.onClick——它是整个链条中优先级最低的一环属于事件传递的尾端。4.3 点击事件的完整传递过程点击事件传递顺序Activity → Window → View逐层分发如果 View 的onTouchEvent返回 false则父容器的onTouchEvent会被调用最终可以一路传递到Activity.onTouchEvent。4.4 事件传递规则要点速记清单View 一旦拦截事件则整个事件序列都由它处理DOWN、UP 等onInterceptTouchEvent不会再被调用因为默认都已拦截一个事件序列也可以通过特殊方法交给其他 View 处理onTouchEvent如果 View 开始处理事件已拦截但不消耗ACTION_DOWNonTouchEvent返回 false则同一事件序列的剩余内容都直接交给父onTouchEvent处理View 消耗了ACTION_DOWN但不处理后续其他事件时整个事件序列会消失父onTouchEvent不会被调用这些消失的点击事件最终会传给 Activity 处理ViewGroup 默认不拦截任何事件onInterceptTouchEvent默认返回 falseView 没有onInterceptTouchEvent方法一旦有事件传递给 ViewonTouchEvent就会被调用View 的onTouchEvent默认都会消费事件并返回 true除非该 View 不可点击clickable和longClickable同时为 falseView 的enable属性不影响onTouchEvent的默认返回值即使是 disable 状态onClick发生的前提是当前 View 可点击并且收到了 DOWN 和 UP 事件事件传递过程是由父到子层层分发子元素可以通过requestDisallowInterceptTouchEvent干预父元素的事件分发ACTION_DOWN除外。05. 事件分发链路Activity → Window → DecorView → ViewGroup → View5.1 Activity 的事件分发过程事件分发的完整路径是Activity - Window - DecorView当前界面的底层容器setContentView 的 View 的父容器- ViewGroup - ViewActivity.dispatchTouchEvent()的关键源码如下public boolean dispatchTouchEvent(MotionEvent ev) { // 一般事件列开始都是 DOWN所以这里基本是 true if (ev.getAction() MotionEvent.ACTION_DOWN) { onUserInteraction(); } // 交给 Window 处理 if (getWindow().superDispatchTouchEvent(ev)) { return true; } return onTouchEvent(ev); }流程要点onUserInteraction()是空方法当 Activity 在栈顶时触屏点击、按 Home/Back/Menu 键等都会触发主要用于屏保等场景Activity 的dispatchTouchEvent交给 Window 处理getWindow().superDispatchTouchEvent()返回 true事件全部结束返回 false所有 View 都没有处理onTouchEvent返回 false则调用Activity.onTouchEvent。5.2 Window 的事件分发Window与superDispatchTouchEvent分别是抽象类和抽象方法Window的唯一实现类是PhoneWindowPhoneWindow.superDispatchTouchEvent()直接调用mDecor.superDispatchTouchEvent()也就是直接传给了 DecorViewOverride public boolean superDispatchTouchEvent(MotionEvent event) { return mDecor.superDispatchTouchEvent(event); // mDecor 是 DecorView 的实例DecorView 是视图的顶层 View }5.3 DecorView 的事件分发DecorView 继承自 FrameLayout因此本质上是 ViewGroupDecorView 的superDispatchTouchEvent()会调用super.dispatchTouchEvent()——也就是ViewGroup 的dispatchTouchEvent之后事件就进入 ViewGroup 层开始层层分发public boolean superDispatchTouchEvent(MotionEvent event) { return super.dispatchTouchEvent(event); }5.4 根 View顶层 ViewGroup的事件分发顶层 View 调用dispatchTouchEvent调用onInterceptTouchEvent判断返回true事件由当前 View 处理。若设置了OnTouchListener执行onTouch并屏蔽掉onTouchEvent未设置则执行onTouchEvent若设置了OnClickListener会在onTouchEvent之后执行onClick返回false不拦截交给子 View 重复以上步骤。5.5 补充事件从 Native 层进入应用层的路径按键类事件与触摸事件的链路在进入应用层之前是一致的。仓库中 返回键事件分发流程 给出了更完整的链路Native 层 → ViewRootImpl 层 → DecorView 层 → Activity 层 → ViewGroup 层 → View 层。其中 Native 层由InputManager管理消息通过InputReaderThread读取与InputDispatcherThread预处理与分发两个线程配合经 IPC 将消息分发到应用进程应用侧经ViewRootImpl$ViewPostImeInputStage.processKeyEvent调用mView.dispatchKeyEvent而mView正是 PhoneWindow 中的 DecorView随后进入上文所述的 DecorView → Activity 的分发流程。06. View 滑动实现方法总结View 滑动与事件分发是强相关的实践话题滑动本质上是onTouchEvent中响应 MOVE 事件并移动 View。常用方法可先概览如下方法实现思路备注layout()在onTouchEvent()中获得滑动前后偏移重新布局定位直接重设位置offsetLeftAndRight()/offsetTopAndBottom()系统提供的上下/左右偏移 API在onTouchEvent()中调用LayoutParams更改自身布局参数margin需父布局配合scrollTo/scrollBy移动 View 的内容本质是移动内容需在父容器中调用Scroller平滑滑动配合computeScroll()弹性滑动属性动画动画对 View 施加平移效果保留动画后状态可点击ViewDragHelper谷歌提供的辅助类用于完成各种拖拽效果自定义 ViewGroup 拖拽6.1 layout() 实现滑动在onTouchEvent()中记录手指滑动前后的偏移量然后调用layout()重新设置 View 的四个坐标即可实现滑动。6.2 offsetLeftAndRight() 与 offsetTopAndBottom()系统提供的便捷 API分别实现左右、上下的整体偏移同样在onTouchEvent()中调用效果等同于手动layout但代码更简洁。6.3 LayoutParams 实现滑动通过修改布局参数margin改变 View 在父控件中的位置。两种写法对比// 方法一通过布局参数设置在父控件的位置。 // 缺点必须要有父控件而且要指定父布局的类型不够通用。 RelativeLayout.LayoutParams layoutParams (RelativeLayout.LayoutParams) getLayoutParams(); layoutParams.leftMargin getLeft() offsetX; layoutParams.topMargin getTop() offsetY; setLayoutParams(layoutParams); /* * 方法二用 ViewGroup.MarginLayoutParams 设置 margin * 优点不需要知道父布局的具体类型通用性更好。 * 缺点滑动到边缘时控件可能会被压缩变小。 **/ ViewGroup.MarginLayoutParams mlayoutParams (ViewGroup.MarginLayoutParams) getLayoutParams(); mlayoutParams.leftMargin getLeft() offsetX; mlayoutParams.topMargin getTop() offsetY; setLayoutParams(mlayoutParams);6.4 scrollTo() 与 scrollBy() 实现滑动两者都是 View 提供的方法scrollTo直接滑到新的 (x, y) 坐标处绝对滑动scrollBy基于当前位置的相对滑动内部就是调用scrollTo效果是移动 View 的内容因此需要在 View 的父控件中调用例如父控件里调用scrollBy让子内容滚动。mScrollX与mScrollY的含义是理解这两个方法的关键mScrollX手机屏幕相对于 View 左边缘向右移动的距离。屏幕向右移动时mScrollX为正屏幕向左移动等价于 View 向右移动时mScrollX为负mScrollY同理屏幕向下移动为正值向上移动为负值mScrollX/Y是基于第一次滑动前位置累计的值。例如第一次向左滑动 200等价屏幕向右滑动 200mScrollX 200第二次向右滑动 50mScrollX 200 (-50) 150而不是 -50。6.5 Scroller 实现平滑滑动Scroller用于封装滑动提供基于时间的滑动偏移值但实际滑动动作仍需自己负责要点调用startScroll()时 Scroller 只是单纯保存参数随后调用invalidate()触发 View 重绘重绘后draw()会调用自己实现的computeScroll()在这里才真正完成滑动工作原理Scroller 本身不能直接实现 View 滑动需要配合computeScroll()实现弹性滑动——不断让 View 重绘通过距离滑动开始的时间间隔计算当前滑动距离每次重绘产生小幅滑动多次小幅滑动组合成平滑的弹性滑动。6.6 属性动画实现滑动可以通过传统补间动画或属性动画实现传统动画需要通过设置fillAfter true来保留动画后的状态但动画后 View 的影像位置无法响应点击操作属性动画会保留动画后的状态且动画后能够正常点击更适合有交互的场景补充通过动画给 View 施加平移效果时补间动画移动的是 View 的影像View 本身位置不变属性动画则会让 View 本身的位置发生改变。6.7 ViewDragHelper谷歌提供的辅助类用于完成各种拖拽效果通过 ViewDragHelper 自定义 ViewGroup可以让其子 View 具备拖拽滑动能力实际项目中使用相对较少属于进阶方案。6.8 三种主流滑动方式的对比方式特点适用场景scrollTo/scrollBy操作简单移动的是 View 的内容View 本身不移动非平滑适合对 View 内容的滑动动画操作简单补间动画移动影像、属性动画移动本体适合无交互的 View 及复杂动画效果改变 LayoutParams操作稍复杂重新布局适用于需要交互的 View07. 延伸实战滑动冲突的两种解法事件分发最典型的工程应用就是解决滑动冲突仓库中 滑动冲突处理 给出了完整方案。当父容器与子 View 都可以滑动时即产生滑动冲突常见场景有三类内外滑动方向不一致垂直 vs 水平如 ScrollView 与 ViewPager、内外滑动方向一致如 ScrollView 与 RecyclerView、以及前两者层层嵌套。7.1 外部拦截法思路父容器根据需要在onInterceptTouchEvent中对事件选择性拦截。public boolean onInterceptTouchEvent(MotionEvent event) { boolean intercepted false; int x (int) event.getX(); int y (int) event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: { intercepted false; // DOWN 一定不能拦截 break; } case MotionEvent.ACTION_MOVE: { if (满足父容器的拦截要求) { intercepted true; } else { intercepted false; } break; } case MotionEvent.ACTION_UP: { intercepted false; // UP 原则上也不拦截 break; } default: break; } mLastXIntercept x; mLastYIntercept y; return intercepted; }要点根据业务需求判断是否处理ACTION_MOVE父 View 需要处理则返回 true否则返回 false 交由子 ViewACTION_DOWN必须返回 false否则根据分发机制后续 MOVE/UP 都将默认交给父容器ACTION_UP原则上也返回 false若父容器拦截 UP子 View 收不到 UPonClick将无法触发。以 ScrollView 与 ViewPager 为例滑动方向不同一个垂直一个水平可通过判断横竖位移差来决策public class MyScrollView extends ScrollView { ... private float mDownPosX 0; private float mDownPosY 0; Override public boolean onInterceptTouchEvent(MotionEvent ev) { final float x ev.getX(); final float y ev.getY(); final int action ev.getAction(); switch (action) { case MotionEvent.ACTION_DOWN: mDownPosX x; mDownPosY y; break; case MotionEvent.ACTION_MOVE: final float deltaX Math.abs(x - mDownPosX); final float deltaY Math.abs(y - mDownPosY); // 拦截判断依据是左右滑动左右滑动交给 ViewPager上下滑动由 ScrollView 自己处理 if (deltaX deltaY) { return false; } } return super.onInterceptTouchEvent(ev); } }7.2 内部拦截法思路父容器不拦截任何事件所有事件先传递给子 View子 View 根据需求决定自己消费还是交还父容器核心是配合requestDisallowInterceptTouchEvent。子 View 修改dispatchTouchEventpublic boolean dispatchTouchEvent(MotionEvent event) { int x (int) event.getX(); int y (int) event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: { parent.requestDisallowInterceptTouchEvent(true); break; } case MotionEvent.ACTION_MOVE: { int deltaX x - mLastX; int deltaY y - mLastY; if (父容器需要此类点击事件) { parent.requestDisallowInterceptTouchEvent(false); } break; } case MotionEvent.ACTION_UP: { break; } default: break; } mLastX x; mLastY y; return super.dispatchTouchEvent(event); }父容器修改onInterceptTouchEventpublic boolean onInterceptTouchEvent(MotionEvent event) { int action event.getAction(); if (action MotionEvent.ACTION_DOWN) { return false; // DOWN 不能拦截 } else { return true; } }要点父容器不能拦截ACTION_DOWN否则后续触摸事件都不会传递给子 View滑动策略逻辑放在子 View 的dispatchTouchEvent的ACTION_MOVE分支中父容器需要处理事件时调用parent.requestDisallowInterceptTouchEvent(false)让父容器接管拦截。08. 总结本篇围绕 YCBlogs 仓库中 View事件总结案例 展开结合 事件机制源码分析、事件基础介绍、滑动冲突处理、View事件问题 等仓库文档完成了一次从结论速记到源码印证再到实战方案的完整梳理。核心结论可以浓缩为以下几点分发顺序事件从 Activity 出发经 WindowPhoneWindow进入 DecorViewFrameLayout再进入 ViewGroup 层层层下发到 View三个方法的分工dispatchTouchEvent分发、onInterceptTouchEvent拦截仅 ViewGroup 拥有默认不拦截、onTouchEvent处理处理优先级OnTouchListener.onTouchonTouchEventTouchDelegateperformClickOnClickListener.onClick且onClick触发需要 View 可点击并完整收到 DOWN 与 UPDOWN 事件是事件序列的入场券谁消耗了 DOWN谁才可能收到后续 MOVE/UP父容器一旦中途拦截会以ACTION_CANCEL通知原处理者滑动实现layout、offsetLeftAndRight/offsetTopAndBottom、LayoutParams、scrollTo/scrollBy、Scroller、属性动画、ViewDragHelper七种方式各有权衡按交互需求选择滑动冲突外部拦截法重写父容器onInterceptTouchEvent与内部拦截法子 View 用requestDisallowInterceptTouchEvent干预父容器是两大标准解法。如需进一步查阅本主题相关的面试问答与细节补充可继续阅读仓库中的 AndroidView事件问题或从 Android博客大汇总 进入事件分发专题的其他文章。赞分享教程技术博客文档【免费下载链接】YCBlogs技术博客笔记大汇总包括Java基础线程并发数据结构Android技术博客等等常用设计模式常见的算法网络协议知识点部分flutter笔记还包括平时开发中遇到的bug汇总当然也在工作之余收集了大量的面试题长期更新维护并且修正持续完善……开源的文件是markdown格式的转载请注明出处谢谢项目地址https://gitcode.com/gh_mirrors/yc/YCBlogs点击查看免费下载相关推荐YCBlogs Android View 事件分发与滑动机制全解析从分发规则到滑动冲突实战YCBlogs Android View 事件分发与滑动机制全解析从分发规则到滑动冲突实战 本文是 YCBlogs 仓库 android/05.事件分发 ht教程技术博客文档YCBlogs Android 触摸事件分发机制源码全解析Activity 到 View 的三层分发与消费链路YCBlogs Android 触摸事件分发机制源码全解析Activity 到 View 的三层分发与消费链路 本文档基于 YCBlogs https://l教程技术博客文档Android事件传递机制深入理解从触摸到响应的完整链路Android事件传递机制深入理解从触摸到响应的完整链路 Android事件传递机制是一个精心设计的体系架构它遵循着从硬件输入到最终用户交互的完整链路。这个文档教程知识库上一篇ngx-admin 拖拽功能无障碍ARIA 属性设置下一篇告别配置文件混乱2025最实用TOML工具与库全攻略创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED

相关推荐

FlashInfer 归一化算子库全解:RMSNorm/LayerNorm 到训练级 Cake RMSNorm 的完整实战指南

FlashInfer 归一化算子库全解:RMSNorm/LayerNorm 到训练级 Cake RMSNorm 的完整实战指南

大模型深度学习算子库后端高性能计算 【免费下载链接】flashinfer FlashInfer: Kernel Library for LLM Serving 项目地址: https://gitcode.com/gh_mirrors/fl/flashinfer 点击查看 免费下载 导读 本文基于 FlashInfer 官方 API 文档 docs/api/norm.rst&#xff…

📅 2026/10/8 7:50:21
Ponytail技术概念解析与工程应用指南

Ponytail技术概念解析与工程应用指南

我无法基于当前输入生成符合要求的博文。原因如下:输入中仅提供了项目标题"ponytail",以及空置的“相关热搜词”“最新网络热词”和完全空白的网络搜索内容块(内无任何实质信息);缺乏【项目正文】、【关键词…

📅 2026/10/8 7:50:21
text-to-cad实战:自然语言生成可编辑CAD模型的原理与落地

text-to-cad实战:自然语言生成可编辑CAD模型的原理与落地

1. 开篇:从一句话到三维模型,这事终于不再只存在于demo里我第一次看到 text-to-cad 这个词的时候,第一反应是“这不就是文字生成模型的又一个变体吗”。真上手试了一圈之后,发现完全不是这么回事。文本生成图片,哪怕生…

📅 2026/10/8 7:45:20
MORE NEWS

更多资讯

📰

ponytail插件机制详解:从加载原理到实战避坑指南

1. 从“ponytail”这个词说起:它到底指什么 第一次看到“ponytail”这个词,绝大多数人的第一反应是发型——马尾辫。但在技术圈和工具生态里,这个词最近被频繁提起,尤其是和“插件”搭配出现的时候,它指向的其实是另一…

📰

Agent-Reach:为LLM Agent打造稳定可控的工具触达层

过去半年我一直在跟 Agent 项目的稳定性较劲。模型选得再好,prompt 写得再讲究,最后卡壳的地方往往出在模型跟外部工具的连接上:工具一多就乱、接口超时没有兜底、返回的 JSON 把上下文撑爆、权限边界形同虚设。这些问题的本质不是某个工具写…

📰

text-to-cad深度实操:代码生成原理、主流方案对比与工程落地避坑指南

去年我在一个机械设计社群里看到有人贴了一张图:输入一句“一个带矩形底座的U型支架,底座上有四个M6通孔,间距50mm”,系统直接吐出了可编辑的STEP文件和渲染图,全过程不到90秒。底下评论区炸了,有人说“CAD…

📰

基于Python后端与uni-app的校园考研论坛微信小程序开发实践

考研这仗,一半是复习,另一半是信息战。每年那么多考生,找真题、求经验贴、问专业方向,信息全散在QQ群和贴吧里,不是被广告刷屏就是被旧帖子误导。我前阵子给学校做了一套"校园考研论坛交流系统",…

📰

mac版Typora快捷键指南:从入门到高效写作的核心技巧

从 Windows 换到 mac 之后,我花了不少时间重新适应各种软件,但最让我头疼的其实是 Markdown 编辑器。Windows 上我习惯了一整套快捷键,一换系统全乱套。折腾一圈下来,mac 上用得最顺手的还是 Typora,而在 Typora 里&am…

📰

风电并网仿真中的混合储能功率平抑与容量配置实践

风电并网仿真做了几个项目之后,我最大的感受是:功率平抑这件事,方案看着简单,落地全是细节。风力发电的出力受风速直接影响,几分钟内波动率能超过20%,直接往电网送,别说调度部门不答应&#xff…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬