当前位置:阳光沙滩 >Android > 查看文章
阿里云优惠码

1.序言

我们在安卓开发中,在自己写空间的时候,有时会遇到一些事件的冲突问题。比如说一个侧滑的控件里头有一个上下滑动的列表。那我们触摸的时候,这个事件是会被侧滑抢掉呢,还是被上下滑动抢掉呢?

这个时候,我们要完美地处理这些冲突,就需要对事件的分发进行了解了。

2.三种事件类型

触摸事件有那些呢?凭借着我们的生活经验,触摸呢,可以分为以下三种:

  • ACTION_DOWN:手按下的时候,就为down操作啦。
  • ACTION_MOVE:当用户的手已经触摸到了屏幕上,有移动操作的时候,就会产生移动的事件了。
  • ACTION_UP:当用户的手,由前两个状态中的其中一个,到离开屏幕,这个动作就是up。

用户操作中,一次完整的触摸事件,一定有ACTION_DOWN和ACTION_UP这两个动作。至于move动作嘛,那得看用户有没有移动呢。

3.控制事件分发的三个方法

我们对事件分发的研究对象呢是三个,分别是:Activity、View、ViewGroup,首先我们要看看一个界面中这三者的典型关系图:

%e5%b1%8f%e5%b9%95%e5%bf%ab%e7%85%a7-2016-12-25-10-37-54

%e5%9b%be%e7%89%871

这里的文字很重要:首先要解释一下的是什么是View 什么是ViewGroup。上面这个图呢,只能代表一下结构,而不能概括全部。因为一个ViewGroup里头可以有多个View和ViewGroup。

那么到这里也就可以解释了:View指的是单一的一个非容器性的控件,比如说TextView,Buttom,ImageView这些控件,它不能再包含其他的控件了,这就是View。而ViewGroup我们可以理解为控件组。也就是把一大堆控件(View)归成一组。所以ViewGrop指的是一些控件容器,比如说:Linearlayout,RelativeLayout这些里头继续包含其他控件或者控件组的就是ViewGroup啦。

另外一个就是ViewGroup是View的子类啦,其实只要这样理解就可以了。View是最小的控件个体,而ViewGroup作为一个容器,用于放置View.

有了前面的前提以后,我们就可以分析负责事件分发的三个方法了:

以下这个是事件分发的回调方法(dispatch)分发的意思。这个方法呢,Activity、View、ViewGrop三者都有,这里要注意一下哈,后面会再提出的。

 public boolean dispatchTouchEvent(MotionEvent ev) {
        
    }

下面这个方法的话,只有ViewGroup才有,而Activity和View没有这个方法。这个方法是用于是否把事件传递,或者说是否把事件分发给它的孩子,也就是包裹在它里面的控件。后面我们再对它的返回值进行详细解释。

  public boolean onInterceptTouchEvent(MotionEvent event) {
       
    }

那么第三个方法呢则是onTouchEvent(MotionEvent event),这个方法是用于处理事件的。也就是用来消费触摸事件的。这个方法和第一个一样,Activity、View、ViewGroup三个都有

 public boolean onTouchEvent(MotionEvent event) {
       
    }

4.举一个例子来看看事件的分发过程

首先,我们创建一个MyButton类并且继承Button,然后 实现三个构造方法,并且覆写上面我们提到的两个事件分发的方法:

package com.sunofbeaches.toucheventdispatchdemo;

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

/**
 * Created by trillgates on 16/12/25.
 */
public class MyButton extends Button{

    private static final String TAG = "MyButton";

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

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

    public MyButton(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        
        //Get the action of current event
        int action = event.getAction();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                Log.d(TAG,"Here is my button's dispatchTouchEvent method ...ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                Log.d(TAG,"Here is my button's dispatchTouchEvent method ...ACTION_MOVE");
                break;
            case MotionEvent.ACTION_UP:
                Log.d(TAG,"Here is my button's dispatchTouchEvent method ...ACTION_UP");
                break;
        }
        return super.dispatchTouchEvent(event);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        //Get the action of current event
        int action = event.getAction();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                Log.d(TAG,"Here is my button's onTouchEvent method ...ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                Log.d(TAG,"Here is my button's onTouchEvent method ...ACTION_MOVE");
                break;
            case MotionEvent.ACTION_UP:
                Log.d(TAG,"Here is my button's onTouchEvent method ...ACTION_UP");
                break;
        }
        
        return super.onTouchEvent(event);
    }
}

 

然后我们选中类名,右键,选择复制引用(Copy Reference)

%e5%b1%8f%e5%b9%95%e5%bf%ab%e7%85%a7-2016-12-25-21-37-48

接着,我们要MainActivity的布局里头,编写如下代码:

