Android事件散发onInterceptTouchEvent与onTouchEvent

Android事件分发onInterceptTouchEvent与onTouchEvent
        最近在做一个项目,需要定制一个View,类似于Launcher中水平滚动效果。当初仿照Workspace类来实现。然而在针对一个子View响应Scroll事件时,这个子View死活都不响应触摸事件。分析了一天,最后给这个子View的属性中添加android:clickable="true", 问题就被搞定了。
       Android平台事件分发的两个函数onInterceptTouchEvent与onTouchEvent,还是比较复杂的。关键点还是在于其返回值。现将网上的一些帖子借鉴,使用一个简单的demo,进行一些分析。
onInterceptTouchEvent()是ViewGroup的一个方法,目的是在系统向该ViewGroup及其各个childView触发onTouchEvent()之前对相关事件进行一次拦截.
1.down事件首先会传递到onInterceptTouchEvent()方法。
2.如果该ViewGroup的onInterceptTouchEvent()在接收到down事件处理完成之后return false,那么后续的move,up等事件将继续会先传递给该ViewGroup,之后才和down事件一样传递给最终的目标view的onTouchEvent()处理。
3.如果该ViewGroup的onInterceptTouchEvent()在接收到down事件处理完成之后return true,那么后续的move, up等事件将不再传递给onInterceptTouchEvent(),而是和down事件一样传递给该ViewGroup的onTouchEvent()处理。并且,childView将接收不到任何事件。
4.如果最终需要处理事件的view的onTouchEvent()返回了false,表示事件没有被消耗,那么该事件将被传递至其上一层次的view的onTouchEvent()处理。
5.如果最终需要处理事件的view 的onTouchEvent()返回了true,那么后续事件将可以继续传递给该view的onTouchEvent()处理。
    通过一个简单的demo,对上述几条信息进行逐一讲解。三个类自定义类分别继承于LinearLayout,ALayout BLayout CLayout(仅仅贴出ALayout类的代码,其他类的代码类似)
public class ALayout extends LinearLayout{
public ALayout(Context context) {
super(context);
// TODO Auto-generated constructor stub
}
public ALayout(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
}

public ALayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// TODO Auto-generated constructor stub
}

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
// TODO Auto-generated method stub
final int action = ev.getAction();
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
Log.e("ALayout","onInterceptTouchEvent ACTION_DOWN called,return = " + super.onInterceptTouchEvent(ev));
break;
case MotionEvent.ACTION_MOVE:
break;
case MotionEvent.ACTION_UP:
break;
}
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
// TODO Auto-generated method stub
final int action = event.getAction();
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
Log.e("ALayout","onTouchEvent ACTION_DOWN called,return = " + super.onTouchEvent(event));
break;
case MotionEvent.ACTION_MOVE:
break;
case MotionEvent.ACTION_UP:
break;
}
return super.onTouchEvent(event);
}
}

布局文件main.xml如下:
<RelativeLayout xmlns:android="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.touchtest.ALayout
        android:id="@+id/view_a"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:background="#ffff0000"
        android:gravity="center" >
        <com.example.touchtest.BLayout
            android:id="@+id/view_b"
            android:layout_width="250dip"
            android:layout_height="250dip"
            android:background="#ff00ff00"
            android:gravity="center" >
            <com.example.touchtest.CLayout
                android:id="@+id/view_c"
                android:layout_width="150dip"
                android:layout_height="150dip"
                android:background="#ff0000ff"
                android:gravity="center" >
            </com.example.touchtest.CLayout>
        </com.example.touchtest.BLayout>
    </com.example.touchtest.ALayout>
</RelativeLayout>
程序运行后效果如下所示:
Android事件散发onInterceptTouchEvent与onTouchEvent

    

应用运行效果如左图所示。红色背景为ALayout,绿色为BLayout,蓝色为CLayout.点击蓝色(CLayout)区域时,查看log如下:

E/ALayout(10407): onInterceptTouchEvent ACTION_DOWN called,return = false

E/BLayout(10407): onInterceptTouchEvent ACTION_DOWN called,return = false

E/CLayout(10407): onInterceptTouchEvent ACTION_DOWN called,return = false

E/CLayout(10407): onTouchEvent ACTION_DOWN called,return = false

E/BLayout(10407): onTouchEvent ACTION_DOWN called,return = false

E/ALayout(10407): onTouchEvent ACTION_DOWN called,return = false

通过log得知,ACTION_DOWN事件最开始是由onInterceptTouchEvent函数进行处理。之后传递给onTouchEvent函数。这两个函数默认的返回值为false。由于A,B和C三个View均没有消耗掉这个ACTION_DOWN事件,则这个事件也就不了了之了。
现在修改一点内容,将BLayout的属性中添加一条android:clickable="true",即布局文件为:
<RelativeLayout xmlns:android="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.touchtest.ALayout
        android:id="@+id/view_a"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:background="#ffff0000"
        android:gravity="center" >
        <com.example.touchtest.BLayout
            android:id="@+id/view_b"
            android:layout_width="250dip"
            android:layout_height="250dip"
            android:clickable="true"
            android:background="#ff00ff00"
            android:gravity="center" >
            <com.example.touchtest.CLayout
                android:id="@+id/view_c"
                android:layout_width="150dip"
                android:layout_height="150dip"
                android:background="#ff0000ff"
                android:gravity="center" >
            </com.example.touchtest.CLayout>
        </com.example.touchtest.BLayout>
    </com.example.touchtest.ALayout>
