美文网首页
03 事件分发机制-1

03 事件分发机制-1

作者: 凤邪摩羯 | 来源:发表于2020-09-24 08:59 被阅读0次

1 事件MotionEvent

  • 单点触摸


    image.png
  • 多点触摸
    ACTION_POINTER_DOWN :非第一个手指按下
    ACTION_POINTER_UP : 非最后一个手指离开

ACTION_POINTER_1_DOWN : 这是之前SDK版本所使用的,表示第一个手指按下,它的数值跟ACTION_DOWN是相等的
ACTION_POINTER_2_DOWN:第二根手指按下
ACTION_POINTER_3_DOWN:第三根手指按下
ACTION_POINTER_x_DOWN:第x根手指离开

2 事件处理

image.png

3 流程

image.png

事件分发流程就是view树的dispatchTouchEvent 的递归调用过程。onTouchEvent是在递归后进行。
分发是由父view传至子view。处理则是子view先进行, 父view后进行

4 事件冲突解决

image.png

5 参考

https://www.jianshu.com/p/38015afcdb58

https://www.jianshu.com/p/eedc98eecf02

https://www.jianshu.com/p/7ee756b1f9ce?utm_source=desktop&utm_medium=timeline

6 最全滑动冲突处理

https://github.com/2211785113/Android_SlidingConflict

总控件:

  • VG 代替 ViewGroup

纵向控件:

  • SRL 代替 SwipeRefreshLayout
  • TL 代替 TabLayout
  • SV 代替 ScrollView
  • RV 代替 RecyclerView
  • LV 代替 ListView

横向控件:

  • VP 代替 ViewPager
  • SM 代替 SlideMenu

6.1 事件传递:

  • disTE 代替 dispatchTouchEvent(事件分发)
  • onITE 代替 onInterceptTouchEvent(事件拦截)
  • onTE 代替 onTouchEvent(事件消费)
  • reDITE 代替 requestDisallowInterceptTouchEvent(子View要求父View不要拦截事件)
  • setOTL 代替 setOnTouchListener

如果大家不了解View的事件分发机制,可以参看文章:

深度理解Android事件分发机制:https://www.jianshu.com/p/80a1bb7ced57

事件冲突实际上就是对事件分发机制的应用。

6.2 解决事件冲突总结来看有下列几种方法:

  • 父View # onITE 方法:决定事件是否向子View传递。
  • 子View # disTE 方法:通过 reDITE 来干预父View事件的分发。
  • 子View # setOTL 方法:通过 reDITE 来干预父View事件的分发。
  • 注意:标志位在MOVE事件和DOWN事件中都会起作用。

6.3 滑动冲突分为三种情况:

  • 同方向;
  • 不同方向;
  • 同方向和不同方向混合。

6.3.1 同方向:

核心要点:

  • 保证外部滑动的时候只滑动外部,内部滑动的时候只滑动内部。
  • 点击事件交给合适的View处理。

冲突场景:

  • 只有一层能滑动;
  • 内外层同时滑动很卡顿。

1.SRL+SV/RV:没有冲突。

分析:

SRL:重写了onITE方法。向下滑动,Y轴距离大于最小滑动距离,拦截事件,效果正常。

SV/RV:没有重写disTE方法。向上滚动,效果正常。

2.SV+SV:有滑动冲突。

问题:内部不能滑动。

分析:外部Y轴距离大于最小滑动距离,拦截事件,导致内部不能滑动。

解决:

内部拦截:内部滑动时,执行getparent().reDITE(true);

public class SV_SV_inner extends ScrollView {
    public SV_SV_inner(Context context) {
        super(context);
    }
    public SV_SV_inner(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        getParent().requestDisallowInterceptTouchEvent(true);
        return super.dispatchTouchEvent(ev);
    }
}

外部拦截:

public class SV_SV_outer extends ScrollView {
    public SV_SV_outer(Context context) {
        super(context);
    }
    public SV_SV_outer(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        super.onInterceptTouchEvent(ev);
        return false;
    }
}

3.SV+RV:有滑动冲突。

问题:内层在滑动时,外层也在滑动,滑动很卡顿。

分析:

SV:重写了onInterceptTouchEvent方法。当Y轴移动距离大于最小滑动距离时,就会拦截事件,否则向子View传递。

RV:没有重写dispatchTouchEvent方法。

解决:

内部拦截:内部滑动时,通过reDITE设置为true使事件向子View分发。

通过打印日志会发现:当内部滑动的时候,外部没有在滑动。效果实现。

public class SV_RV_inner extends RecyclerView {
    public SV_RV_inner(Context context) {
        super(context);
    }
    public SV_RV_inner(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        getParent().requestDisallowInterceptTouchEvent(true);
        if (ev.getAction() == MotionEvent.ACTION_MOVE) {
            Log.d("SV_RV", "dispatchTouchEvent: 内层在滑动++");
        }
        return super.dispatchTouchEvent(ev);
    }
}