<?xml version="1.0" encoding="utf-8"?>
<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"
    tools:context=".MainActivity">

    <com.sunofbeaches.toucheventdispatchdemo.MyButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:text="点击我"/>

</RelativeLayout>

界面就这样子吧:

%e5%b1%8f%e5%b9%95%e5%bf%ab%e7%85%a7-2016-12-26-09-47-02

MainActivity里头我们也写一些相关的代码,看一下一次点击事件是怎么个传递的。

package com.sunofbeaches.toucheventdispatchdemo;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }


    /**
     * We just print the log to know that myButton was clicked.
     * @param v click view
     */
    public void clickMyButton(View v){
        if (v == findViewById(R.id.my_button)) {
            Log.d(TAG,"MyButton was clicked...");
        }
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        //Get the action of current event
        int action = ev.getAction();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                Log.d(TAG,"Here MainActivity dispatchTouchEvent method ...ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                Log.d(TAG,"Here MainActivity dispatchTouchEvent method ...ACTION_MOVE");
                break;
            case MotionEvent.ACTION_UP:
                Log.d(TAG,"Here MainActivity dispatchTouchEvent method ...ACTION_UP");
                break;
        }
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        //Get the action of current event
        int action = event.getAction();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                Log.d(TAG,"Here MainActivity onTouchEvent method ...ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                Log.d(TAG,"Here MainActivity onTouchEvent method ...ACTION_MOVE");
                break;
            case MotionEvent.ACTION_UP:
                Log.d(TAG,"Here MainActivity onTouchEvent method ...ACTION_UP");
                break;
        }
        return super.onTouchEvent(event);
    }
}

把程序跑起来,然后点击按钮,查看Log如下:

D/MainActivity: Here MainActivity dispatchTouchEvent method ...ACTION_DOWN
D/MyButton: Here is my button's dispatchTouchEvent method ...ACTION_DOWN
D/MyButton: Here is my button's onTouchEvent method ...ACTION_DOWN
D/MainActivity: Here MainActivity dispatchTouchEvent method ...ACTION_UP
D/MyButton: Here is my button's dispatchTouchEvent method ...ACTION_UP
D/MyButton: Here is my button's onTouchEvent method ...ACTION_UP
D/MainActivity: MyButton was clicked...

我们从上面这些Log看来,就已经很清晰了是吧。故事是这样讲起的:

从Activity的dispatchTouchEvent方法的按下,再到button的dispatchTouchEvent方法,之后呢,进入button的onTouchEvent方法进行消费。接着呢,是松开事件(up)的处理也是一样的流程。当一个按下和松开,也就是一个down和up的完成以后,也就是这个按钮的点击事件啦。

%e5%b1%8f%e5%b9%95%e5%bf%ab%e7%85%a7-2016-12-26-10-32-45

上面这个是不完整的,我们仅仅是把Activity和View的打出来的。这两个的话我们前面已经说过了,它们俩只有两个处理事件的方法,分别是dispatchTouchEvent方法和onTouchEvent方法。

那么接下来的话,我们要继续研究一下这个ViewGroup相关的事件分发的方法。ViewGroup呢,有三个事件分发相关的方法。分别是dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent。同们,我们编写一个类,名字叫做MyRelativeLayout并且继承自RelativeLayout这个类。然后呢,实现对应的构造方法和覆写三个事件分发的方法,已经打出一些Log信息,好让我们分析一下事件的分发流程。

5.ViewGroup的事件分发

我们先通过上面一个简单的例子,让大家看懂了,再来去分析这个ViewGrop的事件分发哈。其实是在前面的基础上的。理解了就非常简单的。

同样的,我们编写一个类叫做MyRelativeLayout并且继承RelativeLayout,实现里面的几个构造方法来着,并且复写三个控制事件分发的三个方法。

package com.sunofbeaches.toucheventdispatchdemo;

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

/**
 * Created by trillgates on 16/12/25.
 */
public class MyRelativeLayout extends RelativeLayout {
    private static final String TAG = "MyRelativeLayout";

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

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