</RelativeLayout>
运行后,点击蓝色区域,显示的log信息为:
E/ALayout(10764): onInterceptTouchEvent ACTION_DOWN called,return = false
E/BLayout(10764): onInterceptTouchEvent ACTION_DOWN called,return = false
E/CLayout(10764): onInterceptTouchEvent ACTION_DOWN called,return = false
E/CLayout(10764): onTouchEvent ACTION_DOWN called,return = false
E/BLayout(10764): onTouchEvent ACTION_DOWN called,return = true
由于只有BLayout响应click事件,onTouchEvent函数捕获到DOWN事件后,消耗掉此事件。接下来的MOVE和UP事件均传递给BLayout了。由此可以得出结论:
 onTouchEvent 中,返回值是 true ,则说明消耗掉了这个事件,返回值是 false ,则没有消耗掉,会继续传递下去。
      接下来,做第三个测试。在CLayout上,添加一个Button。布局文件如下所示:
<RelativeLayout xmlns:android="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.touchtest.ALayout
        android:id="@+id/view_a"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:background="#ffff0000"
        android:gravity="center" >
        <com.example.touchtest.BLayout
            android:id="@+id/view_b"
            android:layout_width="250dip"
            android:layout_height="250dip"
            android:clickable="true"
            android:background="#ff00ff00"
            android:gravity="center" >
            <com.example.touchtest.CLayout
                android:id="@+id/view_c"
                android:layout_width="150dip"
                android:layout_height="150dip"
                android:clickable="false"
                android:background="#ff0000ff"
                android:gravity="center" >
                <Button
                    android:id="@+id/button1"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="Button" />
            </com.example.touchtest.CLayout>
        </com.example.touchtest.BLayout>
    </com.example.touchtest.ALayout>
</RelativeLayout>
响应的Activity中添加Button响应事件处理
        mBtn = (Button) findViewById(R.id.button1);
        mBtn.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
// TODO Auto-generated method stub
Log.e("","mBtn was click...");
}
});
运行后,效果如图所示

Android事件散发onInterceptTouchEvent与onTouchEvent

  点击Button后,log信息如下 

E/ALayout(10856):onInterceptTouchEvent ACTION_DOWN called,return = false

E/BLayout(10856): onInterceptTouchEvent ACTION_DOWN called,return = false

E/CLayout(10856): onInterceptTouchEvent ACTION_DOWN called,return = false

E/(10856): mBtn was click...

从log分析得知,Button处于最上层的一个View,有Btn截获点击事件,不在传递给B,C,A。

第三个测试,说明了"江湖流言"4,5两条

4.如果最终需要处理事件的view的onTouchEvent()返回了false,表示事件没有被消耗,那么该事件将被传递至其上一层次的view的onTouchEvent()处理。
5.如果最终需要处理事件的view 的onTouchEvent()返回了true,那么后续事件将可以继续传递给该view的onTouchEvent()处理。
 接着做第四个测试。依旧使用上面的代码,但要在ALayout的View中添加一条属性。android:clickable="true"。并且ALayout中的onInterceptTouchEvent函数返回值为true。其修改后的代码如下所示:
ALayout.Java文件
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
// TODO Auto-generated method stub
final int action = ev.getAction();
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
Log.e("ALayout","onInterceptTouchEvent ACTION_DOWN called,return = true");
break;
case MotionEvent.ACTION_MOVE:
break;
case MotionEvent.ACTION_UP:
break;
}
// return super.onInterceptTouchEvent(ev);
return true;
}
布局文件为:
<RelativeLayout xmlns:android="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.touchtest.ALayout
        android:id="@+id/view_a"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:background="#ffff0000"
        android:clickable="true"
        android:gravity="center" >
        <com.example.touchtest.BLayout
            android:id="@+id/view_b"
            android:layout_width="250dip"
            android:layout_height="250dip"
            android:clickable="true"
            android:background="#ff00ff00"
            android:gravity="center" >
            <com.example.touchtest.CLayout
                android:id="@+id/view_c"
                android:layout_width="150dip"
                android:layout_height="150dip"
                android:clickable="true"
                android:background="#ff0000ff"
                android:gravity="center" >
                <Button
                    android:id="@+id/button1"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="Button" />
            </com.example.touchtest.CLayout>
        </com.example.touchtest.BLayout>
    </com.example.touchtest.ALayout>
</RelativeLayout>
运行后,点击Button,log信息如下所示:
12-19 22:49:34.722: E/ALayout(11032): onInterceptTouchEvent ACTION_DOWN called,return = true
12-19 22:49:34.722: E/ALayout(11032): onTouchEvent ACTION_DOWN called,return = true
由第四个测试印证了"江湖流言"2,3两条。
2.如果该ViewGroup的onInterceptTouchEvent()在接收到down事件处理完成之后return false,那么后续的move,   up等事件将继续会先传递给该ViewGroup,之后才和down事件一样传递给最终的目标view的onTouchEvent()处理。
3.如果该ViewGroup的onInterceptTouchEvent()在接收到down事件处理完成之后return true,那么后续的move, up等事件将不再传递给onInterceptTouchEvent(),而是和down事件一样传递给该ViewGroup的onTouchEvent()处理。并且,childView将接收不到任何事件
PS:关于事件分发机制的深入研究,最好还是从源码着手。以上测试例子,仅仅从结果入手,并没有做到“知其所以然”。