美文网首页
事件分发机制

事件分发机制

作者: 大苏打6815 | 来源:发表于2019-11-18 17:31 被阅读0次

点击APP手机一个屏幕,可以理解为一个空的屏幕,在这里,肯定会硬件和软件进行一部分复杂的工作,简单的来理解,就是硬件调用我们软件层,然后调用底层一些C++的代码,C++代码又调用java层,我们进入Activity里面的源码,可以看到有一个方法叫public boolean dispatchTouchEvent(MotionEvent ev),这样我们就知道我们手指触碰的一些最基本的一些指令。

源码
public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();//空方法,以便于自己后续实现一些需求
        }
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev); 
    }
分发的核心方法

dispatchTouchEvent(MotionEvent ev):用来进行事件分发
onInterceptTouchEvent(MotionEvent ev):判断是否拦截事件(只存在于ViewGroup中)
onTouchEvent(MotionEvent ev):处理点击事件

注意:View是没有拦截方法的,只有容器里面才有

![image.png](https://img.haomeiwen.com/i16119272/8e33336f189fbe0 d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

以下是事件分发的传递过程:

事件分发.jpg
事件分发实例

多指触控过程中:
ACTION_DOWN:触控时总是第一个被触发,之后就不会再触发。
ACTION_POINTER_DOWN:只要还有触控点在屏幕上,之后手指下去都是只触发这个事件。
ACTION_UP:触控点离开事,仅当最后一个触控点消失时才会触发。
ACTION_POINTER_UP:只要还有触控点在屏幕上,每当手指离开都会触发这个事件
ACTION_MOVE:可以获取按下触控点的位置(getX和getY)

多点触控重要的方法:
getPointerCount():返回触控点总数
getActionMasked():用这个方法才能出现掩盖多指触控的事件。
getActionIndex():屏幕上每个手指,都对应一个index,返回触发按下或抬起动作的下标index.
getX(int pointerIndex):获取指定触控点的x坐标
getY(int pointerIndex):获取指定触控点的y坐标

我们经常可见的滑动冲突用下列几张图代表:
1、外部滑动与内部滑动方向不一致

image.png

2、外部滑动与内部滑动方向一致

image.png

3、以上两种情况的组合


image.png

解决滑动冲突的步骤
1、指定合适的滑动策略
2、按滑动策略分发事件

外部拦截法

所谓外部拦截发,是指点击事件都先经过父容器的拦截处理,如果父容器需要就拦截,否则就不拦截,这种方法比较符合点击事件的分发机制,外部拦截法需要重写父容器的onIntercepterTouchEvent方法,在内部做相应的拦截即可。

image.png
内部拦截法

如果是个容器也可以在onInterceptTouchEvent中实现此段代码
内部拦截法是指父容器不拦截任何事件,所有的事件都传递给子元素,如果子元素需要此事件就直接消耗掉,否则就交给父容器进行处理,这种方法和Android的事件分发机制不一致,需要配合requestDisallowInterceptTouchEvent方法才能正常工作,较外部拦截法稍显复杂。内部拦截法需要重写子元素的dispatchTouchEvent方法

image.png

ViewPager和RecycleView其实都对内部的一些滑动冲突自动做了处理。

常见实际案例中处理方法
ezgif-6-8d92797b6f7b.gif

以上的布局,其实最外层是一个大的Scroll(命名为ConScrollView)包含了一个linerLayout,LinerLayout分三部分,最上面红色的部分是一个Scroll(命名为ConScrollView1),中间的是一个RecycleView(自动做了一系列处理事件),最下面就是一个LinerLayout包含的几个TextView

代码如下

<?xml version="1.0" encoding="utf-8"?>
<com.dn_alan.myapplication.conflict.ConScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <com.dn_alan.myapplication.conflict.ConScrollView1
            android:id="@+id/viewpager"
            android:layout_width="match_parent"
            android:layout_height="200dp">

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:orientation="vertical">

                <TextView
                    android:id="@+id/text"
                    android:layout_width="match_parent"
                    android:layout_height="100dp"
                    android:background="#f00"
                    android:text="TwoScrollView1" />

                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="100dp"
                    android:background="#f00"
                    android:text="TwoScrollView1" />

                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="100dp"
                    android:background="#f00"
                    android:text="TwoScrollView1" />

            </LinearLayout>
        </com.dn_alan.myapplication.conflict.ConScrollView1>

        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/rel_view"
            android:layout_width="match_parent"
            android:layout_height="400dp"
            app:layoutManager="LinearLayoutManager"/>

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">

            <TextView
                android:layout_width="match_parent"
                android:layout_height="100dp"
                android:textColor="#f00"
                android:text="TwoScrollView" />

            <TextView
                android:layout_width="match_parent"
                android:layout_height="100dp"
                android:textColor="#f00"
                android:text="TwoScrollView" />

            <TextView
                android:layout_width="match_parent"
                android:layout_height="100dp"
                android:textColor="#f00"
                android:text="TwoScrollView" />

        </LinearLayout>

    </LinearLayout>
</com.dn_alan.myapplication.conflict.ConScrollView>

如果我们什么都不处理的话,效果图并不是上面那样子的,你会发现当你滑动红色的地方的时候,会出现拖不动红色框内部的Scroll,因为父控件已经把他给拦截了。我们想要处理这个事件,有两种方法。

第一种就是不让父控件拦截,重写父控件里面的OnInterceptTouchEvent(外部拦截)
public class ConScrollView extends ScrollView {
    private int downY;
    private int mTouchSlop;   //滑动系数

    public ConScrollView(Context context) {
        super(context);
        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
    }

    public ConScrollView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
    }

    public ConScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
    }

    @TargetApi(21)
    public ConScrollView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();//系统给出来滑动多少才算滑动的意思。
    }

    //外部拦截法
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        switch (ev.getAction()){
            case MotionEvent.ACTION_DOWN:
                downY = (int)ev.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                //请求父View 不要拦截事件
               /* if (ev.getY() > 200) {
                    return true;  //拦截
                } else {
                    return false;  //不拦截
                }*/


                int moveY = (int)getY();
                if(Math.abs(moveY - downY) > mTouchSlop){
                    return false;  //不拦截
                }
            case MotionEvent.ACTION_UP:
                break;

        }
        return super.onInterceptTouchEvent(ev);

    }
}
第二种就是让子控件去自己处理(内部拦截)需要重写子控件dispatchTouchEvent方法,并且注意了,还要用上requestDisallowInterceptTouchEvent,主要是让上层父控件拦不拦截的意思。要配合着写。
public class ConScrollView1 extends ScrollView {
    private int mTouchSlop;
    private int downY;

