曾经有个朋友问过我,你到底是想挣钱还是搞技术?
这个问题很简单,但是我却沉思了。这就关于自己的目标了,到底是安安心心有个工作,有钱拿就行?还是继续深入搞技术,宁愿待在工资高的小公司还是愿意待在工资低的大公司?晚上躺在床上想了想,还是搞技术吧,技术好了自然就有钱了。好吧~最终还是脱离不了钱,毕竟只是个庸俗的人。想想那么多要学的就蓝瘦香菇~
================================我是华丽的分手线=================================
言归正传,事件分发是必备技能,因为在很多场景下,都会遇到一些事件冲突,虽然有各式各样的解决方法,但万变不离其宗,所以了解Android里的事件分发机制,有利于理解Android的那些事件冲突从何而来,应该在哪个点着手解决。
主要的针对控件是Activity,View,ViewGroup的事件分发,拦截和消费。事件分发机制相关的方法dispatchTouchEvent,onInterceptTouchEvent,onTouchEvent
(1)dispatchTouchEvent:事件的分发,
super.dispatchTouchEvent(ev):允许响应事件向下层(子控件)传递。
true:在dispatchTouchEvent方法中消费该事件,不下层传递。
false:在Activity(最外层控件)中,事件将被消费,在子控件中返回false会将该事件向上层传递,在父控件的onTouchEvent中处理。
(2)onInterceptTouchEvent:事件拦截,
super.onInterceptTouchEvent(ev):允许响应事件向下层(子控件)传递。
true:对事件进行拦截,事件被分配到该控件的onTouchEvent进行处理。
false:同super。
(3)onTouchEvent:事件响应,
super.onInterceptTouchEvent(ev):对事件进行消费(可理解为放行)后可调用onClick。
true:对事件进行消费(可理解为销毁)后不会调用onClick。(很多资料都说true和super没区别,这是个误区!)
false:将该事件向上层传递,在父控件的onTouchEvent中处理。
这里需要指出的是:Activity,View只有dispatchTouchEvent,onTouchEvent。
ViewGroup有dispatchTouchEvent,onInterceptTouchEvent,onTouchEvent。
这些都是事件分发的基础,这些网上多得是,只是有一些细节要亲自试验才知道正确与否。
Activity嵌套Relative内嵌TextView
先自定义一个TextView,重写dispatchTouchEvent,onInterceptTouchEvent,onTouchEvent。
public class MyTextView extends TextView {
private static final String TAG = "MyTextView";
public MyTextView(Context context) {
super(context);
}
public MyTextView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
/**
* 在TextView中,只有dispatchTouchEvent和onTouchEvent
* dispatchTouchEvent返回值:
* (1) super.dispatchTouchEvent(ev):允许该响应事件向下层(子控件)传递
* (2)true:在dispatchTouchEvent方法中消费该事件,不向下层传递
* (3)false:把该事件响应停止向下层传递,向上层(父控件)传递,父控件的onTouchEvent中进行处理
*/
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
Log.i(TAG,"dispatchTouchEvent ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.i(TAG,"dispatchTouchEvent ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.i(TAG,"dispatchTouchEvent ACTION_UP");
break;
}
return super.dispatchTouchEvent(event);
}
/**
* 在TextView中,只有dispatchTouchEvent和onTouchEvent
* onTouchEvent返回值:
* (1) super.dispatchTouchEvent(ev):在该方法中消费该事件
* (2)true:在该方法中消费该事件
* (3)false:向上层(父控件)传递,父控件的onTouchEvent中进行处理
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
Log.i(TAG,"onTouchEvent ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.i(TAG,"onTouchEvent ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.i(TAG,"onTouchEvent ACTION_UP");
break;
}
return super.onTouchEvent(event);
}
}
自定义RelativeLayout
public class MyRelativeLayout extends RelativeLayout {
private static final String TAG = "MyRelativeLayout";
public MyRelativeLayout(Context context) {
super(context);
}
public MyRelativeLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
Log.i(TAG,"dispatchTouchEvent ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.i(TAG,"dispatchTouchEvent ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.i(TAG,"dispatchTouchEvent ACTION_UP");
break;
}
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
Log.i(TAG,"onInterceptTouchEvent ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.i(TAG,"onInterceptTouchEvent ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.i(TAG,"onInterceptTouchEvent ACTION_UP");
break;
}
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
Log.i(TAG,"onTouchEvent ACTION_DOWN");
return super.onTouchEvent(event);
case MotionEvent.ACTION_MOVE:
Log.i(TAG,"onTouchEvent ACTION_MOVE");
return super.onTouchEvent(event);
case MotionEvent.ACTION_UP:
Log.i(TAG,"onTouchEvent ACTION_UP");
return super.onTouchEvent(event);
default:
return super.onTouchEvent(event);
}
}
}
在Activity中
public class MainActivity extends AppCompatActivity implements View.OnTouchListener, View.OnClickListener {
/**
* 点击MyTextView会产生3个事件,分别是ACTION_DOWN,ACTION_MOVE,ACTION_UP,只有执行了ACTION_DOWN,ACTION_UP之后,并且onTouchEvent返回super才会执行onClick
* 每个事件的正常默认传递流程是:
* Activity dispatchTouchEvent --> MyTextView dispatchTouchEvent -->MyTextView onTouch -->MyTextView onTouchEvent ==>MyTextView onClick
*/
private static final String TAG = "MainActivity";
private MyTextView myTextView;
private MyRelativeLayout myRelativeLayout;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
myTextView = (MyTextView) findViewById(R.id.my_textview);
myRelativeLayout = (MyRelativeLayout) findViewById(R.id.activity_main);
myTextView.setOnTouchListener(this);
myTextView.setOnClickListener(this);
/**
*在事件分发中,只有最底层控件的才会触发onTouch方法
*/
myRelativeLayout.setOnTouchListener(this);
myRelativeLayout.setOnClickListener(this);
}
/**
* 在Activity中,只有dispatchTouchEvent和onTouchEvent
* dispatchTouchEvent返回值:
* (1) super.dispatchTouchEvent(ev):允许该响应事件向下层(子控件)传递
* (2)true:在dispatchTouchEvent方法中消费该事件,不向下层传递
* (3)false:在dispatchTouchEvent方法中消费该事件,不向下层传递
*/
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
Log.i(TAG,"dispatchTouchEvent ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.i(TAG,"dispatchTouchEvent ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.i(TAG,"dispatchTouchEvent ACTION_UP");
break;
}
return super.dispatchTouchEvent(ev);
}
/**
* onTouchEvent返回值:
* (1) super.dispatchTouchEvent(ev):在该方法中消费该事件
* (2)true:在该方法中消费该事件
* (3)false:在该方法中消费该事件
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
Log.i(TAG,"onTouchEvent ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.i(TAG,"onTouchEvent ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.i(TAG,"onTouchEvent ACTION_UP");
break;
}
return super.onTouchEvent(event);
}
/**
* TextView的onTouch方法返回值:
* (1)true:在onTouch方法中消费该事件,不向下层传递
* (2)false:向下层传递
*/
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
switch (motionEvent.getAction()){
case MotionEvent.ACTION_DOWN:
Log.i(TAG,"onTouch ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.i(TAG,"onTouch ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.i(TAG,"onTouch ACTION_UP");
break;
}
return false;
}
/**
* TextView的onClick方法
*/
@Override
public void onClick(View view) {
if (view.getId() == R.id.my_textview){
Log.i(TAG,"MyTextView onClick");
}else {
Log.i(TAG,"MyRelativeLayout onClick");
}
}
}
正常一次点击事件,打印的日志
ACTION_DOWN的事件分发如下:(和ACTION_MOVE一样)
I/MainActivity: dispatchTouchEvent ACTION_DOWN
I/MyRelativeLayout: dispatchTouchEvent ACTION_DOWN
I/MyRelativeLayout: onInterceptTouchEvent ACTION_DOWN
I/MyTextView: dispatchTouchEvent ACTION_DOWN
I/MainActivity: onTouch ACTION_DOWN
I/MyTextView: onTouchEvent ACTION_DOWN
ACTION_UP的事件分发如下:
I/MainActivity: dispatchTouchEvent ACTION_UP
I/MyRelativeLayout: dispatchTouchEvent ACTION_UP
I/MyRelativeLayout: onInterceptTouchEvent ACTION_UP
I/MyTextView: dispatchTouchEvent ACTION_UP
I/MainActivity: onTouch ACTION_UP
I/MyTextView: onTouchEvent ACTION_UP
I/MainActivity: MyTextView onClick
相应的事件分发流程图
总结:(1) 触摸事件的传递流程是从dispatchTouchEvent开始的,若不经过人为干扰,直接返回super会一层一层的向子控件传递,最后在子控件的onTouchEvent中进行处理,否则将一层一层向外传递,直至消失。
(2)在向内层传递过程中,返回true事件就会提前消费。不会再向内层传递。
(3)控件调用onTouchEvent方法之前,会调用onTouch方法,返回true表示在onTouch消费事件,返回false表示继续向下传递。
那么我想问一下,onClick是在view调用了ACTION_DOWN,ACTION_MOVE,ACTION_UP事件分发之后才触发,那么如果我在VIew的dispatchTouchEvent方法ACTION_DOWN时返回true,在ACTION_UP时返回super,会触发onClick方法吗?(希望读者自己动手实验一下,这些都是小细节)。
关于调用onClick方法,我试验了一些情况。给出以下总结,不对的欢迎指正。
TextView需要调用onClick,那么在ACTION_DOWN和ACTION_UP两个事件分发中,事件需要在TextView的onTouchEvent中消费,并且最后要返回super,返回true时无法调用onClick的。不信的可以试试。ACTION_MOVE也不是必须的。