touch翻译为接触,触摸。我们今天要聊的就是摸的事件。

在Android中了解了Touch事件可以帮助我们解决,ScrollView嵌套ListView,GridView,viewPager滑动冲突,还可以实现覆盖多层布局中里层某个控件的触摸事件处理(简单一点我理解的意思就是隔山打牛)等等,可能和你理解的有些偏差都是我在项目开发过程中总结而来的。针对以上问题,文章后面会给出解决方案。

什么是摸的事件,它们之间又是怎么传递的呢,请听我一一给你们道来。

什么是Android touch事件

就是触摸屏幕的一个过程,在Android中用户的Touch事件被包装成MotionEvent。

touch事件主要类型有:

  1. ACTION_DOWN: 表示用户开始触摸.
  2. ACTION_MOVE: 表示用户在移动(手指或者其他)
  3. ACTION_UP:表示用户抬起了手指
  4. ACTION_CANCEL:表示手势被取消了
  5. ACTION_OUTSIDE:表示用户触碰超出了正常的UI边界.
  6. ACTION_POINTER_DOWN:有一个非主要的手指按下了.
  7. ACTION_POINTER_UP:一个非主要的手指抬起来了

touch事件的元数据包括:

  1. touch的位置
  2. 手指的个数
  3. touch事件的时间

当然一个touch手势被定义为以ACTION_DOWN开始和以 ACTION_UP结束。

事件传递

Android 中与 Touch 事件相关的三个方法

1、public boolean dispatchTouchEvent(MotionEvent ev),这个方法是用来分发事件处理。

2、public boolean onInterceptTouchEvent(MotionEvent ev) ,这个方法是用来拦截,截断事件处理。

3、public boolean onTouchEvent(MotionEvent ev),这个方法是用来处理事件。

能够响应、调用以上方法的有ViewGroup、View、Activity,继承ViewGroup的控件大多是容器控件,如LinearLayout,RelativeLayout等。继承View的控件大多是显示控件,如TextView,Button等。

需要注意的是显示控件是没有onInterceptTouchEvent方法。

上面我们具体来看一个案例,来了解事件是怎么传递的:

Android touch命令 touch for android_touch

上图的.xml文件

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.github.ws.touchdemo.MyRelativeLayout1
        android:layout_width="280dp"
        android:layout_height="280dp"
        android:layout_centerInParent="true"
        android:background="#f00000">

        <com.github.ws.touchdemo.MyRelativeLayout2
            android:layout_width="180dp"
            android:layout_height="180dp"
            android:layout_centerInParent="true"
            android:background="#00f000">

            <com.github.ws.touchdemo.MyTextView
                android:layout_width="80dp"
                android:layout_height="80dp"
                android:layout_centerInParent="true"
                android:background="#0000f0" />

        </com.github.ws.touchdemo.MyRelativeLayout2>

    </com.github.ws.touchdemo.MyRelativeLayout1>

</RelativeLayout>

MyRelativeLayout1

package com.github.ws.touchdemo;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.RelativeLayout;

/**
 * Created by Administrator on 3/9 0009.
 */
public class MyRelativeLayout1 extends RelativeLayout {

    public MyRelativeLayout1(Context context) {
        super(context);
    }

    public MyRelativeLayout1(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.e("------MyRelativeLayout1----", "--------dispatchTouchEvent------");
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.e("------MyRelativeLayout1----", "--------onTouchEvent------");
        return super.onTouchEvent(event);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        Log.e("------MyRelativeLayout1----", "--------onInterceptTouchEvent------");
        return super.onInterceptTouchEvent(ev);
    }
}

MyRelativeLayout2和MyRelativeLayout1一样。

MyTextView

package com.github.ws.touchdemo;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.TextView;

/**
 * Created by Administrator on 3/9 0009.
 */
public class MyTextView extends TextView {
    public MyTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public MyTextView(Context context) {
        super(context);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.e("------MyTextView----", "--------dispatchTouchEvent------");
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.e("------MyTextView----", "--------onTouchEvent------");
        return super.onTouchEvent(event);
    }

}

当我们触摸蓝色区域Log打印如下:

Android touch命令 touch for android_touch_02

通过log图,我们很容易的得出结论:dispatchTouchEvent(),onInterceptTouchEvent()隧道式向下分发(先里后外)。而onTouchEvent()执行顺序恰好相反即冒泡式向上处理(先外后里)。

我们来改变touch方法的返回值,看看它们又是怎么执行的。

MyRelativeLayout1类中dispatchTouchEvent返回true

我直接贴log:

Android touch命令 touch for android_Android touch命令_03

很明显事件并不会向外分发,后面的事件不会触发,默认返回false。

MyRelativeLayout1类中onInterceptTouchEvent返回true

Android touch命令 touch for android_滑动冲突_04

onInterceptTouchEvent()用于处理事件并改变事件的传递方向。处理事件就是在函数内部编写代码处理就可以了。而决定传递方向的是返回值,返回为false时事件会传递给子控件的dispatchTouchEvent();返回值为true时事件会传递给当前控件的onTouchEvent(),不会传递给子控件,这就是所谓的Intercept(拦截,截断)。默认返回false。

MyRelativeLayout1类中onTouchEvent返回true

我们在onTouchEvent中添加如下代码:

switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                Log.e("-------MyRelativeLayout1---------", "--------ACTION_DOWN------");
                break;
            case MotionEvent.ACTION_MOVE:
                Log.e("--------MyRelativeLayout1--------", "--------ACTION_MOVE------");
                break;
        }

贴出log:

Android touch命令 touch for android_事件传递_05

onTouchEvent() 用于处理事件,返回值决定当前控件是否消费了这个事件。如果返回值为true表示消费了这个事件,那么系统就会认为ACTION_DOWN已经发生,ACTION_MOVE或者ACTION_UP就被捕获。反之,ACTION_MOVE或者ACTION_UP就不会被捕获。默认是返回false。(ACTION_MOVE或者ACTION_UP发生的前提是一定曾经发生了ACTION_DOWN)。

看到这里,我相信大家对事件传递有了自己的认识,我给出开头滑动冲突的解决方案:

mScrollView.requestDisallowInterceptTouchEvent(true);

一张图来诠释 Android Touch 事件传递:

Android touch命令 touch for android_touch_06

如果你有兴趣查看源码,请点击github地址