本文共 9824 字,大约阅读时间需要 32 分钟。
曾经有个朋友问过我,你到底是想挣钱还是搞技术?
这个问题很简单,但是我却沉思了。这就关于自己的目标了,到底是安安心心有个工作,有钱拿就行?还是继续深入搞技术,宁愿待在工资高的小公司还是愿意待在工资低的大公司?晚上躺在床上想了想,还是搞技术吧,技术好了自然就有钱了。好吧~最终还是脱离不了钱,毕竟只是个庸俗的人。想想那么多要学的就蓝瘦香菇~
================================我是华丽的分手线=================================
言归正传,事件分发是必备技能,因为在很多场景下,都会遇到一些事件冲突,虽然有各式各样的解决方法,但万变不离其宗,所以了解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_DOWNI/MyRelativeLayout: dispatchTouchEvent ACTION_DOWNI/MyRelativeLayout: onInterceptTouchEvent ACTION_DOWNI/MyTextView: dispatchTouchEvent ACTION_DOWNI/MainActivity: onTouch ACTION_DOWNI/MyTextView: onTouchEvent ACTION_DOWN
ACTION_UP的事件分发如下:
I/MainActivity: dispatchTouchEvent ACTION_UPI/MyRelativeLayout: dispatchTouchEvent ACTION_UPI/MyRelativeLayout: onInterceptTouchEvent ACTION_UPI/MyTextView: dispatchTouchEvent ACTION_UPI/MainActivity: onTouch ACTION_UPI/MyTextView: onTouchEvent ACTION_UPI/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也不是必须的。