Android事件分发机制浅析(1)
本文来自网易云社区
作者:孙有军
事件机制是Android中一个比较复杂且重要的知识点,比如你想自定义拦截事件,或者某系组件中嵌套了其他布局,往往会出现这样那样的事件冲突,坑爹啊!!事件主要涵盖onTouch,onClick,onTouchEvent,dispatchTouchEvent,onInterceptTouchEvent等等一系列事件,并且事件间还相互交互耦合,甚至有的事件还有返回值,一会true,一会false,什么情况下返回true,什么情况下返回false,为什么要有返回值,想想这些就感觉整个人都不好了。
但是(万恶的但是),该知识点还是必须要掌握的,知识的深度与广度决定了你走的远度,鉴于此我们就来捅一捅该知识点。
准备工作
俗话说工欲善其事必先利其器,为了看他的执行流程,我们还是先写个样例,打几个日志看看执行流程吧!
首先自定义一个外层布局的Layout,自定义Layout继承了LinearLayout,复写了相应的函数,在调用之前输入日志。如下:
public class Layout extends LinearLayout { public Layout(Context context) { super(context); init(); } public Layout(Context context, AttributeSet attrs) { super(context, attrs); init(); } public Layout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init() { //requestDisallowInterceptTouchEvent(false); } @TargetApi(Build.VERSION_CODES.KITKAT) @Override public boolean onInterceptTouchEvent(MotionEvent ev) { Log.e("Event", "Layout onInterceptTouchEvent " + MotionEvent.actionToString(ev.getAction())); return super.onInterceptTouchEvent(ev); } @TargetApi(Build.VERSION_CODES.KITKAT) @Override public boolean onTouchEvent(MotionEvent event) { Log.e("Event", "Layout onTouchEvent " + MotionEvent.actionToString(event.getAction())); return super.onTouchEvent(event); } @TargetApi(Build.VERSION_CODES.KITKAT) @Override public boolean dispatchTouchEvent(MotionEvent event) { Log.e("Event", "Layout dispatchTouchEvent " + MotionEvent.actionToString(event.getAction())); return super.dispatchTouchEvent(event); } }
我们还自定义了一个LogTextView,继承自TextView,也是为了输出日志,代码如下:
public class LogTextView extends TextView { public LogTextView(Context context) { super(context); } public LogTextView(Context context, AttributeSet attrs) { super(context, attrs); } public LogTextView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @TargetApi(Build.VERSION_CODES.KITKAT) @Override public boolean onTouchEvent(MotionEvent event) { Log.e("Event", "TextView onTouchEvent " + MotionEvent.actionToString(event.getAction())); return super.onTouchEvent(event); } @Override public void setOnTouchListener(OnTouchListener l) { super.setOnTouchListener(l); } @Override public void setOnClickListener(OnClickListener l) { super.setOnClickListener(l); } }
接下来是布局文件了:
<?xml version="1.0" encoding="utf-8"?> <com.sunny.event.wigdet.Layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin"> <com.sunny.event.wigdet.LogTextView android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:layout_marginTop="40dp" android:background="#999999" android:padding="20dp" android:text="Hello World!"/> <ImageView android:id="@+id/image" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:layout_marginTop="20dp" android:src="@mipmap/ic_launcher"/> </com.sunny.event.wigdet.Layout>
布局中嵌套了两个view,一个TextView,一个ImageView。最后就是主界面了。
public class MainActivity extends AppCompatActivity { private LogTextView tv; private ImageView imageView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); findViews(); setViewListener(); } private void findViews() { tv = (LogTextView) findViewById(R.id.textView); imageView = (ImageView) findViewById(R.id.image); } private void setViewListener() { tv.setOnTouchListener(new View.OnTouchListener() { @TargetApi(Build.VERSION_CODES.KITKAT) @Override public boolean onTouch(View v, MotionEvent event) { Log.e("Event", "TextView onTouch " + MotionEvent.actionToString(event.getAction())); return true; } }); tv.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Log.e("Event", "TextView onClick "); } }); imageView.setOnTouchListener(new View.OnTouchListener() { @TargetApi(Build.VERSION_CODES.KITKAT) @Override public boolean onTouch(View v, MotionEvent event) { Log.e("Event", "ImageView onTouch " + MotionEvent.actionToString(event.getAction())); return false; } }); imageView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Log.e("Event", "ImageView onClick "); } }); } }
执行结果
round 1
TextView的onTouch返回为false,点击TextView,日志如下:
05-05 14:04:44.091 27644-27644/com.sunny.event E/Event: Layout dispatchTouchEvent ACTION_DOWN 05-05 14:04:44.091 27644-27644/com.sunny.event E/Event: Layout onInterceptTouchEvent ACTION_DOWN 05-05 14:04:44.091 27644-27644/com.sunny.event E/Event: TextView onTouch ACTION_DOWN 05-05 14:04:44.091 27644-27644/com.sunny.event E/Event: TextView onTouchEvent ACTION_DOWN 05-05 14:04:44.158 27644-27644/com.sunny.event E/Event: Layout dispatchTouchEvent ACTION_MOVE 05-05 14:04:44.158 27644-27644/com.sunny.event E/Event: Layout onInterceptTouchEvent ACTION_MOVE 05-05 14:04:44.158 27644-27644/com.sunny.event E/Event: TextView onTouch ACTION_MOVE 05-05 14:04:44.158 27644-27644/com.sunny.event E/Event: TextView onTouchEvent ACTION_MOVE 05-05 14:04:44.158 27644-27644/com.sunny.event E/Event: Layout dispatchTouchEvent ACTION_UP 05-05 14:04:44.159 27644-27644/com.sunny.event E/Event: Layout onInterceptTouchEvent ACTION_UP 05-05 14:04:44.159 27644-27644/com.sunny.event E/Event: TextView onTouch ACTION_UP 05-05 14:04:44.159 27644-27644/com.sunny.event E/Event: TextView onTouchEvent ACTION_UP 05-05 14:04:44.159 27644-27644/com.sunny.event E/Event: TextView onClick
根据日志我们可以看到首先有一个ACTION_DOWN事件,执行的顺序是Layout的dispatchTouchEvent→onInterceptTouchEvent→(TextView)onTouch要→onTouchEvent,之后的我帕金森发生了,产生了ACTION_MOVE事件,传递的顺序与Down是一致的,最后一个事件是UP事件,正常点击不滑动是不会产生MOVE事件的,在这个这个三个事件最后调用了TextView的onClick事件。
小结:
事件的传递顺序是先外层容器,之后再是具体的View。
onTouch事件先于onTouchEvent事件,onTouchEvent先于onClick事件
round 2
我们将TextView的onTouch事件返回true。重新执行。执行顺序如下:
05-05 14:10:20.556 27644-27644/com.sunny.event E/Event: Layout dispatchTouchEvent ACTION_DOWN 05-05 14:10:20.556 27644-27644/com.sunny.event E/Event: Layout onInterceptTouchEvent ACTION_DOWN 05-05 14:10:20.556 27644-27644/com.sunny.event E/Event: TextView onTouch ACTION_DOWN 05-05 14:10:20.616 27644-27644/com.sunny.event E/Event: Layout dispatchTouchEvent ACTION_MOVE 05-05 14:10:20.616 27644-27644/com.sunny.event E/Event: Layout onInterceptTouchEvent ACTION_MOVE 05-05 14:10:20.616 27644-27644/com.sunny.event E/Event: TextView onTouch ACTION_MOVE 05-05 14:10:20.622 27644-27644/com.sunny.event E/Event: Layout dispatchTouchEvent ACTION_UP 05-05 14:10:20.622 27644-27644/com.sunny.event E/Event: Layout onInterceptTouchEvent ACTION_UP 05-05 14:10:20.622 27644-27644/com.sunny.event E/Event: TextView onTouch ACTION_UP
从日志可以看出如果onTouch返回为true,执行顺序变成了如下:
首先还是ACTION_DOWN事件(Layout)dispatchTouchEvent→onInterceptTouchEvent→(TextView)onTouch,ACTION_MOVE与ACTION_UP执行顺序同ACTION_DOWN,可以发现的是TextView的onTouchEvent事件没有了,并且onClick事件也没有了。
小结
1、onTouch事件的返回值为true会拦截onTouchEvent事件
2、onTouchEvent与onClick有关联
上面的两次执行中每次都调用了onInterceptTouchEvent事件,这个到底又是啥?我们去看看他的返回值是什么?
网易云免费体验馆,0成本体验20+款云产品!
更多网易研发、产品、运营经验分享请访问网易云社区
相关文章:
【推荐】 谈谈分布式Aggregation
【推荐】 网易云安全DDoS高防全新上线 ,游戏防护实力领先