Android滑动冲突解决方法

发布于:2025-05-24 ⋅ 阅读:(22) ⋅ 点赞:(0)

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

  • 重写父容器的onInterceptTouchEvent(),在内部做出相应的拦截即可
  • ACTION_DOWN----------这个事件必须返回false,不拦截(父容器一旦开始拦截,那么后续的时间都会交给它处理),如果拦截,就会导致子元素无法收到ACTION_UP事件,????
  • ACTION_MOVE-----------这个事件根据具体情况决定是否拦截
  • ACTION_UP ---------------必须返回false,因为ACTION_UP事件本身没有多大意义。如果父容器返回true,就会导致子元素无法接收到ACTION_UP事件,这个时候onClick事件就无法触发。
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;
    }
}    
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(),父元素才能拦截所需的事件
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);
    }
}    
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)。适用于复杂交互场景。

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


网站公告

今日签到

点亮在社区的每一天
去签到