Android滑动冲突解决方法

外部拦截法

点击事件都先经过父容器的拦截处理,如果父容器需要此事件就拦截,如果不需要此事件就不拦截。(比较符合点击事件的分发机制)

  • 重写父容器的onInterceptTouchEvent(),在内部做出相应的拦截即可
  • ACTION_DOWN----------这个事件必须返回false,不拦截(父容器一旦开始拦截,那么后续的时间都会交给它处理),如果拦截,就会导致子元素无法收到ACTION_UP事件,????
  • ACTION_MOVE-----------这个事件根据具体情况决定是否拦截
  • ACTION_UP ---------------必须返回false,因为ACTION_UP事件本身没有多大意义。如果父容器返回true,就会导致子元素无法接收到ACTION_UP事件,这个时候onClick事件就无法触发。
java 复制代码
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;

/**
 * 外部拦截法:父容器通过onInterceptTouchEvent()决定是否拦截事件
 */
public class ExternalInterceptParent extends ViewGroup {
    private float mLastX;
    private float mLastY;
    
    public ExternalInterceptParent(Context context) {
        super(context);
    }
    
    public ExternalInterceptParent(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        // 简单布局,只放置一个子View
        if (getChildCount() > 0) {
            View child = getChildAt(0);
            child.layout(0, 0, getWidth(), getHeight());
        }
    }
    
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        boolean intercepted = false;
        float x = ev.getX();
        float y = ev.getY();
        
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                // 按下事件不拦截,否则后续事件收不到
                intercepted = false;
                mLastX = x;
                mLastY = y;
                break;
                
            case MotionEvent.ACTION_MOVE:
                // 判断是否为水平滑动,如果是则拦截事件
                float dx = x - mLastX;
                float dy = y - mLastY;
                if (Math.abs(dx) > Math.abs(dy)) {
                    intercepted = true; // 水平滑动,父容器处理
                } else {
                    intercepted = false; // 垂直滑动,不拦截,子View处理
                }
                mLastX = x;
                mLastY = y;
                break;
                
            case MotionEvent.ACTION_UP:
                // 抬起事件不拦截,否则子View无法接收点击事件
                intercepted = false;
                break;
        }
        
        return intercepted;
    }
    
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        // 处理父容器的滑动逻辑
        float x = event.getX();
        float y = event.getY();
        
        switch (event.getAction()) {
            case MotionEvent.ACTION_MOVE:
                float dx = x - mLastX;
                // 处理水平滑动...
                mLastX = x;
                mLastY = y;
                break;
        }
        
        return true;
    }
}    
java 复制代码
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.Button;

/**
 * 子View不需要特殊处理事件
 */
public class ChildButton extends Button {
    public ChildButton(Context context) {
        super(context);
    }
    
    public ChildButton(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        // 处理点击事件
        switch (event.getAction()) {
            case MotionEvent.ACTION_UP:
                // 处理点击逻辑
                performClick();
                break;
        }
        return super.onTouchEvent(event);
    }
}    

内部拦截法

父容器不拦截任何事件,所有事件都传递给子元素,如果子元素需要此事件就直接消耗掉,否则就交给父容器处理。(与Android事件分发机制不一样)

  • 重写子元素的onTouchEvent()
  • 父元素不能拦截ACTION_DOWN----------这个事件必须返回false,不拦截(父容器一旦开始拦截,那么后续的时间都会交给它处理),如果拦截,就会导致子元素无法收到ACTION_UP事件
  • 父容器默认拦截ACTION_MOVE和ACTION_UP事件,这样只有当子元素调用了parent.requestDisallowTouchEvent(),父元素才能拦截所需的事件
java 复制代码
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.Button;

/**
 * 子View通过requestDisallowInterceptTouchEvent控制父容器是否拦截事件
 */
public class ChildButtonInternal extends Button {
    private InternalInterceptParent parent;
    private float mLastX;
    private float mLastY;
    
    public ChildButtonInternal(Context context) {
        super(context);
    }
    
    public ChildButtonInternal(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    
    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        // 获取父容器引用
        if (getParent() instanceof InternalInterceptParent) {
            parent = (InternalInterceptParent) getParent();
        }
    }
    
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        float x = event.getX();
        float y = event.getY();
        
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                // 按下时请求父容器不拦截事件
                if (parent != null) {
                    parent.requestDisallowInterceptTouchEvent(true);
                }
                mLastX = x;
                mLastY = y;
                break;
                
            case MotionEvent.ACTION_MOVE:
                float dx = x - mLastX;
                float dy = y - mLastY;
                // 如果是垂直滑动,允许父容器拦截事件
                if (Math.abs(dy) > Math.abs(dx)) {
                    if (parent != null) {
                        parent.requestDisallowInterceptTouchEvent(false);
                    }
                }
                mLastX = x;
                mLastY = y;
                break;
                
            case MotionEvent.ACTION_UP:
                // 处理点击逻辑
                performClick();
                break;
        }
        
        return super.onTouchEvent(event);
    }
}    
java 复制代码
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.ViewGroup;

/**
 * 内部拦截法:父容器默认不拦截除DOWN外的事件
 */
public class InternalInterceptParent extends ViewGroup {
    public InternalInterceptParent(Context context) {
        super(context);
    }
    
    public InternalInterceptParent(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        // 简单布局,只放置一个子View
        if (getChildCount() > 0) {
            View child = getChildAt(0);
            child.layout(0, 0, getWidth(), getHeight());
        }
    }
    
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        // 关键点:DOWN事件必须返回false,否则后续事件不会传递给子View
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            return false;
        } else {
            // 其他事件根据子View的请求决定是否拦截
            return !disallowIntercept;
        }
    }
    
    private boolean disallowIntercept = false;
    
    @Override
    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
        this.disallowIntercept = disallowIntercept;
        super.requestDisallowInterceptTouchEvent(disallowIntercept);
    }
    
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        // 处理父容器的滑动逻辑
        return true;
    }
}    

两种方法的对比

  1. 外部拦截法:简单直观,父容器控制事件拦截逻辑,子 View 无需特殊处理。适用于大多数场景。
  2. 内部拦截法:灵活性高,子 View 可以根据自身需求动态控制父容器是否拦截事件,但需要父容器配合(DOWN 事件必须返回 false)。适用于复杂交互场景。

在实际开发中,推荐优先使用外部拦截法,因为实现简单且不容易出错。只有在外部拦截法无法满足需求时,才考虑使用内部拦截法。

相关推荐
Kapaseker5 小时前
一杯美式搞懂 Any、Unit、Nothing
android·kotlin
黄林晴5 小时前
你的 Android App 还没接 AI?Gemini API 接入全攻略
android
恋猫de小郭15 小时前
2026 Flutter VS React Native ,同时在 AI 时代 VS Native 开发,你没见过的版本
android·前端·flutter
冬奇Lab16 小时前
PowerManagerService(上):电源状态与WakeLock管理
android·源码阅读
BoomHe21 小时前
Now in Android 架构模式全面分析
android·android jetpack
二流小码农1 天前
鸿蒙开发:上传一张参考图片便可实现页面功能
android·ios·harmonyos
鹏程十八少1 天前
4.Android 30分钟手写一个简单版shadow, 从零理解shadow插件化零反射插件化原理
android·前端·面试
Kapaseker1 天前
一杯美式搞定 Kotlin 空安全
android·kotlin
三少爷的鞋1 天前
Android 协程时代,Handler 应该退休了吗?
android
火柴就是我2 天前
让我们实现一个更好看的内部阴影按钮
android·flutter