Appearance
03_事件分发机制详解
一、Android 事件体系概述
1.1 事件分类
Android 中的输入事件主要分为以下几类:
| 事件类型 | 说明 | 典型场景 |
|---|---|---|
| MotionEvent | 触摸事件 | 点击、滑动、长按 |
| KeyEvent | 键盘事件 | 物理按键、软键盘 |
| ClipboardEvent | 剪贴板事件 | 复制粘贴 |
| TrackballEvent | 轨迹球事件 | 老式设备导航 |
1.2 事件分发与回调
事件分发机制:从上到下传递事件
事件回调机制:从下到上响应事件1.3 事件流程概览
Activity
↓
Window
↓
PhoneWindow
↓
DecorView
↓
ViewGroup (布局)
↓
View (子视图)二、MotionEvent 事件详解
2.1 事件动作类型
java
// MotionEvent 的动作类型
MotionEvent.ACTION_DOWN // 手指按下
MotionEvent.ACTION_MOVE // 手指移动
MotionEvent.ACTION_UP // 手指抬起
MotionEvent.ACTION_CANCEL // 事件取消
MotionEvent.ACTION_OUTSIDE // 触摸区域外抬起
// 多点触控
MotionEvent.ACTION_POINTER_DOWN // 第二个手指按下
MotionEvent.ACTION_POINTER_UP // 第二个手指抬起2.2 事件对象结构
java
public class MotionEvent {
private long mEventTime; // 事件发生时间
private long mDownTime; // 第一个 DOWN 的时间
private int mAction; // 动作类型
private int mMetaState; // 修饰键状态
private int mSource; // 事件来源
private int mEdgeFlags; // 边缘标志
private float mX; // X 坐标
private float mY; // Y 坐标
private float mPressure; // 压力值
private float mSize; // 接触面积
private float mTouchMajor; // 主要接触轴
private float mTouchMinor; // 次要接触轴
private int mToolType; // 工具类型
private int mPointerCount; // 指针数量
// ...
}2.3 获取事件信息
java
public class TouchInfoView extends View {
@Override
public boolean onTouchEvent(MotionEvent event) {
// 获取动作类型
int action = event.getAction();
// 获取 X、Y 坐标
float x = event.getX();
float y = event.getY();
// 获取原始坐标(相对于屏幕)
float rawX = event.getRawX();
float rawY = event.getRawY();
// 获取指针数量(多点触控)
int pointerCount = event.getPointerCount();
// 获取特定指针的 ID
int pointerId = event.getPointerId(0);
// 获取特定指针的坐标
float pointerX = event.getX(0);
float pointerY = event.getY(0);
// 获取压力值(0.0 - 1.0)
float pressure = event.getPressure();
// 获取事件时间
long eventTime = event.getEventTime();
long downTime = event.getDownTime();
return super.onTouchEvent(event);
}
}2.4 多点触控处理
java
public class MultiTouchView extends View {
@Override
public boolean onTouchEvent(MotionEvent event) {
int pointerCount = event.getPointerCount();
for (int i = 0; i < pointerCount; i++) {
int pointerId = event.getPointerId(i);
float x = event.getX(i);
float y = event.getY(i);
// 处理每个指针
Log.d("MultiTouch", "Pointer " + pointerId + ": (" + x + ", " + y + ")");
}
return true;
}
}三、事件分发三大方法
3.1 dispatchTouchEvent 事件分发
java
public boolean dispatchTouchEvent(MotionEvent ev) {
// 1. 调用回调
if (onInterceptTouchEvent(ev)) {
// 2. 拦截事件,调用自己的 onTouchEvent
return onTouchEvent(ev);
}
// 3. 不拦截,分发给子 View
return super.dispatchTouchEvent(ev);
}3.1.1 View 的 dispatchTouchEvent
java
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
// 1. 检查是否可用
if (mListener != null) {
if (mListener.onTouchEvent(event)) {
return true;
}
}
// 2. 检查触摸监听器
if (onTouchEvent(event)) {
return true;
}
// 3. 检查点击监听器
if (mPrivateFlags & PFLAG_CLICKABLE) != 0) {
// 处理点击事件
return performClick();
}
return false;
}3.1.2 ViewGroup 的 dispatchTouchEvent
java
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
// 事件预处理
if (onFilterTouchEventForSecurity(ev)) {
return false;
}
// 调用点击监听器
if (mListener != null) {
if (mListener.onTouchEvent(ev)) {
return true;
}
}
// 获取动作和指针 ID
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
// DOWN 事件:寻找合适的子 View
if (actionMasked == MotionEvent.ACTION_DOWN) {
mTouchTarget = null;
// 从上到下寻找可接收事件的子 View
for (int i = getChildCount() - 1; i >= 0; i--) {
View child = getChildAt(i);
if (isEventWithin(child, ev)) {
if (child.dispatchTouchEvent(ev)) {
mTouchTarget = child;
return true;
}
}
}
}
// MOVE 事件:根据拦截结果分发
else if (actionMasked == MotionEvent.ACTION_MOVE) {
// 检查是否需要拦截
if (onInterceptTouchEvent(ev)) {
// 拦截,分发给自己
return onTouchEvent(ev);
}
// 不拦截,继续分发给 mTouchTarget
if (mTouchTarget != null) {
return mTouchTarget.dispatchTouchEvent(ev);
}
}
// UP/CANCEL 事件
else if (actionMasked == MotionEvent.ACTION_UP
|| actionMasked == MotionEvent.ACTION_CANCEL) {
if (mTouchTarget != null) {
mTouchTarget.dispatchTouchEvent(ev);
mTouchTarget = null;
}
}
return super.dispatchTouchEvent(ev);
}3.2 onInterceptTouchEvent 事件拦截
java
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
// 返回 true:拦截事件,后续事件都交给自己的 onTouchEvent 处理
// 返回 false:不拦截,事件继续向下传递
return false;
}3.2.1 拦截时机
java
public class InterceptViewGroup extends ViewGroup {
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
// 在 DOWN 时决定是否拦截
// 一旦拦截,后续的 MOVE、UP 都会交给自己的 onTouchEvent
return false; // 不拦截
case MotionEvent.ACTION_MOVE:
// 在 MOVE 时可以改变决定
if (shouldIntercept(ev)) {
return true; // 拦截
}
return false;
default:
return super.onInterceptTouchEvent(ev);
}
}
private boolean shouldIntercept(MotionEvent ev) {
// 判断是否需要拦截的逻辑
return false;
}
}3.2.2 拦截注意事项
1. DOWN 事件如果返回 false,后续可能不会再调用 onInterceptTouchEvent
2. 拦截后,之前的子 View 会收到 CANCEL 事件
3. 拦截后,所有后续事件都会交给自己的 onTouchEvent3.3 onTouchEvent 事件处理
java
@Override
public boolean onTouchEvent(MotionEvent event) {
// 处理触摸事件
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// 处理按下
break;
case MotionEvent.ACTION_MOVE:
// 处理移动
break;
case MotionEvent.ACTION_UP:
// 处理抬起
break;
case MotionEvent.ACTION_CANCEL:
// 处理取消
break;
}
return true; // 消费事件
}3.3.1 消费事件
java
public class ConsumerView extends View {
@Override
public boolean onTouchEvent(MotionEvent event) {
// 返回 true:消费事件,事件结束
// 返回 false:不消费,事件向上回溯
return true;
}
}四、完整事件流程图
4.1 事件传递路径
Activity.dispatchTouchEvent()
↓
Window.superDispatchTouchEvent()
↓
PhoneWindow.superDispatchTouchEvent()
↓
DecorView.dispatchTouchEvent()
↓
ViewGroup.dispatchTouchEvent()
├─→ onInterceptTouchEvent() = true
│ ↓
│ onTouchEvent()
│
└─→ onInterceptTouchEvent() = false
↓
child.dispatchTouchEvent()
↓
child.onTouchEvent()4.2 事件处理顺序
1. Activity.dispatchTouchEvent()
2. Window.superDispatchTouchEvent()
3. DecorView.dispatchTouchEvent()
4. ViewGroup.dispatchTouchEvent()
↓
5. ViewGroup.onInterceptTouchEvent() ← 拦截点
↓ (如果拦截)
6. ViewGroup.onTouchEvent()
↓ (如果不拦截)
7. ChildView.dispatchTouchEvent()
↓
8. ChildView.onTouchEvent()4.3 事件回溯机制
当子 View 返回 false 时:
父 ViewGroup.onTouchEvent() ← 回溯
↓
继续向上回溯...五、滑动冲突与解决
5.1 冲突类型
5.1.1 外部嵌套冲突
父容器可滚动,子 View 也可滚动
例如:ScrollView 嵌套 ListView5.1.2 内部嵌套冲突
子 View 之间竞争事件
例如:ViewPager 内部嵌套横向滚动的 View5.2 经典冲突场景
5.2.1 ScrollView 嵌套 RecyclerView
xml
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:orientation="vertical">
<TextView android:text="头部内容"/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<TextView android:text="底部内容"/>
</LinearLayout>
</ScrollView>5.2.2 ViewPager 嵌套 ScrollView
xml
<ViewPager
android:id="@+id/viewPager"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- 可滚动内容 -->
</ScrollView>
</ViewPager>5.2.3 自定义 View 冲突
java
public class SwipeRefreshLayout extends FrameLayout {
// 下拉刷新与内部列表的滚动冲突
}5.3 解决方案
5.3.1 方案一:父控件拦截法
让父控件在适当的时候拦截事件:
java
public class CustomScrollView extends ScrollView {
private View mChildView;
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
// 记录 DOWN 时的 Y 坐标
mLastY = ev.getY();
// 找到可能接收事件的子 View
mChildView = findTargetChild(ev);
break;
case MotionEvent.ACTION_MOVE:
float currentY = ev.getY();
float deltaY = currentY - mLastY;
// 如果子 View 可以滚动,且滚动方向与父 View 一致
if (mChildView != null && canChildScroll(deltaY)) {
// 不拦截,让子 View 处理
return false;
}
// 否则拦截,父 View 处理
return true;
default:
break;
}
return super.onInterceptTouchEvent(ev);
}
private View findTargetChild(MotionEvent ev) {
// 找到点击区域的子 View
return null;
}
private boolean canChildScroll(float deltaY) {
// 判断子 View 是否可以滚动
if (mChildView instanceof NestedScrollingChild) {
return ((NestedScrollingChild) mChildView)
.canScrollVertically((int) deltaY);
}
return false;
}
}5.3.2 方案二:子控件拦截法
让子控件在需要时请求父控件不拦截:
java
public class CustomRecyclerView extends RecyclerView {
private OnTouchListener mOuterTouchListener;
public void setOnTouchListener(OnTouchListener listener) {
mOuterTouchListener = listener;
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
// 如果父容器请求拦截,交给父容器处理
if (mOuterTouchListener != null) {
if (mOuterTouchListener.onTouch(this, ev)) {
return true;
}
}
return super.dispatchTouchEvent(ev);
}
}5.3.3 方案三:同时处理法
父控件和子控件同时处理事件:
java
public class NestedViewGroup extends ViewGroup {
private float mLastX, mLastY;
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
mLastX = ev.getX();
mLastY = ev.getY();
return false; // 不拦截,让子 View 先处理
case MotionEvent.ACTION_MOVE:
float dx = ev.getX() - mLastX;
float dy = ev.getY() - mLastY;
// 判断滑动方向
if (Math.abs(dx) > Math.abs(dy)) {
// 横向滑动,拦截
return true;
} else {
// 纵向滑动,不拦截
return false;
}
default:
return false;
}
}
}5.3.4 方案四:NestedScrolling 机制
Android 5.0 引入的标准解决方案:
java
// 父 View 实现 NestedScrollingParent
public class NestedScrollViewParent extends ViewGroup
implements NestedScrollingParent {
@Override
public boolean onStartNestedScroll(View child, View target, int axes) {
return (axes & View.SCROLL_AXIS_VERTICAL) != 0;
}
@Override
public void onNestedScrollAccepted(View child, View target, int axes) {
// 接受嵌套滚动
}
@Override
public void onNestedPreScroll(View target, int dx, int dy,
int[] consumed) {
// 在子 View 滚动前处理
}
@Override
public boolean onNestedFling(View target, float velocityX,
float velocityY, boolean consumed) {
// 处理快速滑动
return false;
}
@Override
public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
int dxUnconsumed, int dyUnconsumed) {
// 在子 View 滚动后处理
}
}
// 子 View 实现 NestedScrollingChild
public class NestedRecyclerView extends RecyclerView
implements NestedScrollingChild {
@Override
public void setNestedScrollingEnabled(boolean enabled) {
// 启用嵌套滚动
}
@Override
public boolean startNestedScroll(int axes) {
// 启动嵌套滚动
return super.startNestedScroll(axes);
}
@Override
public void stopNestedScroll() {
// 停止嵌套滚动
super.stopNestedScroll();
}
@Override
public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed,
int dxUnconsumed, int dyUnconsumed,
int[] offsetInWindow) {
// 分派嵌套滚动
return super.dispatchNestedScroll(dxConsumed, dyConsumed,
dxUnconsumed, dyUnconsumed, offsetInWindow);
}
}5.4 实际案例分析
5.4.1 SwipeRefreshLayout + RecyclerView
java
// SwipeRefreshLayout 已经实现了 NestedScrollingParent
// RecyclerView 已经实现了 NestedScrollingChild
// 所以可以直接嵌套使用
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/swipe_refresh"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>5.4.2 ViewPager2 + 嵌套滚动
xml
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/viewPager"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- 内容 -->
</androidx.core.widget.NestedScrollView>
</androidx.viewpager2.widget.ViewPager2>5.4.3 CoordinatorLayout + AppBarLayout
xml
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:layout_width="match_parent"
android:layout_height="200dp"
app:layout_scrollFlags="scroll|exitUntilCollapsed">
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_collapseMode="parallax"/>
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="可滚动内容"/>
</androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>六、点击事件处理
6.1 点击监听方式对比
6.1.1 setOnClickListener
java
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 处理点击
}
});6.1.2 实现 OnClickListener 接口
java
public class MyActivity extends AppCompatActivity
implements View.OnClickListener {
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.button1:
break;
case R.id.button2:
break;
}
}
}6.1.3 直接重写 onTouchEvent
java
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mDownX = event.getX();
mDownY = event.getY();
break;
case MotionEvent.ACTION_UP:
// 判断是否在 View 范围内
if (isInTouchArea(event.getX(), event.getY())) {
// 处理点击
}
break;
}
return true;
}6.2 点击事件触发条件
java
// View 的 performClick 方法
public boolean performClick() {
if (mListener != null) {
if (mListener.onClick(this)) {
return true;
}
}
if (isClickable()) {
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
playSoundEffect(SoundEffectConstants.CLICK);
}
return true;
}6.3 点击与长按冲突
java
public class ClickableView extends View {
private static final long CLICK_TIMEOUT = 300; // 300ms
private long mDownTime;
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mDownTime = SystemClock.uptimeMillis();
// 启动长按检测
postDelayed(mLongPressRunnable, ViewConfiguration.getLongPressTimeout());
break;
case MotionEvent.ACTION_UP:
// 取消长按检测
removeCallbacks(mLongPressRunnable);
long duration = SystemClock.uptimeMillis() - mDownTime;
if (duration < CLICK_TIMEOUT) {
// 点击事件
performClick();
}
break;
case MotionEvent.ACTION_CANCEL:
removeCallbacks(mLongPressRunnable);
break;
}
return true;
}
}七、GestureDetector 与 ScaleGestureDetector
7.1 GestureDetector 使用
java
public class GestureView extends View {
private GestureDetector mGestureDetector;
public GestureView(Context context) {
super(context);
mGestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onDown(MotionEvent e) {
// 按下
return true;
}
@Override
public void onShowPress(MotionEvent e) {
// 显示按下效果
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
// 单击
return true;
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2,
float distanceX, float distanceY) {
// 滚动
return true;
}
@Override
public void onLongPress(MotionEvent e) {
// 长按
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2,
float velocityX, float velocityY) {
// 快速滑动
return true;
}
});
}
@Override
public boolean onTouchEvent(MotionEvent event) {
return mGestureDetector.onTouchEvent(event);
}
}7.2 ScaleGestureDetector 使用
java
public class ScaleView extends View {
private ScaleGestureDetector mScaleDetector;
public ScaleView(Context context) {
super(context);
mScaleDetector = new ScaleGestureDetector(context,
new ScaleGestureDetector.SimpleOnScaleGestureListener() {
@Override
public boolean onScale(ScaleGestureDetector detector) {
// 缩放
float scaleFactor = detector.getScaleFactor();
// 处理缩放
return true;
}
});
}
@Override
public boolean onTouchEvent(MotionEvent event) {
mScaleDetector.onTouchEvent(event);
return true;
}
}7.3 组合使用
java
public class CombinedGestureView extends View {
private GestureDetector mGestureDetector;
private ScaleGestureDetector mScaleDetector;
public CombinedGestureView(Context context) {
super(context);
mGestureDetector = new GestureDetector(context,
new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2,
float velocityX, float velocityY) {
// 处理快速滑动
return true;
}
});
mScaleDetector = new ScaleGestureDetector(context,
new ScaleGestureDetector.SimpleOnScaleGestureListener() {
@Override
public boolean onScale(ScaleGestureDetector detector) {
// 处理缩放
return true;
}
});
}
@Override
public boolean onTouchEvent(MotionEvent event) {
// 同时处理多种手势
mScaleDetector.onTouchEvent(event);
mGestureDetector.onTouchEvent(event);
return true;
}
}八、面试考点总结
8.1 基础知识点
Q1: 事件分发的完整流程是什么?
A:
Activity → Window → PhoneWindow → DecorView → ViewGroup → View具体流程:
- Activity.dispatchTouchEvent()
- Window.superDispatchTouchEvent()
- DecorView.dispatchTouchEvent()
- ViewGroup.dispatchTouchEvent() → onInterceptTouchEvent() → onTouchEvent()
- View.dispatchTouchEvent() → onTouchEvent()
Q2: dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent 的区别?
A:
- dispatchTouchEvent: 事件分发,决定是否将事件分发给子 View
- onInterceptTouchEvent: 事件拦截,ViewGroup 特有,决定是否拦截事件
- onTouchEvent: 事件处理,真正处理触摸事件的方法
Q3: 事件是如何传递的?
A:
- 从上到下分发:Activity → ViewGroup → View
- 从下到上响应:View → ViewGroup → Activity
- 拦截后改变方向:ViewGroup.onInterceptTouchEvent() = true
8.2 进阶知识点
Q4: 如何解决滑动冲突?
A:
- 父控件拦截法: 父控件在适当时候拦截事件
- 子控件拦截法: 子控件请求父控件不拦截
- NestedScrolling: Android 5.0 标准解决方案
Q5: onInterceptTouchEvent 什么时候会被调用?
A:
- 每次事件都会调用,直到返回 true
- 一旦返回 true,后续事件不再调用(除非新的 DOWN 事件)
Q6: 什么是事件回溯?
A: 当子 View 的 onTouchEvent 返回 false 时,事件会向上回溯到父 View 的 onTouchEvent。
8.3 实战题目
Q7: 实现一个可拖拽的 View
java
public class DraggableView extends View {
private float mDownX, mDownY;
private float mLastX, mLastY;
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mDownX = event.getX();
mDownY = event.getY();
mLastX = mDownX;
mLastY = mDownY;
break;
case MotionEvent.ACTION_MOVE:
float currentX = event.getX();
float currentY = event.getY();
float dx = currentX - mLastX;
float dy = currentY - mLastY;
// 移动 View
offsetLeftAndRight((int) dx);
offsetTopAndBottom((int) dy);
mLastX = currentX;
mLastY = currentY;
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
// 松开处理
break;
}
return true;
}
}Q8: 自定义一个可折叠的 View
java
public class FoldableView extends ViewGroup {
private boolean mIsExpanded = true;
private int mCollapsedHeight;
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 根据展开状态测量高度
if (mIsExpanded) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
} else {
// 测量折叠后的高度
int height = MeasureSpec.makeMeasureSpec(mCollapsedHeight, MeasureSpec.EXACTLY);
super.onMeasure(widthMeasureSpec, height);
}
}
}8.4 常见面试题
| 问题 | 考察点 | 难度 |
|---|---|---|
| 事件分发流程 | 基础理解 | ⭐⭐ |
| onInterceptTouchEvent 原理 | 拦截机制 | ⭐⭐⭐ |
| 滑动冲突解决 | 实战应用 | ⭐⭐⭐⭐ |
| NestedScrolling 原理 | 深度理解 | ⭐⭐⭐⭐ |
| GestureDetector 使用 | 手势处理 | ⭐⭐ |
| 点击事件触发原理 | 底层原理 | ⭐⭐⭐ |
九、性能优化
9.1 事件处理优化
java
// ❌ 避免在 onTouchEvent 中进行复杂计算
@Override
public boolean onTouchEvent(MotionEvent event) {
// 耗时操作...
return true;
}
// ✅ 使用异步处理
@Override
public boolean onTouchEvent(MotionEvent event) {
// 快速响应
post(new Runnable() {
@Override
public void run() {
// 耗时操作...
}
});
return true;
}9.2 避免内存泄漏
java
public class TouchView extends View {
private OnTouchListener mTouchListener;
public void setOnTouchListener(OnTouchListener listener) {
// 取消旧的监听器
if (mTouchListener != null) {
// 清理资源
}
mTouchListener = listener;
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
// 清理所有回调
mTouchListener = null;
}
}十、总结
10.1 核心要点
- 事件分发三要素: dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent
- 传递方向: 从上到下分发,从下到上响应
- 滑动冲突: 掌握多种解决方案,优先使用 NestedScrolling
- 手势处理: 熟练使用 GestureDetector 和 ScaleGestureDetector
10.2 学习资源
本文档持续更新,欢迎补充和完善。