评论

收藏

[Android] ViewGroup事件分发源码分析

移动开发 移动开发 发布于:2022-04-13 18:43 | 阅读数:467 | 评论:0

1.AndroidStudio源码调试方式
AndroidStudio默认是支持一部分源码调试的,但是build.gradle(app) 中的sdk版本要保持一致,
最好是编译版本、运行版本以及手机的版本都保持一致,比如
android {
  compileSdkVersion 30  //1
  buildToolsVersion "30.0.0"
  defaultConfig {
    applicationId "komine.demos.app"
    minSdkVersion 26
targetSdkVersion 30 //2
versionCode 1
   versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
  }
}
设置断点的源码注意也要一致,比如设置的Android SDK 30,源码也要选择对应的版本.我的AndroidStudio版本是3.6.3
如果都一致发现断点的位置还是不正确,可以重置缓存,在 File-->Invalidate Caches 重新构建.
运行程序之后发现断点上有一个绿色的对勾,表示该断点也可以被执行,并不是100%,所以关键代码调试,最好步进调试.
如果断点成功执行了,如下图
DSC0000.png

2.ViewGroup事件分发源码分析
当我们在屏幕触摸的时候,不管你触摸到那里,是View上面,还是一个View的没有的Activity上,都会调用Activity的dispatchTouchEvent,如果要观察touchEvent事件分发过程,
可以自己定义一个ViewGroup,和View,在其中打印日志
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
   android:orientation="vertical"
   android:layout_width="match_parent"
   android:layout_height="match_parent">
  <komine.demos.app.TouchLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <komine.demos.app.TouchView
      android:layout_width="200dp"
      android:layout_height="200dp"
      android:background="#39c5bb"/>
    <komine.demos.app.TouchView
      android:layout_width="200dp"
      android:layout_height="200dp"
      android:background="#39c5bb"/>
  </komine.demos.app.TouchLayout>
</LinearLayout>
所有Touch事件都会进到Activity.dispatchTouchEvent方法
public boolean dispatchTouchEvent(MotionEvent ev) {
  if (ev.getAction() == MotionEvent.ACTION_DOWN) {
    //如果是down事件,则会回调Activity的onUserInteraction()方法
    //我们可以在该方法中做一些其他事情
    onUserInteraction();
  }
  //getWindow()是mWindow,在Activity的attach方法中赋值,是一个PhoneWindow对象,是直接new PhoneWindow
  if (getWindow().superDispatchTouchEvent(ev)) {
    return true;
  }
  //如果事件没有被消费,则会回调Activity的onTouchEvent()事件
  return onTouchEvent(ev);
}
PhoneWindow.superDispatchTouchEvent
如果你想知道mDecor是干什么的或者怎么被创建的,可以看我的另一篇博客
setContentView源码分析
我们直接把它当作ViewGroup看待就行了
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
  //superDispatchTouchEvent() 其实是调用了ViewGroup的dispatchTouchEvent,
  //因为mDecor是继承自ViewGroup
  return mDecor.superDispatchTouchEvent(event);
}
重点来了,ViewGroup.dispatchTouchEvent
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
  ...
  ...
  //函数的返回值,表示当前事件是否有View消费,true表示有View消费该事件
  //则其他View就不能再处理该事件了
  boolean handled = false;
  //View的方法,默认返回true
  if (onFilterTouchEventForSecurity(ev)) {
  ...
 ...
    //判断当前ViewGroup是否拦截该事件,
final boolean intercepted;
    //down事件才判断是否拦截,所以ViewGroup要拦截事件,一定要在
    //down事件的时候拦截,其他事件会被忽略
if (actionMasked == MotionEvent.ACTION_DOWN
          || mFirstTouchTarget != null) {
    //FLAG_DISALLOW_INTERCEPT 是在requestDisallowInterceptTouchEvent()中赋值的
       //表示子View是否请求了父ViewGroup不拦截本次事件,非ViewGroup子View可以调用getParent().requestDisallowInterceptTouchEvent()
       final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
   //如果子View没有请求父ViewGroup不拦截自己
       if (!disallowIntercept) {
  //调用当前ViewGroup的onInterceptTouchEvent()看看自己是否拦截
          intercepted = onInterceptTouchEvent(ev);
          ev.setAction(action);
       } else {
          intercepted = false;
       }
     } else {
        intercepted = true;
     }
...
...
//caceled默认返回false, intercepted表示当前ViewGroup是否要拦截事件
if (!canceled && !intercepted){
  ...
  ...
  //这是一个倒序循环,最后的那个view会先进行事件分发
  for (int i = childrenCount - 1; i >= 0; i--) {
...
...
//见下方,dispatchTransformedTouchEvent()对子view进行事件分发
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
  //如果有子View返回true,中断事件分发,break完成子View的事件分发
}
  }
  
}
  }
  //mFirstTouchTarget == null 说明没有子View消费当前事件,则会调用
  //父view的dispatchTouchEvent(),完成当前ViewGroup的事件分发
  //意思就是,我已经分完了,我的子View没人要消费事件,你看你要不要,把事件分发交给
  //父类去处理,然后父类又会重复上述步骤,进行事件分发
  if (mFirstTouchTarget == null) {
      handled = dispatchTransformedTouchEvent(ev, canceled, null,
            TouchTarget.ALL_POINTER_IDS);
  } else {
...
handled = true;
  }
  //所有ViewGroup分发完成之后的最终结果
  return handled;
}
dispatchTransformedTouchEvent
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,View child, int desiredPointerIdBits) {
  final boolean handled;
  final int oldAction = event.getAction();
  ...
  ...
  //判断是否是同一根手指
  if (newPointerIdBits == oldPointerIdBits) {
if (child == null || child.hasIdentityMatrix()) {
       if (child == null) {
 //如果子View为空,则返回父类的dispatchTouchEvent()结果
         handled = super.dispatchTouchEvent(event);
       } else {
 ...
     //调用子View的dispatchTouchEvent(),子View的dispatchTouchEvent()又会调用onTouchEvent(),
 //如果View设置了TouchListener则会优先以TouchListener结果返回,然后才会以onTouchEvent()的结果
 //作为返回值,这边不贴代码了,可以自己去看看
         handled = child.dispatchTouchEvent(event);
 ...
       }
       return handled;
    }
  }
}
Touch事件分发流程:
DSC0001.png



   
   
   
                        

关注下面的标签,发现更多相似文章