View滑动冲突的两种解决方式

本文介绍了两种解决View滑动冲突的方法:外部拦截法与内部拦截法。外部拦截法通过父容器拦截处理事件解决冲突,而内部拦截法则通过子元素自行处理事件并配合requestDisallowInterceptTouchEvent方法实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

转载请注明出处
http://blog.youkuaiyun.com/guodongAndroid/article/details/52530227
本文来自【孫小逗的博客】

View滑动冲突的两种解决方式

1、外部拦截法

所谓外部拦截法是指点击事件都先经过父容器的拦截处理,如果父容器需要此事件就拦截,如果不需要此事件就不拦截,这样就可以解决滑动冲突问题,这种方法比较符合点击事件的分发机制。外部拦截法需要重写父容器的onInterceptTouchEvent方法,在内部做相应的拦截即可,这种方法的伪代码如下所示:

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev)
    {
        boolean intercepted = false;
        int x = (int) ev.getX();
        int y = (int) ev.getY();
        int action = ev.getAction();
        switch (action)
        {
            case MotionEvent.ACTION_DOWN:
            {
                intercepted = false;
                break;
            }

            case MotionEvent.ACTION_MOVE:
            {
                if (父容器需要当前点击事件)
                {
                    intercepted = true;
                }
                else
                {
                    intercepted = false;
                }

                break;
            }

            case MotionEvent.ACTION_UP:
            {
                intercepted = false;
                break;
            }

            default:
                break;
        }

        mLastXIntercept = x;
        mLastYIntercept = y;
        return intercepted;
    }

上述代码是外部拦截法的典型逻辑,针对不同的滑动冲突,只需要修改父容器需要当前点击事件的条件即可,其他均不需要修改而且也不能修改。在onInterceptTouchEvent方法中,首先是ACTION_DOWN这个事件,父容器必须返回false,即不拦截DOWN事件,这是因为一旦父容器拦截了DOWN事件,那么后续的MOVE和UP事件都会直接交由父容器处理,这个时候事件没法再传递给子元素了;其次是ACTION_MOVE事件,这个事件可以根据需求来决定是否拦截,如果父容器需要拦截就返回true,否则返回false;最后是ACTION_UP事件,这里必须返回false。

2、内部拦截法

内部拦截法是指父容器不拦截任何事件,所有的事件都传递给子元素,如果子元素需要此事件就直接消耗掉,否则就交由父容器进行处理,这种方法不符合事件分发机制,需要配合requstDisallowInterceptTouchEvent方法才能正常工作,使用起来较外部拦截法稍显复杂,需要重写子元素的dispatchTouchEvent方法,这种方法的伪代码如下所示:

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

        switch (action)
        {
            case MotionEvent.ACTION_DOWN:
            {
                parentView.requestDisallowInterceptTouchEvent(true); // parentView为父容器
                break;
            }

            case MotionEvent.ACTION_MOVE:
            {
                int deltaX = x - mLastDiapatchX;
                int deltaY = y - mLastDiapatchY;
                if (父容器需要此点击事件)
                {
                    parentView.requestDisallowInterceptTouchEvent(false); // parentView为父容器
                }
                break;
            }

            case MotionEvent.ACTION_UP:
                break;

            default:
                break;
        }

        mLastDiapatchX = x;
        mLastDiapatchY = y;
        return super.dispatchTouchEvent(ev);
    }

上述代码是内部拦截法的典型代码,当面对不同的滑动冲突时只需要修改里面的条件即可,其他不需要改动而且也不能改动。除了子元素需要做处理以外,父容器也需要默认拦截除了ACTION_DOWN以外的其他事件,这样当子元素调用parentView.requestDisallowInterceptTouchEvent(false)方法时,父容器才能继续拦截所需事件。父容器为什么不拦截DOWN事件已在外部拦截法中叙述,父容器的改动如下所示:

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev)
    {
        int action = ev.getAction();
        if (action == MotionEvent.ACTION_DOWN)
            return false;
        else
            return true;
    }
### 滑动冲突两种解决方法 #### 方法一:外部解决法 外部解决法适用于滑动方向不同的场景。通过自定义 `OnTouchListener` 来控制事件分发,从而决定由哪个视图处理触摸事件。 以下是实现代码示例: ```java public class ExternalConflictResolver implements View.OnTouchListener { private static final float SLIDE_THRESHOLD = 50f; @Override public boolean onTouch(View v, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: v.getParent().requestDisallowInterceptTouchEvent(true); // 请求父级不拦截 break; case MotionEvent.ACTION_MOVE: float deltaX = Math.abs(event.getX() - startX); float deltaY = Math.abs(event.getY() - startY); if (deltaX > SLIDE_THRESHOLD && deltaX > deltaY) { // 判断水平滑动 v.getParent().requestDisallowInterceptTouchEvent(false); // 取消请求 } break; case MotionEvent.ACTION_UP: v.getParent().requestDisallowInterceptTouchEvent(false); // 结束时恢复默认行为 break; } return false; // 不消费事件,让后续逻辑继续执行 } private float startX, startY; public void setStartPoint(MotionEvent event) { this.startX = event.getX(); this.startY = event.getY(); } } ``` 上述代码实现了对外部解决法的支持[^2]。它通过监听手势移动的距离来判断当前操作是否属于水平滑动,并动态调整父容器的拦截策略。 --- #### 方法二:内部解决法 内部解决法适合用于滑动方向相同的情况。通常需要重写嵌套滚动机制或者利用现有的 API 进行优化。 以下是一个基于 `NestedScrollView` 和 `RecyclerView` 的例子: ```java public class InternalConflictResolver extends RecyclerView { @Override public boolean onInterceptTouchEvent(MotionEvent e) { if (!canScrollHorizontally(-1)) { // 如果无法向左滚动,则允许垂直滑动 getParent().requestDisallowInterceptTouchEvent(false); } else { getParent().requestDisallowInterceptTouchEvent(true); // 否则强制自己处理事件 } return super.onInterceptTouchEvent(e); } @Override public boolean onTouchEvent(MotionEvent e) { if (e.getAction() == MotionEvent.ACTION_MOVE) { if (!canScrollVertically(1) || !canScrollVertically(-1)) { // 当前已到达顶部或底部 getParent().requestDisallowInterceptTouchEvent(false); // 放弃对事件的竞争 } } return super.onTouchEvent(e); } } ``` 此代码片段展示了如何通过覆盖 `onInterceptTouchEvent` 和 `onTouchEvent` 方法来自定义滑动行为[^4]。对于同方向的冲突,可以通过检测子控件的状态(如是否达到边界)进一步细化条件判断。 --- ### 总结 无论是外部还是内部解决法,核心都是围绕事件分发链展开工作。外部解决法更关注全局层面的手势识别,而内部解决法则深入到具体组件的行为定制中。两者各有优劣,需根据实际需求灵活选用。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值