触摸事件/事件分发流程/滑动冲突处理

目录

         1.触摸事件MotionEvent:

1)主要对应3操作

2)MotionEvent内部主要方法:

         2.事件传递

1)默认情况demo:

2)Activity的dispatchTouchEvent返回true

3)ViewGroup的dispatchTouchEvent返回true

         4)ViewGroup的onInterceptTouchEvent返回true

         5)  View的dispatchTouchEvent()返回true

         6)  View的onTouchEvent返回true

         7)ViewGroup的onTouchEvent返回true

8)  给自定义view设置点击事件

9)给自定义view设置点击事件同时设置Touch监听

 3.事件冲突处理的2种方式

1)外部拦截法-通过ViewGroup层去做拦截

2)内部拦截法-通过view层去做拦截


1.触摸事件MotionEvent:

1)主要对应3操作

ACTION_DOWN(手指触屏)       

ACTION_MOVE (手指移动,ViewConfiguration.get(this).getScaledTouchSlop()获取判定为位移的阈值距离)     

ACTION_UP(手指移动)

2)MotionEvent内部主要方法:

getX()获取点击位置距离当前View左边的距离

getY()获取点击位置距离当前View上边的距离

getRawX()获取点击位置距离屏幕左边的距离

getRawY()获取点击位置距离屏幕上边的距离

2.事件传递

Activity主要方法有dispatchTouchEvent()和onTouchEvent();

ViewGroup主要方法有dispatchTouchEvent()和onTouchEvent()和onInterceptTouchEvent();

View主要方法有dispatchTouchEvent()和onTouchEvent();

事件传递是由Activity/Viewgroup/View实现,自上而下传递(即默认情况的传递方向:Activity-->Viewgroup-->View)。

能够将事件消费掉的方法有dispatchTouchEvent和onTouchEvent,onInterceptTouchEvent方法主要起到拦截,并且不向下传递事件的作用。

下面将用demo演示各种场景下,3者的调用顺序,并做出总结。

1)默认情况demo:

MainActivity代码:

public class MainActivity extends Activity {

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.e(TAG, "activity的dispatchTouchEvent");
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.e(TAG, "activity的onTouchEvent");
        return super.onTouchEvent(event);
    }
}

自定义ViewGroup代码:

public class MLinearLayout extends LinearLayout {

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.e(TAG, "viewgroup的dispatchTouchEvent");
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.e(TAG, "viewgroup的onTouchEvent");
        return super.onTouchEvent(event);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        Log.e(TAG, "viewgroup的onInterceptTouchEvent");
        return super.onInterceptTouchEvent(ev);
    }
}

自定义View代码:

public class MTextView extends android.support.v7.widget.AppCompatTextView {
    
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.e(TAG, "view的dispatchTouchEvent");
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.e(TAG, "view的onTouchEvent");
        return super.onTouchEvent(event);
    }
}

xml布局中代码:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
    android:orientation="vertical"
    tools:context="com.bihucj.mcandroid.ui.MainActivity">

    <com.bihucj.mcandroid.view.MLinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <com.bihucj.mcandroid.view.MTextView
            android:layout_width="match_parent"
            android:layout_height="match_parent" />

    </com.bihucj.mcandroid.view.MLinearLayout>
</LinearLayout>

运行后点击布局,打印日志如下:

E/事件分发顺序: activity的dispatchTouchEvent
E/事件分发顺序: viewgroup的dispatchTouchEvent
E/事件分发顺序: viewgroup的onInterceptTouchEvent
E/事件分发顺序: view的dispatchTouchEvent

E/事件分发顺序: view的onTouchEvent
E/事件分发顺序: viewgroup的onTouchEvent
E/事件分发顺序: activity的onTouchEvent

可以看出Activity,ViewGroup,View都是会先走dispatchTouchEvent方法,这个就是前面所说的自上而下的分发顺序,如果在分发的时候不去消费事件,就会导致由View层自下而上的通过onTouchEvent()方法处理事件。是不是很像一个U字型。

https://img-blog.csdn.net/20180815171045271?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM3MzIxMDk4/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70

后面demo我们的xml布局都不会变,并且不改变每一层onTouchEvent()的返回值。