4.VP+VP:没有滑动冲突。

分析:打印日志发现:外部滑动的时候内部没有滑动,内部滑动的时候外部没有滑动。

综上,同一方向有滑动冲突的有两个例子:

  • 只有一层能滑动的例子:SV+SV
  • 内外层同时滑动很卡顿的例子:SV+RV

6.3.2 不同方向:

核心点:

判断谁来拦截:根据滑动是水平滑动还是竖直滑动来判断到底由谁来拦截事件。

判断滑动方向:

  • 根据滑动过程中两点之间的坐标
  • 滑动路径和水平方向所形成的夹角(滑动角度)
  • 水平和竖直方向距离差(距离差)(常用)
  • 水平和竖直方向速度差(速度差)

滑动冲突出现的根本原因:

  • 上层View拦截了下层View导致下层View无法滑动。

1.SRL+VP:

问题:VP左下右下滑动时,SRL也在上下滑动。SRL拦截掉了VP的滑动事件。

分析:SRL源码:Y轴大于最小滑动距离时,父View拦截事件,否则不拦截。

解决:

内部拦截:

SRL_VP_outer:

public class SRL_VP_outer extends SwipeRefreshLayout {
    public SRL_VP_outer(Context context) {
        super(context);
    }
    public SRL_VP_outer(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            super.onInterceptTouchEvent(ev);
            return false;
        }
        return super.onInterceptTouchEvent(ev);
    }
}

SRL_VP_inner:

public class SRL_VP_inner extends ViewPager {
    private float startX;
    private float startY;
    private float x;
    private float y;
    private float deltaX;
    private float deltaY;
    public SRL_VP_inner(Context context) {
        super(context);
    }
    public SRL_VP_inner(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                startX = ev.getX();
                startY = ev.getY();
                //注释1
                ViewCompat.setNestedScrollingEnabled(this, true);
                getParent().requestDisallowInterceptTouchEvent(true);
                break;
            case MotionEvent.ACTION_MOVE:
                x = ev.getX();
                y = ev.getY();
                deltaX = Math.abs(x - startX);
                deltaY = Math.abs(y - startY);
                if (deltaX < deltaY) {
                    getParent().requestDisallowInterceptTouchEvent(false);
                }
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                break;
        }
        return super.dispatchTouchEvent(ev);
    }
}

注释1:

  • 因为SRL重写了requestDisallowInterceptTouchEvent这个方法,

  • ViewCompat.isNestedScrollingEnabled(mTarget)默认为false,

  • 导致super.requestDisallowInterceptTouchEvent(b)不执行,

  • 所以要设置ViewCompat.isNestedScrollingEnabled为true。

内部解决第一种方法:VP内部加这句:ViewCompat.setNestedScrollingEnabled(this, true);

内部解决第二种方法:重写SRL#reDITE。

因为SRL继承ViewGroup类,

所以仿照ViewGroup的reDITE重写SRL的reDITE方法,不写super,class拿到变量名”mGroupFlags”。

@Override
    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
//        super.requestDisallowInterceptTouchEvent(b);
        Class cls = ViewGroup.class;
        try {
            Field field = cls.getField("mGroupFlags");
            int c = (int) field.get(this);
            if (disallowIntercept) {
                field.set(this, 2900051);
            } else {
                field.set(this, 2245715);
            }
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }

外部拦截:

参考链接:https://blog.csdn.net/u010386612/article/details/50548977

SRL_VP_out_SRL:

public class SRL_VP_out_SRL extends SwipeRefreshLayout {
    private boolean mIsBeingDragged;
    private float initialX;
    private float initialY;
    private float x;
    private float y;
    private float deltaX;
    private float deltaY;
    private int mTouchSlop;
    boolean isVpDragged = false;
    public SRL_VP_out_SRL(Context context) {
        super(context);
    }
    public SRL_VP_out_SRL(Context context, AttributeSet attrs) {
        super(context, attrs);
        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
    }
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                initialX = ev.getX();
                initialY = ev.getY();
                isVpDragged = false;
                break;
            case MotionEvent.ACTION_MOVE:
                //只要手指不离开就一直返回false
                if (isVpDragged)
                    return false;
                x = ev.getX();
                y = ev.getY();
                deltaX = Math.abs(x - initialX);
                deltaY = Math.abs(y - initialY);
                if (deltaX > deltaY) {
                    isVpDragged = true;
                    return false;
                }
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                isVpDragged = false;
                break;
        }
        return super.onInterceptTouchEvent(ev);
    }
}

2.SV+VP:没有冲突。

3.VP+SV:没有冲突。

4.VP+LV:没有冲突。

相关文章

网友评论

      本文标题:03 事件分发机制-1

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