Android Activity 触摸屏事件派发机制和源码分析

来源:互联网 发布:黑洞特效源码 编辑:程序博客网 时间:2024/05/17 04:39

Android Activity 触摸屏事件派发机制和源码分析

之前查看View 和ViewGroup的源码的时候就得出结论,ViewGroup的触摸事件是从dispatchTouchEvent()开始,经过一序列判断和处理传递到View的dispatchTouchEvent().当时我们一般看到的页面都是Activity,Activity的触摸事件是怎么出来的呢?
以下分析基于Android L.

1.例子验证

1.1相关代码

 自定义的Button和LinearLayout
  1. public class DoovButton extends Button {
  2. private static final String TAG = "DoovButton";
  3. public DoovButton(Context context, AttributeSet attrs) {
  4. super(context, attrs);
  5. }
  6. @Override
  7. public boolean dispatchTouchEvent(MotionEvent event) {
  8. Log.d(TAG,"dispatchTouchEvent()" + " action=" + event.getAction());
  9. return super.dispatchTouchEvent(event);
  10. }
  11. @Override
  12. public boolean onTouchEvent(MotionEvent event) {
  13. Log.d(TAG,"onTouchEvent()"+ " action=" + event.getAction());
  14. return super.onTouchEvent(event);
  15. }
  16. }

  1. public class DoovLinearLayout extends LinearLayout {
  2. private static final String TAG = "DoovLinearLayout";
  3. public DoovLinearLayout(Context context, AttributeSet attrs) {
  4. super(context, attrs);
  5. }
  6. @Override
  7. public boolean dispatchTouchEvent(MotionEvent ev) {
  8. Log.d(TAG,"dispatchTouchEvent()"+ " action=" + ev.getAction());
  9. return super.dispatchTouchEvent(ev);
  10. }
  11. @Override
  12. public boolean onInterceptTouchEvent(MotionEvent ev) {
  13. Log.d(TAG,"onInterceptTouchEvent()"+ " action=" + ev.getAction());
  14. return super.onInterceptTouchEvent(ev);
  15. }
  16. @Override
  17. public boolean onTouchEvent(MotionEvent event) {
  18. Log.d(TAG,"onTouchEvent()"+ " action=" + event.getAction());
  19. return super.onTouchEvent(event);
  20. }
  21. }
Activity:重写了几个方法和实现了Button和LinearLayout的onClick和onTouch()方法
  1. public class DoovActivity extends Activity implements OnClickListener,OnTouchListener{
  2. private DoovButton mButton;
  3. private DoovLinearLayout mLinearLayout;
  4. private static final String TAG = "DoovActivity";
  5. @Override
  6. protected void onCreate(Bundle savedInstanceState) {
  7. super.onCreate(savedInstanceState);
  8. setContentView(R.layout.test_doov_view);
  9. mLinearLayout = (DoovLinearLayout)this.findViewById(R.id.doov_ll);
  10. mButton = (DoovButton)this.findViewById(R.id.doov_button);
  11. mLinearLayout.setOnClickListener(this);
  12. mLinearLayout.setOnTouchListener(this);
  13. mButton.setOnClickListener(this);
  14. mButton.setOnTouchListener(this);
  15. Log.d(TAG,"onCreate");
  16. }
  17. @Override
  18. public boolean dispatchTouchEvent(MotionEvent ev) {
  19. Log.d(TAG,"dispatchTouchEvent" + " action=" + ev.getAction());
  20. return super.dispatchTouchEvent(ev);
  21. }
  22. @Override
  23. public boolean onTouchEvent(MotionEvent event) {
  24. Log.d(TAG,"onTouchEvent" + " action=" + event.getAction());
  25. return super.onTouchEvent(event);
  26. }
  27. @Override
  28. public boolean onTouch(View v, MotionEvent event) {
  29. Log.d(TAG,"onTouch" + " action=" + event.getAction() + " v=" + v);
  30. return false;
  31. }
  32. @Override
  33. public void onClick(View v) {
  34. Log.d(TAG,"onClick " +"v=" + v);
  35. }
  36. @Override
  37. public void onUserInteraction() {
  38. Log.d(TAG,"onUserInteraction");
  39. super.onUserInteraction();
  40. }
  41. }
layout布局文件
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <com.example.demo.widget.DoovLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. android:layout_width="match_parent"
  4. android:layout_height="match_parent"
  5. android:id="@+id/doov_ll"
  6. android:orientation="vertical" >
  7. <com.example.demo.widget.DoovButton
  8. android:id="@+id/doov_button"
  9. android:text="@string/str_doov_button"
  10. android:layout_width="wrap_content"
  11. android:layout_height="wrap_content" />
  12. </com.example.demo.widget.DoovLinearLayout>

1.2点击button的log

  1. 11-21 14:01:24.946: D/DoovActivity(18390): dispatchTouchEvent action=0
  2. 11-21 14:01:24.946: D/DoovActivity(18390): onUserInteraction
  3. 11-21 14:01:24.946: D/DoovLinearLayout(18390): dispatchTouchEvent() action=0
  4. 11-21 14:01:24.946: D/DoovLinearLayout(18390): onInterceptTouchEvent() action=0
  5. 11-21 14:01:24.946: D/DoovButton(18390): dispatchTouchEvent() action=0
  6. 11-21 14:01:24.946: D/DoovActivity(18390): onTouch action=0 v=com.example.demo.widget.DoovButton{157c21e5 VFED..C. ........ 0,0-264,144 #7f08000d app:id/doov_button}
  7. 11-21 14:01:24.946: D/DoovButton(18390): onTouchEvent() action=0
  8. 11-21 14:01:25.008: D/DoovActivity(18390): dispatchTouchEvent action=2
  9. 11-21 14:01:25.008: D/DoovLinearLayout(18390): dispatchTouchEvent() action=2
  10. 11-21 14:01:25.008: D/DoovLinearLayout(18390): onInterceptTouchEvent() action=2
  11. 11-21 14:01:25.008: D/DoovButton(18390): dispatchTouchEvent() action=2
  12. 11-21 14:01:25.008: D/DoovActivity(18390): onTouch action=2 v=com.example.demo.widget.DoovButton{157c21e5 VFED..C. ...P.... 0,0-264,144 #7f08000d app:id/doov_button}
  13. 11-21 14:01:25.008: D/DoovButton(18390): onTouchEvent() action=2
  14. 11-21 14:01:25.015: D/DoovActivity(18390): dispatchTouchEvent action=1
  15. 11-21 14:01:25.015: D/DoovLinearLayout(18390): dispatchTouchEvent() action=1
  16. 11-21 14:01:25.015: D/DoovLinearLayout(18390): onInterceptTouchEvent() action=1
  17. 11-21 14:01:25.015: D/DoovButton(18390): dispatchTouchEvent() action=1
  18. 11-21 14:01:25.015: D/DoovActivity(18390): onTouch action=1 v=com.example.demo.widget.DoovButton{157c21e5 VFED..C. ...P.... 0,0-264,144 #7f08000d app:id/doov_button}
  19. 11-21 14:01:25.015: D/DoovButton(18390): onTouchEvent() action=1
  20. 11-21 14:01:25.016: D/DoovActivity(18390): onClick v=com.example.demo.widget.DoovButton{157c21e5 VFED..C. ...P.... 0,0-264,144 #7f08000d app:id/doov_button}

  21. 通过以上log我们可以看出,这个和ViewGroup的流程差不多,Down事件经过Activity的dispatchTouchEvent()之后再由onUserInteraction处理,后面经过DoovLinearLayout的dispatchTouchEvent(), DoovLinearLayout的onInterceptTouchEvent没有拦截就调用了DoovButton的dispatchTouchEvent分发事件,接着调用DoovButton的onTouch方法(在Activity里面实现的),由于DoovButton的onTouch并没有消耗掉这个事件所以继续调用DoovButton的onTouchEvent().
  22. 注意: DoovButton的onTouchEvent()和onClick()消耗掉了时间之后,父控件DoovLinearLayout就不能得到时间了,也就是事件结束了.

1.2点击空白区的log

11-21 14:14:33.724: D/DoovActivity(18390): dispatchTouchEvent action=0
11-21 14:14:33.724: D/DoovActivity(18390): onUserInteraction
11-21 14:14:33.725: D/DoovLinearLayout(18390): dispatchTouchEvent() action=0
11-21 14:14:33.725: D/DoovLinearLayout(18390): onInterceptTouchEvent() action=0
11-21 14:14:33.725: D/DoovActivity(18390): onTouch action=0 v=com.example.demo.widget.DoovLinearLayout{e0b3cdc V.E...C. ........ 0,0-1080,1677 #7f08000c app:id/doov_ll}
11-21 14:14:33.725: D/DoovLinearLayout(18390): onTouchEvent() action=0
11-21 14:14:33.751: D/DoovActivity(18390): dispatchTouchEvent action=2
11-21 14:14:33.751: D/DoovLinearLayout(18390): dispatchTouchEvent() action=2
11-21 14:14:33.751: D/DoovActivity(18390): onTouch action=2 v=com.example.demo.widget.DoovLinearLayout{e0b3cdc V.E...C. ...P.... 0,0-1080,1677 #7f08000c app:id/doov_ll}
11-21 14:14:33.751: D/DoovLinearLayout(18390): onTouchEvent() action=2
11-21 14:14:33.766: D/DoovActivity(18390): dispatchTouchEvent action=2
11-21 14:14:33.766: D/DoovLinearLayout(18390): dispatchTouchEvent() action=2
11-21 14:14:33.766: D/DoovActivity(18390): onTouch action=2 v=com.example.demo.widget.DoovLinearLayout{e0b3cdc V.E...C. ...P.... 0,0-1080,1677 #7f08000c app:id/doov_ll}
11-21 14:14:33.766: D/DoovLinearLayout(18390): onTouchEvent() action=2
11-21 14:14:33.768: D/DoovActivity(18390): dispatchTouchEvent action=1
11-21 14:14:33.769: D/DoovLinearLayout(18390): dispatchTouchEvent() action=1
11-21 14:14:33.770: D/DoovActivity(18390): onTouch action=1 v=com.example.demo.widget.DoovLinearLayout{e0b3cdc V.E...C. ...P.... 0,0-1080,1677 #7f08000c app:id/doov_ll}
11-21 14:14:33.770: D/DoovLinearLayout(18390): onTouchEvent() action=1
11-21 14:14:33.770: D/DoovActivity(18390): onClick v=com.example.demo.widget.DoovLinearLayout{e0b3cdc V.E...C. ...P.... 0,0-1080,1677 #7f08000c app:id/doov_ll}

这个上面也差不多,只是我们现在点击不是Button,而且DoovLinearLayout的其他空白区别,因为点击的范围不在Button的坐标访问之内,所以没有响应button的相关的方法.

上面2种情况都显示只在ACTION_DOWN才调用了Activity的onUserInteraction(),至于为什么,后面分析源码.

2.Hierarchy图


为了更清晰的对Activity的布局层次有个了解,这里截下上面的例子的Hierarchy View

3.Activity源码分析:

肯定是从dispatchTouchEvent()开始
  1. public boolean dispatchTouchEvent(MotionEvent ev) {
  2. if (ev.getAction() == MotionEvent.ACTION_DOWN) {
  3. onUserInteraction();
  4. /// M: BMW @{
  5. MultiWindowProxy mMultiWindowProxy = MultiWindowProxy.getInstance();
  6. if (MultiWindowProxy.isFeatureSupport() && mMultiWindowProxy != null && isResumed()) {
  7. Slog.v(TAG, "[BMW] dispatchTouchEvent test into isResumed true");
  8. mMultiWindowProxy.moveActivityTaskToFront(mToken);
  9. }
  10. /// @}
  11. }
  12. if (getWindow().superDispatchTouchEvent(ev)) {//如果PhoneWindow把事件分发下去,并被消耗了,就直接返回,否则调用自己的onTouchEvent()
  13. return true;
  14. }
  15. return onTouchEvent(ev);
  16. }
代码相比ViewGroup就显得太短了,这个方法主要分2部分,前面11行主要是处理ACTION_DOWN事件的.后面12-15是处理其他事件的,当然也会处理ACTION_Down事件的.
看第3行的代码就知道为什么前面我们打log分析的时候发现只有ACTION_Down才有调用onUserInteraction(),当时这个方法是个空方法.具体注释看后面.
其中5-10行是mtk 自行添加的(那个M标识的都是mtk自己添加的),暂时也没有搞懂那个MultiWindowProxy的具体作用,暂时不分析.关键是12-15行.
12行 getWindow().superDispatchTouchEvent(ev),这个getWindow()获取的是一个实现了Window接口的对象,其实就是PhoneWindow.这个我是直接搜那个类继承了Window发现就只有一个PhoneWindow,事实也是它.
那么接着看PhoneWindow是怎么实现的superDispatchTouchEvent().
PhoneWindow.java
  1. @Override
  2. public boolean superDispatchTouchEvent(MotionEvent event) {
  3. boolean handled = mDecor.superDispatchTouchEvent(event);
  4. if (DBG_MOTION) {
  5. Log.d(TAG, "superDispatchTouchEvent = " + event + ", handled = " + handled);
  6. }
  7. return handled;
  8. }
看起开很短,这个方法的重点就在mDecor.superDispatchTouchEvent(event),而且它决定了superDispatchTouchEvent()的返回值.那mDecor是什么?mDecor就是DecorView,其实mDecor调用自己的
mDecor. superDispatchTouchEvent()也就是下面的代码,继续跟踪发现这个super.dispatchTouchEvent()其实就是调用ViewGroup的dispatchTouchEvent().
ViewGroup.java
  1. public boolean superDispatchTouchEvent(MotionEvent event) {
  2. return super.dispatchTouchEvent(event);
  3. }
再回过来看看DecorView发现是PhoneWindow的一个继承了FrameLayout的内部类.这个时候结合我们前面的那个保存的Hierarchy View截图就会发现Activity 的content 区(也就是titile下面的区域)也是一个FrameLayout.其实这个mDecor 就是在Hierarchy View里面id=content的那个FrameLayout,有疑惑的可以打开Activity的setContentView().
这里先介绍一下Activity 和mDecor怎么联系到一起.
我们在Activity里面调用setContentView,其实最终调用的PhoneWindow的setContentView.接着转调PhoneWindow 的installDecor(),接着转调generateLayout.在这个generateLayout()里面会加载一个screen_simple的layout,然后DecorView调用addView来使其变成child view.总之DecorView就是我们在Hierarchy View里面id为content的FrameLayout.
PhoneWindow :generateLayout部分代码如下:
  1. ............
  2. Trace.traceBegin(Trace.TRACE_TAG_VIEW, "DecorView-inflate");
  3. View in = mLayoutInflater.inflate(layoutResource, null);//一般情况下:layoutResource=R.layout.screen_simple
  4. Trace.traceEnd(Trace.TRACE_TAG_VIEW);
  5. decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
  6. mContentRoot = (ViewGroup) in;
  7. ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);//ID_ANDROID_CONTENT=com.android.internal.R.id.content
  8. ................
所以PhoneWindow在superDispatchTouchEvent里面调用mDecor. superDispatchTouchEvent()的时候也就是开始调用Id为content的FrameLayout的dispatchTouchEvent().到这里就开始了ViewGroup的流程.
ViewGroup的流程参考其他笔记.

总结:如果View不消耗除ACTION_Down以外的其他事件(up/move),那么这个点击事件就会消失,此时父元素的onTouchEvent不会执行,并且当前view可以继续接受后续事件,最终这些消失的点击事件会传递给activity处理.(和ViewGroup的笔记一起总结的.)

其他:
  1. /**
  2. * Called whenever a key, touch, or trackball event is dispatched to the
  3. * activity. Implement this method if you wish to know that the user has
  4. * interacted with the device in some way while your activity is running.
  5. * This callback and {@link #onUserLeaveHint} are intended to help
  6. * activities manage status bar notifications intelligently; specifically,
  7. * for helping activities determine the proper time to cancel a notfication.
  8. *
  9. * <p>All calls to your activity's {@link #onUserLeaveHint} callback will
  10. * be accompanied by calls to {@link #onUserInteraction}. This
  11. * ensures that your activity will be told of relevant user activity such
  12. * as pulling down the notification pane and touching an item there.
  13. *
  14. * <p>Note that this callback will be invoked for the touch down action
  15. * that begins a touch gesture, but may not be invoked for the touch-moved
  16. * and touch-up actions that follow.
  17. *
  18. * @see #onUserLeaveHint()
  19. */
  20. public void onUserInteraction() {
  21. }
大致的意思:Activity运行过程中,当按键、触屏或者轨迹球事件初始化时被回调。仅有初始化事件发生时,这一方法才被回调,在落键(key down)、击键(key pressed)和起键(key up)时均不会获得任何调用,而仅仅是在用户操作初始化时才获得回调。




0 0
原创粉丝点击