2)Activity的dispatchTouchEvent返回true

打印日志如下:

E/事件分发顺序: activity的dispatchTouchEvent

可以看到dispatchTouchEvent()方法确实有消费事件的能力,也就不会向下传递事件了。

3)ViewGroup的dispatchTouchEvent返回true

打印日志如下:

E/事件分发顺序: activity的dispatchTouchEvent
E/事件分发顺序: viewgroup的dispatchTouchEvent

事件被ViewGroup层的dispatchTouchEvent方法消费了

4)ViewGroup的onInterceptTouchEvent返回true

打印日志如下:

E/事件分发顺序: activity的dispatchTouchEvent
E/事件分发顺序: viewgroup的dispatchTouchEvent
E/事件分发顺序: viewgroup的onInterceptTouchEvent
E/事件分发顺序: viewgroup的onTouchEvent
E/事件分发顺序: activity的onTouchEvent

可以看到onInterceptTouchEvent类似于导流的作用,U字型的走势变成了小U字型。如图:

https://img-blog.csdn.net/20180815172003695?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM3MzIxMDk4/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70

5) View的dispatchTouchEvent()返回true

打印日志如下:

E/事件分发顺序: activity的dispatchTouchEvent
E/事件分发顺序: viewgroup的dispatchTouchEvent
E/事件分发顺序: viewgroup的onInterceptTouchEvent
E/事件分发顺序: view的dispatchTouchEvent

可以看到事件在view的dispatchTouchEvent()方法内被消费了

6) View的onTouchEvent返回true

打印日志如下:

E/事件分发顺序: activity的dispatchTouchEvent
E/事件分发顺序: viewgroup的dispatchTouchEvent
E/事件分发顺序: viewgroup的onInterceptTouchEvent
E/事件分发顺序: view的dispatchTouchEvent
E/事件分发顺序: view的onTouchEvent

可以看到,事件在view的onTouchEvent方法中被消费。如图:

https://img-blog.csdn.net/20180815172625695?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM3MzIxMDk4/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70

7)ViewGroup的onTouchEvent返回true

打印日志如下:

E/事件分发顺序: activity的dispatchTouchEvent
E/事件分发顺序: viewgroup的dispatchTouchEvent
E/事件分发顺序: viewgroup的onInterceptTouchEvent
E/事件分发顺序: view的dispatchTouchEvent
E/事件分发顺序: view的onTouchEvent
E/事件分发顺序: viewgroup的onTouchEvent

如图:

https://img-blog.csdn.net/20180815172838408?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM3MzIxMDk4/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70

总结:老铁们,看到这了,是不是茅塞顿开,还是那2句话。

事件传递是由Activity/Viewgroup/View实现,自上而下传递(即默认情况的传递方向:Activity-->Viewgroup-->View)。能够将事件消费掉的方法有dispatchTouchEvent和onTouchEvent,onInterceptTouchEvent方法主要起到拦截,并将传递方向导流给ViewGroup的onTouchEvent。

8) 给自定义view设置点击事件

findViewById(R.id.textview).setOnClickListener(new View.OnClickListener() {
       @Override
       public void onClick(View v) {
                Log.e(TAG, "自定义view设置点击事件");
       }
});

打印日志如下:

E/事件分发顺序: activity的dispatchTouchEvent
E/事件分发顺序: viewgroup的dispatchTouchEvent
E/事件分发顺序: viewgroup的onInterceptTouchEvent
E/事件分发顺序: view的dispatchTouchEvent
E/事件分发顺序: view的onTouchEvent
E/事件分发顺序: 自定义view设置点击事件

可以看到事件同样也能被view的点击事件所消费掉。

这里的调用流程为:View(onTouchEvent)-->View(PerFormClick) -->View(Click) -->结束

9)给自定义view设置点击事件同时设置Touch监听

代码如下:

findViewById(R.id.textview).setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                Log.e(TAG, "自定义view设置Touch事件");
                return false;
            }
        });
findViewById(R.id.textview).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.e(TAG, "自定义view设置点击事件");
            }
        });

打印日志如下

