最近项目上遇到复杂的界面事件冲突问题,解决了问题现在整理记录一下view的事件的分发机制。
1、基础知识
(1)事件处理类:
- 事件传递——dispatchTouchEvent()
- 事件拦截——onInterceptTouchEvent()
- 事件消费——onTouchEvent()函数和OnTouchListener
(2)事件类型:
- ACTION_DOWN:表示用户开始触摸.
- ACTION_UP:表示用户在移动(手指或者其他).
- ACTION_MOVE:表示用户抬起了手指.
- ACTION_OUTSIDE: 表示用户触碰超出了正常的UI边界.
- ACTION_CANCEL:表示手势被取消了
多点触控下 - ACTION_POINTER_DOWN:有一个非主要的手指按下了.
- ACTION_POINTER_UP:一个非主要的手指抬起来了.
(3)事件发生的位置,X,Y轴:
- getX() 获得事件发生时,触摸的中间区域在屏幕的X轴.
- getY() 获得事件发生时,触摸的中间区域在屏幕的X轴.
在多点触控中: - getX(int pointerIndex):获得X轴对应手指事件的发生位置.
- getY(int pointerIndex): 获得Y轴对应手指事件的发生位置.
2、案例分析
布局文件,两个自定义View,一个继承自ViewGroup(LinearLayout),一个继承自view(Button)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17<com.coder.tom.android_viewevent.MyViewGroup
android:id="@+id/mylayoutview"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.coder.tom.android_viewevent.MyView
android:id="@+id/mybutton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="myButton"
/>
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button"
/>
</com.coder.tom.android_viewevent.MyViewGroup>
具体看代码:
MyViewGroup继承自LinearLayout1
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
36public class MyViewGroup extends LinearLayout {
String Tag="MyViewGroup";
public MyViewGroup(Context context) {
super(context);
}
public MyViewGroup(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyViewGroup(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
//分发事件
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.d(Tag,"dispatchTouchEvent");
return super.dispatchTouchEvent(ev);
}
//拦截事件
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.d(Tag,"onInterceptTouchEvent");
return super.onInterceptTouchEvent(ev);
// return true;
}
//消费事件
public boolean onTouchEvent(MotionEvent event) {
Log.d(Tag,"onTouchEvent");
return super.onTouchEvent(event);
}
public void setOnTouchListener(OnTouchListener l) {
Log.d(Tag,"setOnTouchListener");
super.setOnTouchListener(l);
}
}
MyView继承自Button1
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
29public class MyView extends Button {
String Tag="MyView";
public MyView(Context context) {
super(context);
}
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
//拦截事件
public boolean dispatchTouchEvent(MotionEvent event) {
Log.d(Tag,"dispatchTouchEvent");
return super.dispatchTouchEvent(event);
}
//消费事件
public boolean onTouchEvent(MotionEvent event) {
Log.d(Tag,"onTouchEvent");
return super.onTouchEvent(event);
}
public void setOnTouchListener(OnTouchListener l) {
Log.d(Tag,"setOnTouchListener");
super.setOnTouchListener(l);
}
}
看一下MainActivity的代码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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77public class MainActivity extends AppCompatActivity {
String Tag="MainActivity";
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.mylayoutview).setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
Toast.makeText(MainActivity.this,"onClick------>button",Toast.LENGTH_SHORT).show();
}
});
findViewById(R.id.mylayoutview).setOnTouchListener(new View.OnTouchListener() {
public boolean onTouch(View v, MotionEvent event) {
eventShow(v,event);
Toast.makeText(MainActivity.this,"onTouch------>button",Toast.LENGTH_SHORT).show();
return false;
}
});
findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
Toast.makeText(MainActivity.this,"onClick------>button",Toast.LENGTH_SHORT).show();
}
});
findViewById(R.id.button).setOnTouchListener(new View.OnTouchListener() {
public boolean onTouch(View v, MotionEvent event) {
eventShow(v,event);
Toast.makeText(MainActivity.this,"onTouch------>button",Toast.LENGTH_SHORT).show();
return false;
}
});
findViewById(R.id.mybutton).setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
Toast.makeText(MainActivity.this,"onClick------>mybutton",Toast.LENGTH_SHORT).show();
}
});
findViewById(R.id.mybutton).setOnTouchListener(new View.OnTouchListener() {
public boolean onTouch(View v, MotionEvent event) {
eventShow(v,event);
Toast.makeText(MainActivity.this,"onTouch------>mybutton",Toast.LENGTH_SHORT).show();
return false;
}
});
}
private void eventShow(View v, 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;
case MotionEvent.ACTION_OUTSIDE:
break;
}
}
//事件拦截
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.d(Tag,"dispatchTouchEvent");
return super.dispatchTouchEvent(ev);
}
public boolean onTouchEvent(MotionEvent event) {
Log.d(Tag,"onTouchEvent");
return super.onTouchEvent(event);
}
}
(1)代码不做任何修改,点击子view,让事件从上往下传递,事件最后在子view上消费;
(2)将父view(ViewGrop)dispatchTouchEvent()返回值改为true
或是onInterceptTouchEvent()改为true,事件将不会往下传递。
1 | //分发事件 |
查看看Log
(3)将所有的点击事件注释,点击界面,最后事件由Activity的OnTouch()消费
(4)给View设置OnClickListener和OnTouchListener事件,onTouchListener的onTouch方法优先级比onTouchEvent高,会先触发。
3、总结
- 事件从Activity.dispatchTouchEvent()开始传递,只要事件没有被停止或是拦截,从上层父View(ViewGroup)开始一只往下(子view)传递,最终子view的OnTouch()对事件进行消费处理。
- 如果事件由上层父view(ViewGroup)传递给子view,父view可以通过dispatchTouchEvent()停止传递或是通过onInterceptTouchEvent()拦截事件,来停止往下传递。
- 如果事件从上往下传递过程中一直没有被停止,且最底层子 View 没有消费事件,事件会反向往上传递,这时父 View(ViewGroup)可以进行消费,如果还是没有被消费的话,最后会到 Activity 的 onTouchEvent()函数。
- OnTouchListener优先于onTouchEvent()对事件进行消费,假如onTouch方法返回false,会接着触发onTouchEvent,反之onTouchEvent方法不会被调用。内置诸如click事件的实现等等都基于onTouchEvent,假如onTouch返回true,这些事件将不会被触发。