    public ConScrollView1(Context context) {
        super(context);
        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
    }

    public ConScrollView1(Context context, AttributeSet attrs) {
        super(context, attrs);
        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
    }

    public ConScrollView1(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
    }

    @TargetApi(21)
    public ConScrollView1(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
    }

    //内部拦截法
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                downY = (int) ev.getY();
                //请求父View 不要拦截事件,这样保存子View 能够接收到action_move
                getParent().requestDisallowInterceptTouchEvent(true);
                break;
            case MotionEvent.ACTION_MOVE:
//                //请求父View 不要拦截事件
                if (ev.getY() > 200) {
                    getParent().requestDisallowInterceptTouchEvent(false);
                } else {
                    getParent().requestDisallowInterceptTouchEvent(true);
                }

                break;

            case MotionEvent.ACTION_UP:
                break;
        }
        return super.dispatchTouchEvent(ev);
    }
}

事件拦截的基本意义就是这样了,比如有那种仿照QQ的侧滑删除,网上太多了,基本原理都差不多。demo和案例都奉上
链接:https://pan.baidu.com/s/1u22c0pY3tEdq_vrnS5gksA
提取码:kh7m

相关文章

网友评论

      本文标题:事件分发机制

      本文链接:https://www.haomeiwen.com/subject/qomtictx.html