E/事件分发顺序: activity的dispatchTouchEvent
E/事件分发顺序: viewgroup的dispatchTouchEvent
E/事件分发顺序: viewgroup的onInterceptTouchEvent
E/事件分发顺序: view的dispatchTouchEvent

E/事件分发顺序: 自定义view设置Touch事件
E/事件分发顺序: view的onTouchEvent
E/事件分发顺序: 自定义view设置点击事件

可以看到Touch事件是在点击事件触发之前调用的。那么如果将Touch事件的返回值改为true,情况又是如何,请看打印的日志:

E/事件分发顺序: activity的dispatchTouchEvent
E/事件分发顺序: viewgroup的dispatchTouchEvent
E/事件分发顺序: viewgroup的onInterceptTouchEvent
E/事件分发顺序: view的dispatchTouchEvent
E/事件分发顺序: 自定义view设置Touch事件

可以看到,如果手动复写Touch监听,并且返回true,将不会再执行View的onTouchEvent方法。

基本上大多情形都已经列举了,谢谢大家!如有遗漏,或者错误的,帮忙指出一下,Thank You!

3.事件冲突处理的2种方式

核心思想就是:哪一层需要消费这个事件,并且不想往下传递,可以通过上面例子中的返回true来实现。所以最主要的还是将判断这一层需要消费这个事件转化为条件,满足条件后,就消费掉,所以这个条件的判断处理好,就行了。

1)外部拦截法-通过ViewGroup层去做拦截

// 分别记录上次滑动的坐标(onInterceptTouchEvent)
private int mLastXIntercept = 0;
private int mLastYIntercept = 0;


@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
        //是否拦截,做导流处理
        boolean intercepted = false;

        //这些属性值,可以做为我们判断的依据
        int x = (int) ev.getX();
        int y = (int) ev.getY();


        switch (ev.getAction()) {
            //down时候不能拦截
            case MotionEvent.ACTION_DOWN:
                intercepted = false;
                break;

            
            case MotionEvent.ACTION_MOVE: {
                int deltaX = x - mLastXIntercept;
                int deltaY = y - mLastYIntercept;

                //这里就是所谓的条件判断,满足需要消费的条件,就拦截
                if (Math.abs(deltaX) > Math.abs(deltaY)) {
                    intercepted = true;
                } else {
                    intercepted = false;
                }
                break;
            }

            //记得在up动作时候不去拦截
            case MotionEvent.ACTION_UP: {
                intercepted = false;
                break;
            }
        }
        mLastXIntercept = x;
        mLastYIntercept = y;
        return intercepted;
    }

2)内部拦截法-通过view层去做拦截

先重写自定义view的dispatchTouchEvent方法

@Override
public boolean dispatchTouchEvent(MotionEvent event) {
         int x = (int) event.getX();
         int y = (int) event.getY();

        switch (event.getAction()) {
        
        //方法设置为true表示父容器不拦截
        case MotionEvent.ACTION_DOWN: {
             parent.requestDisallowInterceptTouchEvent(true);
           break;
        }

        case MotionEvent.ACTION_MOVE: {
             int deltaX = x - mLastX;
             int deltaY = y - mLastY;
            //达到父容器需要消费此事件的条件,父容器兰家
            if (父容器需要此类点击事件) {
                parent.requestDisallowInterceptTouchEvent(false);
             }
            break;
         }
       case MotionEvent.ACTION_UP: {
             break;
         }
        default:
             break;
        }
 
        mLastX = x;
        mLastY = y;
   return super.dispatchTouchEvent(event);
}

重写的子元素的dispatchTouchEvent方法,这里同时需要重写父容器的onInterceptTouchEvent方法,为什么呢?

因为ACTION_DOWN事件并不受FLAG_DISALLOW_INTERCEPT这个标记位的控制,所以一旦父容器拦截ACTION_DOWN事件,那么所有的事件都无法传递到子元素之中,这样内部拦截法就无法起作用了。

父容器的代码中方法重写如下:

@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
        int action = event.getAction();
        if (action == MotionEvent.ACTION_DOWN) {
             return false;
         } else {
             return true;
        }
}

猜你喜欢

转载自blog.csdn.net/qq_37321098/article/details/81183891