    public MyRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        //Get the action of current event
        int action = ev.getAction();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                Log.d(TAG,"Here MyRelativeLayout dispatchTouchEvent method ...ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                Log.d(TAG,"Here MyRelativeLayout dispatchTouchEvent method ...ACTION_MOVE");
                break;
            case MotionEvent.ACTION_UP:
                Log.d(TAG,"Here MyRelativeLayout dispatchTouchEvent method ...ACTION_UP");
                break;
        }
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        //Get the action of current event
        int action = event.getAction();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                Log.d(TAG,"Here MyRelativeLayout onInterceptTouchEvent method ...ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                Log.d(TAG,"Here MyRelativeLayout onInterceptTouchEvent method ...ACTION_MOVE");
                break;
            case MotionEvent.ACTION_UP:
                Log.d(TAG,"Here MyRelativeLayout onInterceptTouchEvent method ...ACTION_UP");
                break;
        }
        return super.onInterceptHoverEvent(event);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        //Get the action of current event
        int action = event.getAction();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                Log.d(TAG,"Here MyRelativeLayout onTouchEvent method ...ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                Log.d(TAG,"Here MyRelativeLayout onTouchEvent method ...ACTION_MOVE");
                break;
            case MotionEvent.ACTION_UP:
                Log.d(TAG,"Here MyRelativeLayout onTouchEvent method ...ACTION_UP");
                break;
        }
        return super.onTouchEvent(event);
    }
}

写完这个以后呢,像前面一样,我们要复制这个类的引用。然后转到我们的MainActivity的布局里,把RelatvieLayout布局替换掉即可,如下:

<?xml version="1.0" encoding="utf-8"?>
<com.sunofbeaches.toucheventdispatchdemo.MyRelativeLayout
    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"
    tools:context=".MainActivity">

    <com.sunofbeaches.toucheventdispatchdemo.MyButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/my_button"
        android:onClick="clickMyButton"
        android:layout_centerInParent="true"
        android:text="点击我"/>

</com.sunofbeaches.toucheventdispatchdemo.MyRelativeLayout>

样子还是一样的,只不过呢,这个包裹着MyButton的你控件呢就是我们写的这个MyRelativelayout。这样子,我就把程序跑起来,通过查看Log来看这个事件到底何去何从。

D/MainActivity: Here MainActivity dispatchTouchEvent method ...ACTION_DOWN
D/MyRelativeLayout: Here MyRelativeLayout dispatchTouchEvent method ...ACTION_DOWN
D/MyRelativeLayout: Here MyRelativeLayout onInterceptTouchEvent method ...ACTION_DOWN
D/MyButton: Here is my button's dispatchTouchEvent method ...ACTION_DOWN
D/MyButton: Here is my button's onTouchEvent method ...ACTION_DOWN
D/MainActivity: Here MainActivity dispatchTouchEvent method ...ACTION_UP
D/MyRelativeLayout: Here MyRelativeLayout dispatchTouchEvent method ...ACTION_UP
D/MyRelativeLayout: Here MyRelativeLayout onInterceptTouchEvent method ...ACTION_UP
D/MyButton: Here is my button's dispatchTouchEvent method ...ACTION_UP
D/MyButton: Here is my button's onTouchEvent method ...ACTION_UP
D/MainActivity: MyButton was clicked...

 

通过观察上面的Log,我们可以知道。我们的事件并没有进入Activity的onTouchEvent方法,也没有进入MyRelativeLayout的onInterceptTouchEvent方法以及它的onTouchEvent方法,而是直接进入它孩子MyButton的dispatchTouchEvent方法和它的onTouchEvent方法。

要解开这个问题呢,我们还要深入一下研究。我们可以发现,这些方法都需要返回布尔值的结果。那么这个值呢,就是表示是否消费事件。请看下图吧!

6.事件分发方法的返回值

为了更好地说明事件分发中,返回值对事件传递的影响。我先画个图,稍后再做实验验证吧:

android%e4%ba%8b%e4%bb%b6%e5%88%86%e5%8f%91%e6%b5%81%e7%a8%8b

具体如何时做这个实验,大家可以用我们初中学的控制变量法哈。修改一个值,看打出来的Log,就可以知道事件是怎么个传递的。在实际处理事件冲突的问题中,看着这个图来分析就可以了。不需要背下来哈!

7.请求父控件不要拦截的方法

这个方法是ViewParent里头的一个方法,我们先看看它的说明哈:

 

/**
     * Called when a child does not want this parent and its ancestors to
     * intercept touch events with
     * {@link ViewGroup#onInterceptTouchEvent(MotionEvent)}.
     *
     * <p>This parent should pass this call onto its parents. This parent must obey
     * this request for the duration of the touch (that is, only clear the flag
     * after this parent has received an up or a cancel.</p>
     * 
     * @param disallowIntercept True if the child does not want the parent to
     *            intercept touch events.
     */
    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept);

根据上面的描述,是吧。我们什么时候去调用呢。当这个孩子不想它的父母或者它的祖宗的onInterceptTouchEvent方法拦截事件时,就可以通过这个方法来实现啦。有些时候,事件已经被父控件拦截了。这个时候呢,我们就可以通过这个方法来请求父控件不要拦截啦。

 

7K
相关文章
为您推荐
各种观点

报歉!评论已关闭.