Android Touch事件之二:dispatchTouchEvent()和onTouchEvent()篇
2015-12-01 15:06:14
Android Touch事件第一篇:Touch事件在父ViewGroup和子View之间的传递简单分析了事件的传递流程,这次深入了解下dispatchTouchEvent()和onTouchEvent()这两个方法。
1. 上篇中提到Touch事件是由系统传入的,那么到底是怎么回事?系统是怎么传入的呢?到底是系统的那个模块具体做的?事件究竟是如何到达我们自己编写的View呢?
首先要明白一件事,我们自己的Activity,通过setContentView()设置了一个布局xml文件,一般该布局xml文件的Root View肯定是一个ViewGroup,在上一篇的Demo中,Root View就是MyLinear1,上篇中提到了Touch事件传入了MyLinear1,当时没有解决的问题是,事件是怎样传入MyLinear1的?
现在来梳理一下。其实,Touch事件从手机屏幕(硬件)-->Linux驱动层-->Android系统层-->Window(大家先这么理解即可,这其中具体细节,我也不清楚,等有时间了再研究吧),每一个Activity中都有一个Window,Window也是有布局的,我们自己写的Root View,其实并不是一个Activity的根View,那么我们来找找Activity真正的根View。
2. 完善一下上篇中的Demo,下面是MainActivity的代码,其他不变。
MainActivity.java
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
Log.e("David--MainActivity", "dispatchTouchEvent-" + MyLinear1.getS(event.getAction()));
return super.dispatchTouchEvent(event);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.e("David--MainActivity", "onTouchEvent-" + MyLinear1.getS(event.getAction()));
return super.onTouchEvent(event);
}
}
下面来看一下日志:
点击非MyLienar1的区域
12-01 17:05:30.782 E/David--MainActivity(25309): dispatchTouchEvent-Down
12-01 17:05:30.782 E/David--MainActivity(25309): onTouchEvent-Down
12-01 17:05:30.812 E/David--MainActivity(25309): dispatchTouchEvent-Move
12-01 17:05:30.812 E/David--MainActivity(25309): onTouchEvent-Move
12-01 17:05:30.862 E/David--MainActivity(25309): dispatchTouchEvent-Move
12-01 17:05:30.862 E/David--MainActivity(25309): onTouchEvent-Move
12-01 17:05:30.872 E/David--MainActivity(25309): dispatchTouchEvent-Up
12-01 17:05:30.872 E/David--MainActivity(25309): onTouchEvent-Up
可见,Touch事件最先传到Activity的,由于点击的地方,没有其他View,所以Touch事件没有继续向下传递。
点击MyLienar1
12-01 17:07:44.622 E/David--MainActivity(25309): dispatchTouchEvent-Down
12-01 17:07:44.622 E/David--MyLinear(25309): dispatchTouchEvent-Down
12-01 17:07:44.622 E/David--MyLinear(25309): onInterceptTouchEvent-Down
12-01 17:07:44.622 E/David--C---------------(25309): dispatchTouchEvent-Down
12-01 17:07:44.622 E/David--C---------------(25309): onTouchEvent-Down
12-01 17:07:44.622 E/David--C---------------(25309): dispatchTouchEvent() super.dispatchTouchEvent(event) = true
12-01 17:07:44.622 E/David--MyLinear(25309): dispatchTouchEvent() super.dispatchTouchEvent(event) = true
12-01 17:07:44.682 E/David--MainActivity(25309): dispatchTouchEvent-Move
12-01 17:07:44.682 E/David--MyLinear(25309): dispatchTouchEvent-Move
12-01 17:07:44.682 E/David--MyLinear(25309): onInterceptTouchEvent-Move
12-01 17:07:44.682 E/David--C---------------(25309): dispatchTouchEvent-Move
12-01 17:07:44.682 E/David--C---------------(25309): onTouchEvent-Move
12-01 17:07:44.682 E/David--C---------------(25309): dispatchTouchEvent() super.dispatchTouchEvent(event) = true
12-01 17:07:44.682 E/David--MyLinear(25309): dispatchTouchEvent() super.dispatchTouchEvent(event) = true
12-01 17:07:44.682 E/David--MainActivity(25309): dispatchTouchEvent-Up
12-01 17:07:44.682 E/David--MyLinear(25309): dispatchTouchEvent-Up
12-01 17:07:44.682 E/David--MyLinear(25309): onInterceptTouchEvent-Up
12-01 17:07:44.682 E/David--C---------------(25309): dispatchTouchEvent-Up
12-01 17:07:44.682 E/David--C---------------(25309): onTouchEvent-Up
12-01 17:07:44.682 E/David--C---------------(25309): dispatchTouchEvent() super.dispatchTouchEvent(event) = true
12-01 17:07:44.682 E/David--MyLinear(25309): dispatchTouchEvent() super.dispatchTouchEvent(event) = true
有两点:1、Touch事件是经由Activity传入的;2、事件被MyLinear1和它的子View获取,没有MainActivity什么事,MainActivity的onTouchEvent事件没有被调用;
如果MainActivity的dispatchTouchEvent不调用super.dispatchTouchEvent(),无论直接返回false还是true,事件不向Activity的子View传递,所以可以证明,Touch事件向下传递是在super.dispatchTouchEvent()中。我们去看看Activity.java中的dispatchTouchEvent()方法。
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction(); (1)
}
if (getWindow().superDispatchTouchEvent(ev)) { (2)
return true;
}
return onTouchEvent(ev); (3)
}
(1)这是一个空方法,有Touch操作时候,首先调用的就是这个方法。如果你想知道用户是否有和你的Activity有交互,可以在这里作文章~有兴趣的可以研究下,毕竟对于收集用户的交互行为是很有意义的;
(3)如果Activity没有View想要处理Touch事件,那就调用Activity的onTouchEvent()方法。
(2)getWindow()获取的是android.view.Window,决定一个窗口外观和行为的最顶级的抽象类,作为top-level view被添加到WindowManager。Window只有一个子类PhoneWindow,看看这名字,就知道有多霸气了。getWindow()获取到的就是PhoneWindow了,我们看看PhoneWindow的superDispatchTouchEvent(ev)方法,代码如下:
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
这里有个mDecor对象,看看这是神马鬼
private final class DecorView extends FrameLayout implements RootViewSurfaceTaker { ***
到这里终于真相大白了,Activity真正的的Root View就是DecorView,继承自FrameLayout,是一个ViewGroup哦~那么调用DecorView的dispatchTouchEvent就是在调用ViewGroup的dispatchTouchEvent了,这个圈太绕了~
梳理一下:
我们的Demo中,真正的View层次结构是DecorView-->MyLinear1-->CustomView。证据如下:

看到了吧,根View是DecorView,包含了NavigationBar、ActionBar和FrameLayout,最底下的就是我们自己的MyLinear1和CustomView了。
总结:
当用户在一个Activity上点击或者滑动时,Android系统收到Touch事件,然后分发给Activity的Root View PhoneWindow.DecorView,DecorView是我们自己的View层级的ViewGroup,剩下的就和上篇文章中分析的一样了。
3. id/content的FrameLayout
仔细看一下上图中的第二道红线,有个FrameLayout,前面分析提到过,DecorView是一个GroupView,包含了NavigationBar、ActionBar和FrameLayout,而且这个FrameLayout是“id/content”,它和我们平常使用的setContentView有什么关系呢?
Activity.java中的setContentView()方法:
/**
* Set the activity content from a layout resource. The resource will be
* inflated, adding all top-level views to the activity.
*
* @param layoutResID Resource ID to be inflated.
*
* @see #setContentView(android.view.View)
* @see #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)
*/
public void setContentView(int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
getWindow()获取的是PhoneWindow,这点前面提到过。至于initWindowDecorActionBar()可以理解为初始化ActionBar相关的东西,不在咱们的探究范围内。进入PhoneWindow的setContentView()方法。
@Override
public void setContentView(int layoutResID) {
// Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
// decor, when theme attributes and the like are crystalized. Do not check the feature
// before this happens.
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
} if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
mLayoutInflater.inflate(layoutResID, mContentParent);(1)
}
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
}
从代码中(1)可以看出,如果mContentParent存在,那么直接将我们自己的View解析到mContentParent。如果mContentParent==null,那么就installDecor,我们先看看mContentParent的定义:
// This is the top-level view of the window, containing the window decor.
private DecorView mDecor; // This is the view in which the window contents are placed. It is either
// mDecor itself, or a child of mDecor where the contents go.
private ViewGroup mContentParent; private ViewGroup mContentRoot;
mDecor:top-level View;
mContentParent:注定要被替换的ViewGroup,说白了,就是被我们自己写的布局xml文件替换掉。
mContentRoot:Activity的布局xml文件(不是我们自己继承的MainActivity)
进入installDecor()方法:
private void installDecor() {
if (mDecor == null) {
mDecor = generateDecor();
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
如果mDecor==null,初始化mDecor。如果mContentParent==null,同样做初始化的操作,Decor类后面再说,先看generateLayout()方法。
protected ViewGroup generateLayout(DecorView decor) {
// Apply data from current theme.
// 省略一些代码,主要是做theme判断的,比如Activity是否要显示成Float,是否有ActionBar什么的
// Inflate the window decor.
// 生成Window Decor
int layoutResource;
int features = getLocalFeatures();
// System.out.println("Features: 0x" + Integer.toHexString(features));
if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
layoutResource = R.layout.screen_swipe_dismiss;
} else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
if (mIsFloating) {
TypedValue res = new TypedValue();
getContext().getTheme().resolveAttribute(
R.attr.dialogTitleIconsDecorLayout, res, true);
layoutResource = res.resourceId;
} else {
layoutResource = R.layout.screen_title_icons; (1)
}
// XXX Remove this once action bar supports these features.
removeFeature(FEATURE_ACTION_BAR);
// System.out.println("Title Icons!");
}
…
mDecor.startChanging();
View in = mLayoutInflater.inflate(layoutResource, null);
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
mContentRoot = (ViewGroup) in; (2)
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
if (contentParent == null) {
throw new RuntimeException("Window couldn't find content container view");
}
// Remaining setup -- of background and title -- that only applies
// to top-level windows.
if (getContainer() == null) {
final Drawable background;
if (mBackgroundResource != 0) {
background = getContext().getDrawable(mBackgroundResource);
} else {
background = mBackgroundDrawable;
}
mDecor.setWindowBackground(background);
final Drawable frame;
if (mFrameResource != 0) {
frame = getContext().getDrawable(mFrameResource);
} else {
frame = null;
}
mDecor.setWindowFrame(frame);
mDecor.setElevation(mElevation);
mDecor.setClipToOutline(mClipToOutline);
if (mTitle != null) {
setTitle(mTitle);
}
if (mTitleColor == 0) {
mTitleColor = mTextColor;
}
setTitleColor(mTitleColor);
}
mDecor.finishChanging();
return contentParent;
}
上面的方法中只保留了关键代码
(1) 这是最常见的一种Activity布局,当然还有其他的布局xml文件,有兴趣的自己研究吧,看一下screen_title_icons.xml文件
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2006 The Android Open Source Project Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
--> <!--
This is the basic layout for a screen, with all of its features enabled.
--> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:fitsSystemWindows="true"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- Popout bar for action modes -->
<ViewStub android:id="@+id/action_mode_bar_stub"
android:inflatedId="@+id/action_mode_bar"
android:layout="@layout/action_mode_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="?attr/actionBarTheme"/>
<RelativeLayout android:id="@android:id/title_container"
style="?android:attr/windowTitleBackgroundStyle"
android:layout_width="match_parent"
android:layout_height="?android:attr/windowTitleSize">
<!-- The title background has 9px left padding. -->
<ImageView android:id="@android:id/left_icon"
android:visibility="gone"
android:layout_marginEnd="9dip"
android:layout_width="16dip"
android:layout_height="16dip"
android:scaleType="fitCenter"
android:layout_alignParentStart="true"
android:layout_centerVertical="true" />
<ProgressBar android:id="@+id/progress_circular"
style="?android:attr/progressBarStyleSmallTitle"
android:visibility="gone"
android:max="10000"
android:layout_centerVertical="true"
android:layout_alignParentEnd="true"
android:layout_marginStart="6dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<!-- There are 6dip between this and the circular progress on the right, we
also make 6dip (with the -3dip margin_left) to the icon on the left or
the screen left edge if no icon. This also places our left edge 3dip to
the left of the title text left edge. -->
<ProgressBar android:id="@+id/progress_horizontal"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="-3dip"
android:layout_toStartOf="@android:id/progress_circular"
android:layout_toEndOf="@android:id/left_icon"
android:layout_centerVertical="true"
android:visibility="gone"
android:max="10000" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:layout_toStartOf="@id/progress_circular"
android:layout_toEndOf="@android:id/left_icon"
>
<TextView android:id="@android:id/title"
style="?android:attr/windowTitleStyle"
android:layout_width="0dip"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@null"
android:fadingEdge="horizontal"
android:scrollHorizontally="true"
android:gravity="center_vertical"
android:layout_marginEnd="2dip"
/>
<!-- 2dip between the icon and the title text, if icon is present. -->
<ImageView android:id="@android:id/right_icon"
android:visibility="gone"
android:layout_width="16dip"
android:layout_height="16dip"
android:layout_weight="0"
android:layout_gravity="center_vertical"
android:scaleType="fitCenter"
/>
</LinearLayout>
</RelativeLayout>
<FrameLayout android:id="@android:id/content"
android:layout_width="match_parent"
android:layout_height="0dip"
android:layout_weight="1"
android:foregroundGravity="fill_horizontal|top"
android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>
这是一个典型的Activity的布局,绿色部分的代码,就是一个ViewGroup,是用来放置我们自己的xml布局文件的View Container,现在知道为什么我们需要setContentView(***)了吗?因为容器的id就是content~
(2)处,mContentRoot就是就是Activity的View;
(3)处,contentParent也就是mContentParent,才是真正放置我们自己View的ViewGroup。
总结:至此,关于Activity根View的问题已经解释清楚了。同时也搞明白了系统事件到底是怎么传递到MyLinear1的~
4. 回到本篇的标题,仔细研究一下View.java中的dispatchTouchEvent()和onTouchEvent()到底是怎样工作的。
Android Touch事件之二:dispatchTouchEvent()和onTouchEvent()篇的更多相关文章
- Android Touch事件传递机制 二:单纯的(伪生命周期)
转载于:http://blog.csdn.net/yuanzeyao/article/details/38025165 在前一篇文章中,我主要讲解了Android源码中的Touch事件的传递过程,现在 ...
- Android Touch事件传递机制 一: OnTouch,OnItemClick(监听器),dispatchTouchEvent(伪生命周期)
ViewGroup View Activity dispatchTouchEvent 有 有 有 onInterceptTouchEvent 有 无 无 onTouchEvent 有 有 有 例 ...
- Android Touch事件传递机制 二:单纯的(伪生命周期) 这个清楚一点
转载于:http://blog.csdn.net/yuanzeyao/article/details/38025165 在前一篇文章中,我主要讲解了Android源码中的Touch事件的传递过程,现在 ...
- Android touch 事件传递机制
前言: (1)在自定义view的时候经常会遇到事件拦截处理,比如在侧滑菜单的时候,我们希望在侧滑菜单里面有listview控件,但是我们希望既能左右滑动又能上下滑动,这个时候就需要对触摸的touch事 ...
- Android Touch事件传递机制通俗讲解
在讲正题之前我们讲一段有关任务传递的小故事,抛砖迎玉下: 话说一家软件公司,来一个任务,分派给了开发经理去完成: 开发经理拿到,看了一下,感觉好简单,于是 开发经理:分派给了开发组长 开发组长:分派给 ...
- Android Touch事件传递机制详解 下
尊重原创:http://blog.csdn.net/yuanzeyao/article/details/38025165 资源下载:http://download.csdn.net/detail/yu ...
- Android Touch事件传递机制具体解释 下
尊重原创:http://blog.csdn.net/yuanzeyao/article/details/38025165 资源下载:http://download.csdn.net/detail/yu ...
- Android touch事件的派发流程
Android TouchEvent事件传递机制 通俗易懂,能够了解Touch事件派发的基本流程. Android中的dispatchTouchEvent().onInterceptTouchEven ...
- Android Touch事件原理加实例分析
Android中有各种各样的事件,以响应用户的操作.这些事件可以分为按键事件和触屏事件.而Touch事件是触屏事件的基础事件,在进行Android开发时经常会用到,所以非常有必要深入理解它的原理机制. ...
随机推荐
- 猫眼电影爬取(二):requests+beautifulsoup,并将数据存储到mysql数据库
上一篇通过requests+正则爬取了猫眼电影榜单,这次通过requests+beautifulsoup再爬取一次(其实这个网站更适合使用beautifulsoup库爬取) 1.先分析网页源码 可以看 ...
- [Solution] The superclass “javax.servlet.http.HttpServlet” was not found on the Java Build Path
HttpServlet需要tomcat等. 右键project点开properties>project facets> 在右侧栏的Runtime tab中勾选tomcat或者新建tomca ...
- causal snps | causal variants | tensorflow | 神经网络实战 | Data Simulation
先读几篇文章: Interpretation of Association Signals and Identification of Causal Variants from Genome-wide ...
- MySQL表类型和存储引擎
一.基本介绍 从事务安全性的角度,可以把存储引擎分为两大类: 事务安全: BDB和innodb; 事务非安全性: myisam 和 memory 二.存储引擎的比较图 看你的mysql当前默认的存储引 ...
- CF-503div2-A/B/C
A. New Building for SIS time limit per test 1 second memory limit per test 256 megabytes input stand ...
- poj-1026-置换
Cipher Bob and Alice started to use a brand-new encoding scheme. Surprisingly it is not a Public Key ...
- UI基础五:简单的OP组件POPUP搜索帮助
需求:给一个配置表,需要根据配置表来弹出选择框,并将选择的数据添加到SALES ORDER的项目 BSP_WD_CMPWB 新建组件:ZHSI_JPMPG 新建视图,适用VALUE NODE 参考表Z ...
- Oracle中事务处理控制用法
oracle 事物控制包括 COMMINT ROLLBACK SAVEPOINT avepoint是事务内部允许部分rollback的标志符.因为事务中对记录做了修改,我们可以在事务中创建savepo ...
- HTML5 <li> <ol> <ul> 用法
定义和用法 <li> 标签定义列表项目. <li> 标签可用在有序列表 (<ol>) 和无序列表 (<ul>) 中. HTML 与 XHTML 之间的差 ...
- Python装饰器--decorator
装饰器 装饰器实质是一个函数,其作用就是在不改动其它函数代码的情况下,增加一些功能.如果我们需要打印函数调用前后日志,可以这么做 def log(func): print('%s is running ...