最近在学习《Android开发艺术探索》,很多东西感觉很有趣,但是前面的跨进程开发因为之前的项目一直没有接触过,所以一直没有敲代码,最近今天看到了事件分发机制以及后面的滑动冲突,觉得这个自己可以写一点代码来测试了,结果一测试就出大问题了,后来仔细看了看内容,发现还是当时自己没有理解透彻。所以打算写一篇博客让自己以自己的角度来理解书中的内容。OK,首先说一下本文的思路:
一、事件分发机制的大致了解;
二、滑动冲突的三种情况(与原文不同);
首先,事件分发机制这里只需要了解一个大概,然后在后面的案例中再来结合源码思考,所以这里面只介绍滑动事件的概述和三个核心方法以及Android事件分发的核心过程。首先来看看什么是一次滑动事件序列?
同一个滑动事件序列是指手指从接触屏幕的那一刻开始,到手指离开屏幕的那一刻结束,在这个过程中产生的一系列事件,这个事件序列以down事件开始,中间含有数量不定的move事件,最终以up事件结束。这个概念在后文有用到,我个人认为还是相当重要的。
接下来我们看看核心方法,如下:
@Override
public boolean dispatchTouchEvent(MotionEvent ev)
{
return super.dispatchTouchEvent(ev);
}
上面这个方法是Viewgroup的事件分发核心方法,所有传递过来的滑动事件都会经过它来处理,如果这里返回false代表还需要Viewgroup的上一级View来处理该事件,否则则是由Viewgroup内部消化掉此次滑动事件。
@Override
public boolean onInterceptTouchEvent(MotionEvent ev)
{
return super.onInterceptTouchEvent(ev);
}
上面这个方法是Viewgroup的拦截方法,返回true代表拦截该次滑动事件序列,默认返回false,代表默认是不拦截的,需要分发给相应的子View来处理。
@Override
public boolean onTouchEvent(MotionEvent event)
{
return super.onTouchEvent(event);
}
最后这个方法大家平时用得最多,主要是处理滑动事件。这里需要注意返回值:返回false代表该View搞不定,需要上一层View来处理;返回true代表给上级反馈这件事我搞定了,不用麻烦你来动手了。
理解了这个方法以后,我们接下来看看事件分发的核心过程,伪代码如下:
public boolean dispatchTouchEvent(MotionEvent ev)
{
boolean consume = false;
if(onInterceptTouchEvent(ev))//是否拦截
{
consume = onTouchEvent(ev);//拦截以后,自己处理滑动事件,是否处理完毕
}
else
{
consume = child.dispatchTouchEvent(ev);//分发给子View处理的结果,是否需要上级View处理?
}
return consume;
}
}
OK,事件分发的内容大致就这样,接着我们尝试自己写一些View来模拟滑动事件冲突,冲突场景如图:
我放了一个Viewgroup作为parentView,然后在parentView里面放了一个Viewgroup作为ChildView,最后在ChildView里面放置了一个TextView。代码比较简单,我们看看parentView里面实现分发事件的过程以及ChildView事件分发的过程,以验证我们上面的代码。
此为冲突一 ——空白VIewgroup:
parentView:
private int mLastX;
private int mLastY;
private static final String TAG = "Tag";
private String tag = "ParentView onInterceptTouchEvent";
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
boolean result = false;
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.i("Tag",tag+"this is MotionEvent.ACTION_DOWN");
result = false;
break;
case MotionEvent.ACTION_MOVE:
Log.i("Tag",tag+"this is MotionEvent.ACTION_MOVE");
int deltaX = (int) (ev.getX() - mLastX);
int deltaY = (int) (ev.getY() - mLastY);
if (Math.abs(deltaX) > Math.abs(deltaY)) {
result = true;
} else {
result = false;
}
break;
case MotionEvent.ACTION_UP:
Log.i("Tag",tag+"this is MotionEvent.ACTION_UP");
result = false;
break;
}
mLastX = (int) ev.getX();
mLastY = (int) ev.getY();
Log.e(TAG, tag+" result: "+result);
return result;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.i("Tag"," ParentView is TouchEventing! ");
return super.onTouchEvent(event);
}
ChildView:
private static final String TAG = "Tag";
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.i("Tag","ChildView is TouchEventing");
switch (event.getAction())
{
case MotionEvent.ACTION_DOWN:
Log.i(TAG,"this is MotionEvent.ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.i("Tag","this is MotionEvent.ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.i("Tag","this is MotionEvent.ACTION_UP");
break;
}
return false;
}
上面这种方法在ParentView里面判断是否拦截叫做外部拦截方法,当然也有在ChildView里面判断是否让ParentView拦截的方法,这一种方法叫做内部拦截。因为第一种方法简单方便,所以一般是使用第一种方法——外部拦截。
布局代码:
"1.0" encoding="utf-8"?>
"http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.example.administrator.testtouchevent.ParentView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/parent"
android:background="#CC9988">
<com.example.administrator.testtouchevent.ChildView
android:layout_width="400dp"
android:layout_height="400dp"
android:id="@+id/child"
android:layout_alignParentRight="true"
android:background="#8899CC">
"@+id/tvTest"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello Android!"/>
com.example.administrator.testtouchevent.ChildView>
com.example.administrator.testtouchevent.ParentView>
接着我们按照箭头方向滑动看看它是怎么来处理这些事件的呢?
垂直方向滑动
具体日志信息:
Tag: ParentView onInterceptTouchEventthis is MotionEvent.ACTION_DOWN
Tag: ParentView onInterceptTouchEvent result: false
Tag: ChildView is TouchEventing
Tag: this is MotionEvent.ACTION_DOWN
Tag: ParentView is TouchEventing!
在这里貌似有问题了,ParentView没有拦截事件,返回的是false,但是ChildView也只响应了DOWN事件,然后该事件序列的MOVE事件和UP事件并没有交由ChildView了,且ParentView也只响应了一次事件,不用说也只能是DOWN事件了!那么,这是为什么呢?
我们看一下这几个问题:
1、ChildView的onTouchEvent(MotionEvent event)为什么也只接受到DOWN事件?
2、ParentView的onTouchEvent(MotionEvent event)为什么只接受到DOWN事件?
3、ParentView的onInterceptTouchEvent(MotionEvent ev)为什么只响应了事件序列中的第一次?不上每一次滑动事件未被拦截的时候都会运行该方法吗?
答:
1、因为一次滑动事件序列正常只能由一个View来处理,即执行onTouchEvent方法。但是此处的ChildView能力不行,处理完之后没有搞定,返回值为false,意味着还需要上一级的ParentView来接着处理,所以下面源码中的mFirstTouchTarget还是没有被赋值,可以理解为,相对于ChildView来说,Viewgroup自己消耗掉了此处的滑动事件。
2、这里是因为默认的返回值super.onTouchEvent(event)为false,这里可以重写这个方法并查看日志:
@Override
public boolean onTouchEvent(MotionEvent event) {
boolean result = super.onTouchEvent(event);
Log.i("Tag"," ParentView is TouchEventing! result: "+result);
return result;
}
01-01 17:08:01.470 10556-10556/com.example.administrator.testtouchevent I/Tag: ParentView is TouchEventing! result: false
所以这个问题与上面的问题是同样一回事。这里可能大家要问,如果Viewgroup的onTouchEvent(MotionEvent event)要是一直任性的返回false(不要问他实际意义,它就是任性!),那么滑动事件到底有没有谁来处理呢?这个肯定是有负责处理的。滑动事件的传递顺序是这样的,Activity——>Window——>DecorView——>子View……
这个DecorView就是我们每一个Activity在setContentView( int layoutResID)的Viewgroup。
3、要解答这个疑问则需要查看源码了:
// Check for interception.
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted = true;
}
上面是Viewgroup的dispatchTouchEvent方法部分源码,通过这个方法我们可以看到在分发事件的时候会先判断是否满足条件,不满足条件直接在当前的Viewgroup给处理了,这意味系统会拦截当前事件,不会分发给子View了。而mFirstTouchTarget值就是指派处理滑动事件的View,这里为null的话,则代表没有子View处理滑动事件,由Viewgroup自己处理的.
由于一个滑动事件序列正常情况下只能由一个View来处理事件,所以此处直接给拦截了MOVE等事件,因为当ChildView在空白处滑动的时候mFirstTouchTarget没有被赋值,所以MOVE事件的时候ChildView的dispatchTouchEvent(MotionEvent ev)就拦截了,但是ChildView在处理滑动事件的时候onTouchEvent方法返回了false,导致ParentView的mFirstTouchTarget等于null,所以ParentView的onInterceptTouchEvent(MotionEvent ev)只响应了事件序列中的第一次,即MotionEvent.ACTION_DOWN事件。
这个有点绕,大家可以看看源码,其实很简单的!
冲突二——外部拦截:
这里我们需要查看TextView,之前因为TextView太小了,无法测试上面写好的外部拦截,所以这里我们把TextView放到最大,测试一下外部拦截正确性!
修改布局:
"@+id/tvTest"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:text="Hello Android!"/>
这里还是按照上面的两种手势来滑动ChildView,分别得到两份日志,如下:
垂直滑动日志:
I/Tag: ParentView onInterceptTouchEventthis is MotionEvent.ACTION_DOWN
E/Tag: ParentView onInterceptTouchEvent result: false
I/Tag: ParentView onInterceptTouchEventthis is MotionEvent.ACTION_MOVE
E/Tag: ParentView onInterceptTouchEvent result: false
I/Tag: ParentView onInterceptTouchEventthis is MotionEvent.ACTION_UP
E/Tag: ParentView onInterceptTouchEvent result: false
这里完全是按照我们的意愿来执行的,即每次垂直滑动的事件都让ChildView来处理了。由于此处的滑动事件是由TextView在处理,所以这里没有输出日志。从这里我们也可以推断出TextView的onTouchEvent(MotionEvent event)默认返回值为true,因为我们的ChildView onTouchEvent(MotionEvent event)并没有收到滑动事件。OK,我们接下来看看水平滑动的时候是谁在处理呢?
此处重写ParentView的onTouchEvent(MotionEvent event),代码如下:
@Override
public boolean onTouchEvent(MotionEvent event)
{
Log.i("Tag"," ParentView is TouchEventing! MotionEvent: "+event.getAction());
return true;
}
日志信息如下:
I/Tag: ParentView onInterceptTouchEventthis is MotionEvent.ACTION_DOWN
E/Tag: ParentView onInterceptTouchEvent result: false
I/Tag: ParentView onInterceptTouchEventthis is MotionEvent.ACTION_MOVE
E/Tag: ParentView onInterceptTouchEvent result: true
I/Tag: ParentView is TouchEventing! MotionEvent: 2
I/Tag: ParentView is TouchEventing! MotionEvent: 2
I/Tag: ParentView is TouchEventing! MotionEvent: 2
I/Tag: ParentView is TouchEventing! MotionEvent: 2
I/Tag: ParentView is TouchEventing! MotionEvent: 2
I/Tag: ParentView is TouchEventing! MotionEvent: 2
I/Tag: ParentView is TouchEventing! MotionEvent: 1
这里我们在事件分发的onInterceptTouchEvent(MotionEvent ev)对ParentView需要响应的事件做了判断,如果这里需要ParentView处理的话,就拦截下来,否则就分发下去。此处DOWN事件没有拦截,所以ParentView并没有响应到,而MOVE事件(int值为2)以及UP事件(int值为1)都响应到了。但是这里还是有一个bug,就是一次只能解决一次,即如果水平滑动没有结束的时候转为垂直滑动,由于一个滑动事件序列正常情况下是由一个View来处理,所以这里只能一次处理一个事件,即水平或者垂直。为了解决这个问题,我们可以重写ParentView的onTouchEvent(MotionEvent event)方法,代码如下:
@Override
public boolean onTouchEvent(MotionEvent event)
{
tag = "ParentView onTouchEvent";
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.i("Tag",tag+"this is MotionEvent.ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.i("Tag",tag+"this is MotionEvent.ACTION_MOVE");
int deltaX = (int) (event.getX() - mTouchLastX);
int deltaY = (int) (event.getY() - mTouchLastY);
if (Math.abs(deltaX) < Math.abs(deltaY)) {
event.setAction(MotionEvent.ACTION_DOWN);
dispatchTouchEvent(event);
}
break;
case MotionEvent.ACTION_UP:
Log.i("Tag",tag+"this is MotionEvent.ACTION_UP");
break;
}
mTouchLastX = (int) event.getX();
mTouchLastY = (int) event.getY();
return true;
}
大致思路就是在onTouchEvent方法判断是否需要子View处理,是的话将该MOVE事件设置为DOWN事件,并重新交由dispatchTouchEvent方法来分发,否则就消费掉当次滑动事件即可。
三、内部拦截:
上面说的外部拦截是在Viewgroup里面来处理是否拦截的逻辑,而内部拦截则是在Viewgroup的子View来处理这个逻辑。这里为了让日志更清晰,我们将TextView隐藏掉,让ChildView里面的onTouchEvent方法来滑动事件,这样层次清楚一些。我们修改代码:
布局:
"1.0" encoding="utf-8"?>
"http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.example.administrator.testtouchevent.ParentView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/parent"
android:background="#CC9988">
<com.example.administrator.testtouchevent.ChildView
android:layout_width="400dp"
android:layout_height="400dp"
android:id="@+id/child"
android:layout_alignParentRight="true"
android:background="#8899CC">
com.example.administrator.testtouchevent.ChildView>
com.example.administrator.testtouchevent.ParentView>
ParentView修改代码:
...
public boolean isExternalIntercept = true;
@Override
public boolean onInterceptTouchEvent(MotionEvent ev)
{
if(isExternalIntercept)
{
tag = "ParentView onInterceptTouchEvent";
boolean result = false;
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.i("Tag", tag + "this is MotionEvent.ACTION_DOWN");
result = false;
break;
case MotionEvent.ACTION_MOVE:
Log.i("Tag", tag + "this is MotionEvent.ACTION_MOVE");
int deltaX = (int) (ev.getX() - mLastX);
int deltaY = (int) (ev.getY() - mLastY);
if (Math.abs(deltaX) > Math.abs(deltaY)) {
result = true;
} else {
result = false;
}
break;
case MotionEvent.ACTION_UP:
Log.i("Tag", tag + "this is MotionEvent.ACTION_UP");
result = false;
break;
}
mLastX = (int) ev.getX();
mLastY = (int) ev.getY();
Log.e(TAG, tag + " result: " + result);
return result;
}
else
{
if(ev.getAction()==MotionEvent.ACTION_DOWN)
{
return false;
}
return true;
}
}
...
上面主要是在onInterceptTouchEvent方法里根据是否进行外部拦截的Boolean值进行不同的判断;
下面是ChildView修改的代码:
...
public boolean isInternalInterception = false;
public ParentView mParentView = null;
// 分别记录上次滑动的坐标
private int mLastX = 0;
private int mLastY = 0;
//切记一定要对该方法进行初始化
public void setParentView(ParentView parentView)
{
this.mParentView = parentView;
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev)
{
if(isInternalInterception)
{
int x = (int) ev.getX();
int y = (int) ev.getY();
switch (ev.getAction())
{
case MotionEvent.ACTION_DOWN:
mParentView.requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_MOVE:
if(Math.abs(x-mLastX)>Math.abs(y-mLastY))
{
mParentView.requestDisallowInterceptTouchEvent(false);
}
break;
case MotionEvent.ACTION_UP:
break;
}
mLastX = x;
mLastY = y;
}
return super.dispatchTouchEvent(ev);
}
内部拦截的重点就在ChildView里面,首先我们提供一个方法对变量mParentView进行实例化,这个方法一定记得调用,我自己在测试的时候忘了实例化,然后demo没有报空指针异常,然后出现的异常让我一度抓瞎!
接着在dispatchTouchEvent方法里面判断是不是内部拦截,是的话就需要对相关的滑动事件进行处理,比如这一句:
case MotionEvent.ACTION_MOVE:
if(Math.abs(x-mLastX)>Math.abs(y-mLastY))
{
mParentView.requestDisallowInterceptTouchEvent(false);
}
break;
这里需要再看一看Viewgroup的dispatchTouchEvent方法部分源码,然后继续说明:
// Check for interception.
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted = true;
}
当滑动事件为DOWN的时候,进入dispatchTouchEvent方法,然后ChildView被赋值为mFirstTouchTarget,接着当滑动事件为MOVE事件的时候,因为mFirstTouchTarget不为null,所以继续进入ParentView的dispatchTouchEvent方法。这里,由于我们在DOWN事件的时候调用了ParentView的requestDisallowInterceptTouchEvent方法,我们先看这一段源码再回头研究上面的代码。
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
// We're already in this state, assume our ancestors are too
return;
}
if (disallowIntercept) {
mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
} else {
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
// Pass it up to our parent
if (mParent != null) {
mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
}
}
因为文中涉及到位或、位与、取反等位运算,比较复杂,大家只要了解到一个关键的点就好,即当requestDisallowInterceptTouchEvent方法设为true的话,mGroupFlags |= FLAG_DISALLOW_INTERCEPT计算结果等于mGroupFlags=FLAG_DISALLOW_INTERCEPT=0x80000,换句话说就是mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0,否则mGroupFlags=FLAG_DISALLOW_INTERCEPT=0,换个表达方式则是mGroupFlags & FLAG_DISALLOW_INTERCEPT)== 0。
大家先理一下,我喝口水接着理!!
所以接着上面的dispatchTouchEvent源码思考,当DOWN事件时,mFirstTouchTarget不为null,且mGroupFlags=FLAG_DISALLOW_INTERCEPT=0x80000,所以方法内的intercepted等于false,相当于说onInterceptTouchEvent返回false,再看看源码:
// Check for interception.
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted = true;
}
以上则是整个内部拦截的原理和代码,说了这么多,我们还是来看看源码,看看我说的这些个效果是不是这么回事?即水平滑动由ParentView处理,垂直滑动由ChildView处理!
最后再次强调不要忘了对ChildView里面的mParentView进行实例化,代码如下:
package com.example.administrator.testtouchevent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private ParentView ViewParent;
private ChildView ViewChild;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
ViewParent = (ParentView) findViewById(R.id.parent);
ViewChild = (ChildView) findViewById(R.id.child);
setInternalInterception(false);
// TextView tvView = (TextView) findViewById(R.id.tvTest);
// tvView.setOnClickListener( this);
}
@Override
public void onClick(View v) {
// Toast.makeText(this,"tvView is Click",Toast.LENGTH_SHORT).show();
}
private void setInternalInterception(boolean isOutOrIn)
{
if(isOutOrIn)
{
ViewParent.isExternalIntercept = isOutOrIn;
ViewChild.isInternalInterception = !isOutOrIn;
}
else
{
ViewParent.isExternalIntercept = !isOutOrIn;
ViewChild.isInternalInterception = isOutOrIn;
//重要的事情说三遍:实例化、实例化、实例化
ViewChild.setParentView(ViewParent);
}
}
}
垂直滑动日志如下:
01-01 16:29:28.130 25359-25359/com.example.administrator.testtouchevent I/Tag: ParentView onInterceptTouchEventthis is MotionEvent.ACTION_DOWN
01-01 16:29:28.130 25359-25359/com.example.administrator.testtouchevent E/Tag: ParentView onInterceptTouchEvent result: false
01-01 16:29:28.130 25359-25359/com.example.administrator.testtouchevent I/Tag: ChildView is TouchEventing this is MotionEvent.ACTION_DOWN
01-01 16:29:28.510 25359-25359/com.example.administrator.testtouchevent I/Tag: ParentView onInterceptTouchEventthis is MotionEvent.ACTION_MOVE
01-01 16:29:28.510 25359-25359/com.example.administrator.testtouchevent E/Tag: ParentView onInterceptTouchEvent result: false
01-01 16:29:28.510 25359-25359/com.example.administrator.testtouchevent I/Tag: ChildView is TouchEventing this is MotionEvent.ACTION_MOVE
01-01 16:29:28.510 25359-25359/com.example.administrator.testtouchevent I/Tag: ParentView onInterceptTouchEventthis is MotionEvent.ACTION_UP
01-01 16:29:28.510 25359-25359/com.example.administrator.testtouchevent E/Tag: ParentView onInterceptTouchEvent result: false
01-01 16:29:28.510 25359-25359/com.example.administrator.testtouchevent I/Tag: ChildView is TouchEventing this is MotionEvent.ACTION_UP
01-01 16:37:09.990 25359-25359/com.example.administrator.testtouchevent I/Tag: ParentView onInterceptTouchEventthis is MotionEvent.ACTION_DOWN
01-01 16:37:09.990 25359-25359/com.example.administrator.testtouchevent E/Tag: ParentView onInterceptTouchEvent result: false
01-01 16:37:09.990 25359-25359/com.example.administrator.testtouchevent I/Tag: ChildView is TouchEventing this is MotionEvent.ACTION_DOWN
01-01 16:37:10.870 25359-25359/com.example.administrator.testtouchevent I/Tag: ParentView onInterceptTouchEventthis is MotionEvent.ACTION_MOVE
01-01 16:37:10.870 25359-25359/com.example.administrator.testtouchevent E/Tag: ParentView onInterceptTouchEvent result: true
01-01 16:37:12.560 25359-25359/com.example.administrator.testtouchevent I/Tag: ParentView onTouchEventthis is MotionEvent.ACTION_MOVE
01-01 16:37:13.120 25359-25359/com.example.administrator.testtouchevent I/Tag: ParentView onTouchEventthis is MotionEvent.ACTION_MOVE
01-01 16:37:14.060 25359-25359/com.example.administrator.testtouchevent I/Tag: ParentView onTouchEventthis is MotionEvent.ACTION_MOVE
01-01 16:37:14.060 25359-25359/com.example.administrator.testtouchevent I/Tag: ParentView onTouchEventthis is MotionEvent.ACTION_UP
OK,到这里本篇文章就结束了!
欢迎大家指正讨论,谢谢!
源码点击下载