view 事件传递

    近项目上遇到复杂的界面事件冲突问题,解决了问题现在整理记录一下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继承自LinearLayout

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
public 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);
}
//分发事件
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.d(Tag,"dispatchTouchEvent");
return super.dispatchTouchEvent(ev);
}
//拦截事件
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.d(Tag,"onInterceptTouchEvent");
return super.onInterceptTouchEvent(ev);
// return true;
}
//消费事件
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d(Tag,"onTouchEvent");
return super.onTouchEvent(event);
}
@Override
public void setOnTouchListener(OnTouchListener l) {
Log.d(Tag,"setOnTouchListener");
super.setOnTouchListener(l);
}
}

MyView继承自Button

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
public 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);
}
//拦截事件
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
Log.d(Tag,"dispatchTouchEvent");
return super.dispatchTouchEvent(event);
}
//消费事件
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d(Tag,"onTouchEvent");
return super.onTouchEvent(event);
}
@Override
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
77
public class MainActivity extends AppCompatActivity {
String Tag="MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

findViewById(R.id.mylayoutview).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this,"onClick------>button",Toast.LENGTH_SHORT).show();
}
});
findViewById(R.id.mylayoutview).setOnTouchListener(new View.OnTouchListener() {
@Override
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() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this,"onClick------>button",Toast.LENGTH_SHORT).show();
}
});
findViewById(R.id.button).setOnTouchListener(new View.OnTouchListener() {
@Override
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() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this,"onClick------>mybutton",Toast.LENGTH_SHORT).show();
}
});
findViewById(R.id.mybutton).setOnTouchListener(new View.OnTouchListener() {
@Override
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;
}
}
//事件拦截
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.d(Tag,"dispatchTouchEvent");
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d(Tag,"onTouchEvent");
return super.onTouchEvent(event);
}
}

(1)代码不做任何修改,点击子view,让事件从上往下传递,事件最后在子view上消费;

(2)将父view(ViewGrop)dispatchTouchEvent()返回值改为true
或是onInterceptTouchEvent()改为true,事件将不会往下传递。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
    //分发事件
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.d(Tag,"dispatchTouchEvent");
// return super.dispatchTouchEvent(ev);
return true;
}
//拦截事件
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.d(Tag,"onInterceptTouchEvent");
return super.onInterceptTouchEvent(ev);
// return true;
}

查看看Log

(3)将所有的点击事件注释,点击界面,最后事件由Activity的OnTouch()消费

(4)给View设置OnClickListener和OnTouchListener事件,onTouchListener的onTouch方法优先级比onTouchEvent高,会先触发。

3、总结

  1. 事件从Activity.dispatchTouchEvent()开始传递,只要事件没有被停止或是拦截,从上层父View(ViewGroup)开始一只往下(子view)传递,最终子view的OnTouch()对事件进行消费处理。
  2. 如果事件由上层父view(ViewGroup)传递给子view,父view可以通过dispatchTouchEvent()停止传递或是通过onInterceptTouchEvent()拦截事件,来停止往下传递。
  3. 如果事件从上往下传递过程中一直没有被停止,且最底层子 View 没有消费事件,事件会反向往上传递,这时父 View(ViewGroup)可以进行消费,如果还是没有被消费的话,最后会到 Activity 的 onTouchEvent()函数。
  4. OnTouchListener优先于onTouchEvent()对事件进行消费,假如onTouch方法返回false,会接着触发onTouchEvent,反之onTouchEvent方法不会被调用。内置诸如click事件的实现等等都基于onTouchEvent,假如onTouch返回true,这些事件将不会被触发。