Android处理滑动冲突

1、技术概述

在Android开发中,不可避免地会遇到View嵌套的情况,而其中就会出现滑动冲突的现象,所以了解解决滑动冲突的方法是十分有必要的

2、技术详述

滑动冲突的场景

常见的滑动冲突的场景主要有以下两种:

  • 父View滑动方向和子View滑动方向不一致
  • 父View滑动方向和子View滑动方向一致

滑动冲突产生的原因

Android的事件分发机制是从父View传递到子View的,首先事件会传递到父View的dispatchTouchEvent方法中,而该方法会调用onInterceptTouchEvent方法判断父View在此时是否需要拦截该事件,如果用户在某个方向的滑动距离超过某一个阈值,则父View的onInterceptTouchEvent方法会返回true,此时父View会直接接管该事件流程直到出现ACTION_UP事件也就是用户抬手时,事件会直接交由父View的onTouchEvent方法处理,造成了子View无法接收到事件;亦或是父View在ACTION_DOWN事件时放弃了该事件,父View会将该事件分发给所有子View处理,此时某个子View先行检测到滑动,此时子View的onInterceptTouchEvent将返回true,事件就会由子View的onTouchEvent方法处理,若该方法返回了true,那么该事件将不会被父View处理。此时便产生了滑动冲突。

滑动冲突的解决方案

滑动冲突的解决方法主要有两种,分为外部拦截法与内部拦截法

外部拦截法

外部拦截法的原理是重写父View的onInterceptTouchEvent方法,通过判断父View是否需要该事件来选择是否拦截该事件,从而解决了滑动冲突

public boolean onInterceptTouchEvent(MotionEvent event) {
    boolean intercepted = false;
    int x = (int) event.getX();
    int y = (int) event.getY();
    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN: 
            //父View的ACTION_DOWN事件必须返回false,否则后续ACTION_MOVE以及ACTION_UP事件会直接交由父View处理不会经过子View
            intercepted = false;
            break;
        case MotionEvent.ACTION_MOVE: 
            if (父容器需要当前点击事件) {
                intercepted = true;
            } else {
                intercepted = false;
            }
            break;
        case MotionEvent.ACTION_UP: 
            //如果父View拦截了该事件,则子View会接收不到ACTION_UP事件导致点击事件失效
            intercepted = false;
            break;
        default:
            break;
    }
    mLastXIntercept = x;
    mLastYIntercept = y;
    return intercepted;
}
内部拦截法

内部拦截法的原理是重写子View的dispatchTouchEvent方法,利用调用父View的requestDisallowInterceptTouchEvent(true)方法使父View不处理该事件让事件分发给子View处理,如果子View不需要处理该事件则调用父View的requestDisallowInterceptTouchEvent(false)使父View重新具备处理事件的能力

public boolean dispatchTouchEvent(MotionEvent event) {
    int x = (int) event.getX();
    int y = (int) event.getY();
    switch (event.getAction()) {
        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);
}

同时还需要重写父View的onInterceptTouchEvent方法,因为父View在处理ACTION_DOWN事件时是不受requestDisallowInterceptTouchEvent设置的标志位约束的,具体而言是在父View的dispatchTouchEvent方法中判断事件是ACTION_DOWN类型时会清除requestDisallowInterceptTouchEvent设置的标志位,如果父View处理了ACTION_DOWN事件,之后的ACTION_MOVE以及ACTION_UP事件就不会分发给子View,这时重写的·dispatchTouchEvent·方法里写的判断就都失效了。所以需要重写方法使父View不处理ACTION_DOWN事件从而让事件分发给子View

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

3、总结

滑动冲突可以利用外部拦截法以及内部拦截法处理,主要利用了onInterceptTouchEventrequestDisallowInterceptTouchEvent等方法进行处理

4、参考文献

《Android开发艺术探索》 任玉刚著. ————电子工业出版社

你可能感兴趣的:(Android处理滑动冲突)