Android 触摸屏事件派发机制详解与源码分析三 (Activity 篇)
2 实例验证
2-1 代码
如下实例与前面实例相同,一个 Button 在 LinearLayout 里,只不过我们这次重写了 Activity 的一些方法而已。具体如下:
自定义的 Button 与 LinearLayout:
public class TestButton extends Button {
public TestButton(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
Log.i(null, "TestButton--dispatchTouchEvent--action="+event.getAction());
return super.dispatchTouchEvent(event);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.i(null, "TestButton--onTouchEvent--action="+event.getAction());
return super.onTouchEvent(event);
}
}
public class TestLinearLayout extends LinearLayout {
public TestLinearLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.i(null, "TestLinearLayout--onInterceptTouchEvent--action="+ev.getAction());
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
Log.i(null, "TestLinearLayout--dispatchTouchEvent--action=" + event.getAction());
return super.dispatchTouchEvent(event);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.i(null, "TestLinearLayout--onTouchEvent--action="+event.getAction());
return super.onTouchEvent(event);
}
}
整个界面的布局文件:
<com.example.yanbo.myapplication.TestLinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/layout">
<com.example.yanbo.myapplication.TestButton
android:text="click test"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/button"/>
</com.example.yanbo.myapplication.TestLinearLayout>
整个界面 Activity,重写了 Activity 的一些关于触摸派发的方法(三个):
public class MainActivity extends Activity implements View.OnClickListener, View.OnTouchListener {
private TestButton mButton;
private TestLinearLayout mLayout;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mButton = (TestButton) this.findViewById(R.id.button);
mLayout = (TestLinearLayout) this.findViewById(R.id.layout);
mButton.setOnClickListener(this);
mLayout.setOnClickListener(this);
mButton.setOnTouchListener(this);
mLayout.setOnTouchListener(this);
}
@Override
public void onClick(View v) {
Log.i(null, "onClick----v=" + v);
}
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.i(null, "onTouch--action="+event.getAction()+"--v="+v);
return false;
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.i(null, "MainActivity--dispatchTouchEvent--action=" + ev.getAction());
return super.dispatchTouchEvent(ev);
}
@Override
public void onUserInteraction() {
Log.i(null, "MainActivity--onUserInteraction");
super.onUserInteraction();
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.i(null, "MainActivity--onTouchEvent--action="+event.getAction());
return super.onTouchEvent(event);
}
}
如上就是实例测试代码,非常简单,没必要分析,直接看结果吧。
2-2 结果分析
直接点击 Button 按钮打印如下:
MainActivity--dispatchTouchEvent--action=0
MainActivity--onUserInteraction
TestLinearLayout--dispatchTouchEvent--action=0
TestLinearLayout--onInterceptTouchEvent--action=0
TestButton--dispatchTouchEvent--action=0
onTouch--action=0--v=com.example.yanbo.myapplication.TestButton
TestButton--onTouchEvent--action=0
MainActivity--dispatchTouchEvent--action=1
TestLinearLayout--dispatchTouchEvent--action=1
TestLinearLayout--onInterceptTouchEvent--action=1
TestButton--dispatchTouchEvent--action=1
onTouch--action=1--v=com.example.yanbo.myapplication.TestButton
TestButton--onTouchEvent--action=1
onClick----v=com.example.yanbo.myapplication.TestButton
分析可以发现,当点击 Button 时除过派发 Activity 的几个新方法之外其他完全符合前面两篇分析的 View 与 ViewGroup 的触摸事件派发机制。对于 Activity 来说,ACTION_DOWN 事件首先触发 dispatchTouchEvent,然后触发 onUserInteraction,再次 onTouchEvent,接着的 ACTION_UP 事件触发 dispatchTouchEvent 后触发了 onTouchEvent,也就是说 ACTION_UP 事件时不会触发 onUserInteraction(待会可查看源代码分析原因)。
直接点击 Button 以外的其他区域:
MainActivity--dispatchTouchEvent--action=0
MainActivity--onUserInteraction
TestLinearLayout--dispatchTouchEvent--action=0
TestLinearLayout--onInterceptTouchEvent--action=0
onTouch--action=0--v=com.example.yanbo.myapplication.TestLinearLayout
TestLinearLayout--onTouchEvent--action=0
MainActivity--dispatchTouchEvent--action=1
TestLinearLayout--dispatchTouchEvent--action=1
onTouch--action=1--v=com.example.yanbo.myapplication.TestLinearLayout
TestLinearLayout--onTouchEvent--action=1
onClick----v=com.example.yanbo.myapplication.TestLinearLayout
怎么样?完全符合上面点击 Button 结果分析的猜想。
那接下来还是要看看 Activity 里关于这几个方法的源码了。
3 Android 5.1.1(API 22) Activity 触摸屏事件传递源码分析
通过上面例子的打印我们可以确定分析源码的顺序,那就开始分析呗。
3-1 从 Activity 的 dispatchTouchEvent 方法说起
3-1-1 开始分析
先上源码,如下:
/**
Called to process touch screen events. You can override this to
intercept all touch screen events before they are dispatched to the
window. Be sure to call this implementation for touch screen events
that should be handled normally.
@param ev The touch screen event.
@return boolean Return true if this event was consumed.
*/
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
哎呦!这次看着代码好少的样子,不过别高兴,浓缩才是精华,这里代码虽少,涉及的问题点还是很多的,那么咱们就来一点一点分析吧。
12 到 14 行看见了吧?上面例子咱们看见只有 ACTION_DOWN 事件派发时调运了 onUserInteraction 方法,当时还在疑惑呢,这下明白了吧,不多解释,咱们直接跳进去可以看见是一个空方法,具体下面会分析。
好了,自己分析 15 到 17 行,看着简单吧,我勒个去,我怎么有点懵,这是哪的方法?咱们分析分析吧。
首先分析 Activity 的 attach 方法可以发现 getWindow()返回的就是 PhoneWindow 对象(PhoneWindow 为抽象 Window 的实现子类),那就简单了,也就相当于 PhoneWindow 类的方法,而 PhoneWindow 类实现于 Window 抽象类,所以先看下 Window 类中抽象方法的定义,如下:
/**
Used by custom windows, such as Dialog, to pass the touch screen event
further down the view hierarchy. Application developers should
not need to implement or call this.
*/
public abstract boolean superDispatchTouchEvent(MotionEvent event);
看见注释没有?用户不需要重写实现的方法,实质也不能,在 Activity 中没有提供重写的机会,因为 Window 是以组合模式与 Activity 建立关系的。好了,看完了抽象的 Window 方法,那就去 PhoneWindow 里看下 Window 抽象方法的实现吧,如下:
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
又是看着好简单的样子哦,实际又是一堆问题,继续分析。你会发现在 PhoneWindow 的 superDispatchTouchEvent 方法里又直接返回了另一个 mDecor 对象的 superDispatchTouchEvent 方法,mDecor 是啥?继续分析吧。
在 PhoneWindow 类里发现,mDecor 是 DecorView 类的实例,同时 DecorView 是 PhoneWindow 的内部类。最惊人的发现是 DecorView extends FrameLayout implements RootViewSurfaceTaker,看见没有?它是一个真正 Activity 的 root view,它继承了 FrameLayout。怎么验证他一定是 root view 呢?很简单,不知道大家是不是熟悉 Android App 开发技巧中关于 UI 布局优化使用的 SDK 工具 Hierarchy Viewer。咱们通过他来看下上面刚刚展示的那个例子的 Hierarchy Viewer 你就明白了,如下我在 Ubuntu 上截图的 Hierarchy Viewer 分析结果:
看见没有,我们上面例子中 Activity 中 setContentView 时放入的 xml layout 是一个 LinearLayout,其中包含一个 Button,上图展示了我们放置的 LinearLayout 被放置在一个 id 为 content 的 FrameLayout 的布局中,这也就是为啥 Activity 的 setContentView 方法叫 set content view 了,就是把我们的 xml 放入了这个 id 为 content 的 FrameLayout 中。
赶快回过头,你是不是发现上面 PhoneWindow 的 superDispatchTouchEvent 直接
返回了 DecorView 的 superDispatchTouchEvent,而 DecorView 又是 FrameLayout 的子类,FrameLayout 又是 ViewGroup 的子类。机智的你想到了啥木有?
没想到就继续看下 DecorView 类的 superDispatchTouchEvent 方法吧,如下:
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
这回你一定恍然大悟了吧,不然就得脑补前面两篇博客的内容了。。。
搞半天 Activity 的 dispatchTouchEvent 方法的 15 行if (getWindow().superDispatchTouchEvent(ev))
本质执行的是一个 ViewGroup 的 dispatchTouchEvent 方法(这个 ViewGroup 是 Activity 特有的 root view,也就是 id 为 content 的 FrameLayout 布局),接下来就不用多说了吧,完全是前面两篇分析的执行过程。
接下来依据派发事件返回值决定是否触发 Activity 的 onTouchEvent 方法。
评论