View 事件分发机制完全掌握
触摸事件、点击事件、拦截机制一网打尽
目录
- 事件分发家族
- dispatchTouchEvent 流程
- onInterceptTouchEvent 拦截
- onTouchEvent 处理
- 滑动冲突解决
- 实战案例
1. 事件分发家族
三大方法
flowchart TD
Dispatch["dispatchTouchEvent(MotionEvent)"] --> Intercept["onInterceptTouchEvent()<br/>仅 ViewGroup"]
Intercept -->|true| SelfTouch["自己的 onTouchEvent"]
Intercept -->|false| Child["传递给子 View"]
Dispatch --> Touch["onTouchEvent(MotionEvent)"]
Touch -->|true| Consume["消费事件"]
Touch -->|false| Bubble["向上传递给父 View 的 onTouchEvent"]
事件类型
| 事件 |
含义 |
| ACTION_DOWN |
手指按下 |
| ACTION_MOVE |
手指移动 |
| ACTION_UP |
手指抬起 |
| ACTION_CANCEL |
事件被取消 |
2. dispatchTouchEvent 流程
ViewGroup 分发流程
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public boolean dispatchTouchEvent(MotionEvent ev) { boolean handled = false; if (onInterceptTouchEvent(ev)) { handled = onTouchEvent(ev); } else { handled = child.dispatchTouchEvent(ev); } return handled; }
|
完整流程图
flowchart TD
Down["DOWN 事件"] --> DispatchDown["dispatchTouchEvent(DOWN)"]
DispatchDown --> InterceptDown["onInterceptTouchEvent(DOWN)"]
InterceptDown -->|true| SelfDown["onTouchEvent(DOWN)<br/>自己处理"]
SelfDown --> FutureSelf["后续 MOVE / UP 继续给自己"]
InterceptDown -->|false| ChildDown["dispatchChildTouchEvent(DOWN)"]
ChildDown --> ChildHandle["子 View 处理"]
ChildHandle -->|不处理| ParentTouch["onTouchEvent(DOWN)"]
DispatchDown --> Result["如果 DOWN 没处理<br/>后续事件不再传递"]
3. onInterceptTouchEvent 拦截
拦截场景
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| class MyViewGroup @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null ) : ViewGroup(context, attrs) { private var lastX = 0f private var lastY = 0f override fun onInterceptTouchEvent(ev: MotionEvent): Boolean { when (ev.action) { MotionEvent.ACTION_DOWN -> { lastX = ev.x lastY = ev.y return false } MotionEvent.ACTION_MOVE -> { val dx = abs(ev.x - lastX) val dy = abs(ev.y - lastY) if (dx > dy && dx > ViewConfiguration.get(context).scaledTouchSlop) { return true } return false } } return super.onInterceptTouchEvent(ev) } }
|
拦截机制要点
1 2 3 4 5 6 7 8 9 10 11 12
| override fun onInterceptTouchEvent(ev: MotionEvent): Boolean { if (ev.action == MotionEvent.ACTION_DOWN) { return false } return true }
|
4. onTouchEvent 处理
View 处理逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| public boolean onTouchEvent(MotionEvent event) { final float x = event.getX(); final float y = event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: if (isFocusable() || isInTouchMode()) { requestFocus(); } break; case MotionEvent.ACTION_MOVE: break; case MotionEvent.ACTION_UP: if (!pointInView(x, y, touchSlop)) { clearFocus(); } if (mPerformClick == null) { mPerformClick = new PerformClick(); } post(mPerformClick); break; } return isClickable() || isLongClickable(); }
|
点击与长按
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| class ClickableView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null ) : View(context, attrs) { fun setOnClickListener(listener: OnClickListener?) { } fun setOnLongClickListener(listener: OnLongClickListener?) { } override fun onTouchEvent(event: MotionEvent): Boolean { when (event.action) { MotionEvent.ACTION_UP -> { performClick() } MotionEvent.ACTION_DOWN -> { checkForLongClick() } } return true } fun performClick(): Boolean { return true } }
|
5. 滑动冲突解决
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| override fun onInterceptTouchEvent(ev: MotionEvent): Boolean { when (ev.action) { MotionEvent.ACTION_DOWN -> { mLastMotionX = ev.x mLastMotionY = ev.y return false } MotionEvent.ACTION_MOVE -> { val deltaX = abs(ev.x - mLastMotionX) val deltaY = abs(ev.y - mLastMotionY) if (deltaX > deltaY) { return true } return false } } return false }
|
外部拦截法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| class ParentViewGroup : ViewGroup { override fun onInterceptTouchEvent(ev: MotionEvent): Boolean { var intercept = false when (ev.action) { MotionEvent.ACTION_DOWN -> { intercept = false lastX = ev.x lastY = ev.y } MotionEvent.ACTION_MOVE -> { val deltaX = abs(ev.x - lastX) val deltaY = abs(ev.y - lastY) if (deltaX > deltaY * 2) { intercept = true } else { intercept = false } } MotionEvent.ACTION_UP -> { intercept = false } } return intercept } }
|
内部拦截法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| class ParentViewGroup : ViewGroup { override fun onInterceptTouchEvent(ev: MotionEvent): Boolean { return ev.action == MotionEvent.ACTION_MOVE } }
class ChildView : View { override fun dispatchTouchEvent(event: MotionEvent): Boolean { when (event.action) { MotionEvent.ACTION_DOWN -> { parent.requestDisallowInterceptTouchEvent(true) } MotionEvent.ACTION_MOVE -> { if (isHorizontalScroll) { parent.requestDisallowInterceptTouchEvent(true) } } MotionEvent.ACTION_UP -> { parent.requestDisallowInterceptTouchEvent(false) } } return super.dispatchTouchEvent(event) } }
|
6. 实战案例
手势检测器 GestureDetector
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| class MyView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null ) : View(context, attrs) { private val gestureDetector = GestureDetector(context, object : GestureDetector.SimpleOnGestureListener() { override fun onDown(e: MotionEvent): Boolean = true override fun onSingleTapUp(e: MotionEvent): Boolean { return true } override fun onDoubleTap(e: MotionEvent): Boolean { return true } override fun onScroll(e1: MotionEvent?, e2: MotionEvent, distanceX: Float, distanceY: Float): Boolean { return true } override fun onFling(e1: MotionEvent?, e2: MotionEvent, velocityX: Float, velocityY: Float): Boolean { return true } override fun onLongPress(e: MotionEvent) { } }) override fun onTouchEvent(event: MotionEvent): Boolean { return gestureDetector.onTouchEvent(event) || super.onTouchEvent(event) } }
|
ScaleGestureDetector 缩放
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| class ZoomableImageView : ImageView { private var scaleFactor = 1f private val scaleGestureDetector = ScaleGestureDetector( context, object : ScaleGestureDetector.SimpleOnScaleGestureListener() { override fun onScale(detector: ScaleGestureDetector): Boolean { scaleFactor *= detector.scaleFactor scaleFactor = max(0.5f, min(scaleFactor, 5f)) scaleX = scaleFactor scaleY = scaleFactor return true } } ) override fun onTouchEvent(event: MotionEvent): Boolean { scaleGestureDetector.onTouchEvent(event) return true } }
|
事件分发流程总结
flowchart TD
Activity["Activity.dispatchTouchEvent"] --> Group["ViewGroup.dispatchTouchEvent"]
Group --> Intercept2["onInterceptTouchEvent(DOWN)"]
Intercept2 -->|false| ChildView["子 View.dispatchTouchEvent"]
ChildView --> ChildTouch["子 View.onTouchEvent"]
ChildTouch -->|false| ParentFallback["父 View.onTouchEvent"]
ChildTouch -->|true| End1["事件结束"]
Group --> SelfFallback["ViewGroup.onTouchEvent(DOWN)"]
SelfFallback -->|false| ActivityTouch["Activity.onTouchEvent"]
面试常问
| 问题 |
答案 |
| DOWN 事件谁先收到? |
Activity → ViewGroup → View |
| onIntercept 返回 true 会怎样? |
后续事件不传递给子 View |
| 子 View 不处理会怎样? |
传递给父 View 的 onTouchEvent |
| 点击和长按哪个先触发? |
长按检测在 DOWN 时开始 |
相关文章: