zoukankan      html  css  js  c++  java
  • Android事件传递机制详解及最新源码分析——Activity篇

    版权声明:本文出自汪磊的博客,转载请务必注明出处。

    在前两篇我们共同探讨了事件传递机制《View篇》《ViewGroup篇》,我们知道View触摸事件是ViewGroup传递过去的,比如一个很简单的布局最外层是LinearLayout,里面就一个Button,我们点击Button的时候触摸事件是由外层LinearLayout传递给里面Button的,但是有没有想过当前触摸事件是谁传递给外层的LinearLayout的呢?带着这个疑问我们继续来共同探讨一下。

    从Demo示例说起

    我们还是先写一个简单的demo,很简单,代码如下:自定义Button:

     1 public class MyButton extends Button {
     2 
     3     private final String TAG = "WL";
     4     
     5     public MyButton(Context context, AttributeSet attrs) {
     6         super(context, attrs);
     7     }
     8 
     9     @Override
    10     public boolean dispatchTouchEvent(MotionEvent ev) {
    11         //
    12         Log.i(TAG, "MyButton_dispatchTouchEvent_Action:"+ev.getAction());
    13         return super.dispatchTouchEvent(ev);
    14     }
    15     
    16     @Override
    17     public boolean onTouchEvent(MotionEvent event) {
    18         //
    19         Log.i(TAG, "MyButton_onTouchEvent_Action:"+event.getAction());
    20         return super.onTouchEvent(event);
    21     }
    22 }

    自定义LinearLayout:

     1 public class MyLinearLayout extends LinearLayout {
     2     
     3     private final String TAG = "WL";
     4 
     5     public MyLinearLayout(Context context, AttributeSet attrs) {
     6         super(context, attrs);
     7         //
     8     }
     9     
    10     @Override
    11     public boolean dispatchTouchEvent(MotionEvent ev) {
    12         //
    13         Log.i(TAG, "MyLinearLayout_dispatchTouchEvent_Action:"+ev.getAction());
    14         return super.dispatchTouchEvent(ev);
    15     }
    16     
    17     @Override
    18     public boolean onTouchEvent(MotionEvent event) {
    19         //
    20         Log.i(TAG, "MyLinearLayout_onTouchEvent_Action:"+event.getAction());
    21         return super.onTouchEvent(event);
    22     }
    23 }

    布局文件:

     1 <com.wl.activitydispatchtouchevent.MyLinearLayout 
     2     xmlns:android="http://schemas.android.com/apk/res/android"
     3     xmlns:tools="http://schemas.android.com/tools"
     4     android:layout_width="match_parent"
     5     android:layout_height="match_parent"
     6     android:background="#0099cc"
     7     android:id="@+id/mylinearlayout"
     8     android:gravity="center"
     9     tools:context="com.wl.activitydispatchtouchevent.MainActivity" >
    10 
    11     <com.wl.activitydispatchtouchevent.MyButton
    12         android:id="@+id/mybutton"
    13         android:layout_width="wrap_content"
    14         android:layout_height="wrap_content"
    15         android:textSize="20sp"
    16         android:text="WL_Button" />
    17 
    18 </com.wl.activitydispatchtouchevent.MyLinearLayout>

    Activity中代码:

     1 public class MainActivity extends Activity implements OnClickListener,
     2         OnTouchListener {
     3 
     4     private final String TAG = "WL";
     5 
     6     @Override
     7     protected void onCreate(Bundle savedInstanceState) {
     8         super.onCreate(savedInstanceState);
     9         setContentView(R.layout.activity_fullscreen);
    10         //
    11         findViewById(R.id.mybutton).setOnClickListener(this);
    12         findViewById(R.id.mybutton).setOnTouchListener(this);
    13         //
    14         findViewById(R.id.mylinearlayout).setOnClickListener(this);
    15         findViewById(R.id.mylinearlayout).setOnTouchListener(this);
    16     }
    17 
    18     @Override
    19     public boolean onTouch(View v, MotionEvent event) {
    20         //
    21         Log.i(TAG, "onTouch___v:" + v + "___action:" + event.getAction());
    22         return false;
    23     }
    24 
    25     @Override
    26     public void onClick(View v) {
    27         //
    28         Log.i(TAG, "onClick___v:" + v);
    29     }
    30 
    31     @Override
    32     public boolean dispatchTouchEvent(MotionEvent ev) {
    33         Log.i(TAG, "MainActivity__dispatchTouchEvent__action:" + ev.getAction());
    34         return super.dispatchTouchEvent(ev);
    35     }
    36 
    37     @Override
    38     public boolean onTouchEvent(MotionEvent event) {
    39         Log.i(TAG, "MainActivity___onTouchEvent___action=" + event.getAction());
    40         return super.onTouchEvent(event);
    41     }
    42 }

    怎么样,很简单吧。和上一篇讲解ViewGroup传递机制的Demo几乎差不多,主要差别就是在Activity中我们重写了Activity的dispatchTouchEventonTouchEvent方法。

    我们看一下运行效果,点击Button,打印信息如下:

     1 MainActivity__dispatchTouchEvent__action:0
     2 MyLinearLayout_dispatchTouchEvent_Action:0
     3 MyButton_dispatchTouchEvent_Action:0
     4 onTouch___v:com.wl.activitydispatchtouchevent.MyButton___action:0
     5 MyButton_onTouchEvent_Action:0
     6 MainActivity__dispatchTouchEvent__action:1
     7 MyLinearLayout_dispatchTouchEvent_Action:1
     8 MyButton_dispatchTouchEvent_Action:1
     9 onTouch___v:com.wl.activitydispatchtouchevent.MyButton___action:1
    10 MyButton_onTouchEvent_Action:1
    11 onClick___v:com.wl.activitydispatchtouchevent.MyButton

    除去与Activity有关的信息,其余信息打印顺序相信你应该轻松理解了。我们看到触摸事件实现传递到Activity中的,其次才传递到MyLinearLayout,最后传递给MyButton。是不是触摸事件就是Activity先获取到接下来才继续向下传递的呢?别急着下结论,我们看看Activity中dispatchTouchEvent都做了什么。

    Activity事件传递机制源码分析(源码版本为API23

    Activity中dispatchTouchEvent方法源码如下:

    1  public boolean dispatchTouchEvent(MotionEvent ev) {
    2         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
    3             onUserInteraction();
    4         }
    5         if (getWindow().superDispatchTouchEvent(ev)) {
    6             return true;
    7         }
    8         return onTouchEvent(ev);
    9     }

    是不是爽歪歪?这么短,我们看2-4行代码,首先判断如果是ACTION_DOWN事件则执行onUserInteraction()方法,对于onUserInteraction()方法这里不做具体分析,不是本篇重点。

    我们继续向下分析,5-9代码,如果if条件成立则直接返回true,不成立则dispatchTouchEvent最终返回值由onTouchEvent决定,那么if判断就是关键了。

    5行代码,getWindow()返回mWindow对象,在Activity的attach方法中进行的初始化,如下:

     1     final void attach(Context context, ActivityThread aThread,
     2             Instrumentation instr, IBinder token, int ident,
     3             Application application, Intent intent, ActivityInfo info,
     4             CharSequence title, Activity parent, String id,
     5             NonConfigurationInstances lastNonConfigurationInstances,
     6             Configuration config, String referrer, IVoiceInteractor voiceInteractor) {
     7         
     8         ...........
     9         mWindow = new PhoneWindow(this);
    10         mWindow.setCallback(this);
    11         ...........
    12 }
    mWindow其实就是PhoneWindow对象,接下来我们找到PhoneWindow类(源码目录:...sdksourcesandroid-23comandroidinternalpolicy)。
    PhoneWindow类继承自Window类,我们先看看父类中superDispatchTouchEvent是怎么处理的。
    Window类中superDispatchTouchEvent源码如下:
    1  /**
    2      * Used by custom windows, such as Dialog, to pass the touch screen event
    3      * further down the view hierarchy. Application developers should
    4      * not need to implement or call this.
    5      *
    6      */
    7 public abstract boolean superDispatchTouchEvent(MotionEvent event);
    
    

    看到了吧,很简单,父类中就是一个抽象方法, 看注释就知道此方法主要用来屏幕事件传递的,开发者不需要实现或者调用这个方法。

    接下来我们看看PhoneWindow类中的superDispatchTouchEvent方法:

    1  @Override
    2     public boolean superDispatchTouchEvent(MotionEvent event) {
    3         return mDecor.superDispatchTouchEvent(event);
    4   }

    是不是更简单?直接调用mDecor的superDispatchTouchEvent方法,mDecor是什么玩意呢?这里就直说说了,mDecor是DecorView的实例。

    DecorView类是PhoneWindow类的内部类,源码如下:

    1 private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {
    2 
    3    ..........
    4    public boolean superDispatchTouchEvent(MotionEvent event) {
    5          return super.dispatchTouchEvent(event);
    6    }
    7     
    8    ..........
    9 }

    我勒个去,搞半天DecorView 继承自FrameLayout,我们知道 FrameLayout继承自ViewGroup,最终就是调用ViewGroup中的dispatchTouchEvent方法进行事件分发。

    但是到这里我们还有一个疑问,以我们Demo为例,通过上述分析事件先传递到Activity的dispatchTouchEvent方法,然后调用DecorView 的superDispatchTouchEvent方法最终调用的ViewGroup的dispatchTouchEvent方法,但是跟我们Demo中的MyLinearLayout有什么关系呢?或者说是怎么传递到MyLinearLayout的呢?

    要解答这个疑问我们就必须熟知我们平时调用Activity中的setContentView方法设置布局的时候我们自己的布局到底是怎么挂载到Activity上的,这篇我们就不进入深入源码解析了,不是本篇重点,直说一些结论性东西。后续会单独写一篇文章专门分析setContentView究竟都做了什么。

    我们在调用setContentView设置布局的时候其实都是被放置在id为content的FrameLayout 布局中的,注意id为content的FrameLayout 布局并不是上面讲的DecorView,具体层级关系如下:

    看到了吧,id为content的FrameLayout 布局DecorView的子View布局。我们自己的布局最后总会替换掉id为content的FrameLayout

    到这里你该明白了吧,Activity将触摸事件经过层层传递给DecorView, DecorView会调用ViewGroup的dispatchTouchEvent方法将事件传递给子View。之后的逻辑就是我们上两篇所讲的内容了。

    接下来我们回看Activity中dispatchTouchEvent方法,第5行根据我们上述分析的,如果最终找到子View消耗事件则返回值为true,进而整个方法返回true。如果没有子View处理当前触摸事件则返回false,执行Activity中onTouchEvent方法。

    我们接下来分析一下Activity中onTouchEvent方法,源码如下;

     1 /**
     2      * Called when a touch screen event was not handled by any of the views
     3      * under it.  This is most useful to process touch events that happen
     4      * outside of your window bounds, where there is no view to receive it.
     5      *
     6      * @param event The touch screen event being processed.
     7      *
     8      * @return Return true if you have consumed the event, false if you haven't.
     9      * The default implementation always returns false.
    10      */
    11     public boolean onTouchEvent(MotionEvent event) {
    12         if (mWindow.shouldCloseOnTouch(this, event)) {
    13             finish();
    14             return true;
    15         }
    16 
    17         return false;
    18     }

     逻辑也不复杂,主要就是第12行代码,调用mWindowshouldCloseOnTouch方法,如果此方法返回true则整个方法返回true,反之则返回false。

    mWindow上面我们分析过就是PhoneWindow的实例,好了我们就去PhoneWindow类中找shouldCloseOnTouch方法看一下吧,然而PhoneWindow中并没有这个方法,那我们看看父类Window中有没有这个方法呢,果然这个方法在其父类中找到,源码如下:

    1 public boolean shouldCloseOnTouch(Context context, MotionEvent event) {
    2         if (mCloseOnTouchOutside && event.getAction() == MotionEvent.ACTION_DOWN
    3                 && isOutOfBounds(context, event) && peekDecorView() != null) {
    4             return true;
    5         }
    6         return false;
    7   }

    这里主要逻辑也是一个判断,判断mCloseOnTouchOutside标记以及是否为ACTION_DOWN事件,然后判断点击事件的坐标x,y有没有超出边界,最后调用peekDecorView()判断是否为空。peekDecorView()在Window类中就是一个抽象方法,具体实现在PhoneWindow类中如下:

    1     public final View peekDecorView() {
    2         return mDecor;
    3     }

    很简单,就是返回mDecor,上面我们分析过mDecor就是DecorView的实例,这里我们需要知道我们在Activity中调用setContentView的时候mDecor就会初始化,具体分析会在下一篇文章中,这里只要知道mDecor不为null就可以了。

    其余的都不难理解但是mCloseOnTouchOutside 是个什么鬼呢?我们知道Activity设置成Dialog样式的时候默认点击外部的时候是会关闭的,同样我们也可以调用setFinishOnTouchOutside(false)设置为点击外部时候Activity不关闭,mCloseOnTouchOutside 就是用来记录这个的,如果我们将Activity设置为Dialog样式mCloseOnTouchOutside 默认就被设置为true,我们知道大部分情况下Activity是不会设置为Dialog样式的,所以mCloseOnTouchOutside 默认为false。(关于mCloseOnTouchOutside其实是想从源码角度分析一下的,但是这部分内容实在和传递机制不沾边,就这部分有一个判断,所以就不仔细分析了,在下一篇分析setContentView的时候在提一下吧 )

    这里我们稍微总结一下:mCloseOnTouchOutside 默认情况下是false,如果Activity样式设置为Dialog系统默认会将mCloseOnTouchOutside 设置为true,所以Dialog样式的Activity默认情况下点击外部会关闭,如果我们调用setFinishOnTouchOutside(false)或者在styles文件中设置了 <item name="android:windowCloseOnTouchOutside">false</item> 那么最终都会将mCloseOnTouchOutside 变量置为false,点击Activity外部也就不会关闭了。

    综上分析,Window中shouldCloseOnTouch大多数情况下是返回false的,从而Activity中onTouchEvent大多说情况下也是返回false,除非我们进行了特殊设置。这也就是Activity中onTouchEvent注释是The default implementation always returns false而不是The default implementation returns false,就多了一个always。

    好了,到此为止关于安卓事件传递机制最重要的部分都已经讲解完毕,最最核心的还是要掌握View以及ViewGroup的部分,至于Activity的传递大体了解一下流程就可以了。

    下一篇我们一起探究一下Activity中setContentView方法究竟做了什么事情。反过来你能更好理解本篇中的某些点。

      


  • 相关阅读:
    form表单提交target属性使用
    window.showModalDialog
    mybaits中date类型显示时分秒(orcle数据库)
    mybatis中in查询
    偷懒的inline-block解决方法
    10. python单元测试(一)
    9. Request & 爬虫
    8. 类与对象
    7. python异常处理&异常基类学习
    6. IO及文件操作
  • 原文地址:https://www.cnblogs.com/leipDao/p/7483822.html
Copyright © 2011-2022 走看看