ViewPager+Fragment组合与HorizontalScrollVIew滑动冲突解决

Demo地址

  1. 重写HorizontalScrollView中onTouchEvent(),也可以重写它的dispatchTouchEvent(),但是重写dispatchTouchEvent()最后发现ViewPager页面切换的时候会出现页面跳跃.
  2. 重写HorizontalScrollView中onTouchEvent()会存在一个问题,就是当手指从ViewPager边界划入时候,ViewPager会将事件直接拦截下来,不会分发到子View中,假如在ViewPager中去判断Fragment中HorizontalScrollView的状态是否允许滑动,又会出现页面切换是屏幕跳跃的现象.
  3. 重写ViewPager中的dispatchTouchEvent(),判断DOWN事件的坐标点,如果太过靠近ViewPager两个侧边,dispatchTouchEvent()直接返回false,拒绝此次事件组.这样会稍微的好一点.
代码
  • 自定义ViewPager类
public class YeyViewPager extends ViewPager {
    // ViewPager 宽度
    private int mWidthSize;
    ...
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        mWidthSize = this.getMeasuredWidth();
    }
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            // 如果Down事件的坐标点太过靠近ViewPager侧边,则ViewPager不消耗该次事件组.
            // 这个用来解决侧边滑入ViewPager时,ViewPager直接拦截该事件组,不向下分发事件到子View.
            // 导致HorizontalScrollView无法随手指进行移动的bug.
            float x = ev.getX();
            if (x < mWidthSize * 0.04 || x > mWidthSize * 0.96) {
                // 该事件直接返回不消费,这次事件组就无法再被分发到ViewPager中来了.
                return false;
            }
        }
        return super.dispatchTouchEvent(ev);
    }
}
  • 自定义HorizontalScrollView类
public class YeyHorizontalScrollView extends HorizontalScrollView {
    private static final String TAG = YeyHorizontalScrollView.class.getName();
    private int childWidth;// HorizontalScrollView唯一子控件的宽度
    private int yeyHorizontalScrollViewWidth;//HorizontalScrollViewWidth的实际宽度
    ...
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        View mChildView = this.getChildAt(0);
        // YeyHorizontalScrollView中唯一子控件宽度
        childWidth = mChildView.getMeasuredWidth();
        // YeyHorizontalScrollView当前宽度
        yeyHorizontalScrollViewWidth = this.getMeasuredWidth();
    }


    /**
     * onInterceptTouchEvent(),该方法在ViewGroup中被调用得满足三个条件:
     * 1. 事件需要是ACTION_DOWN事件.
     * 2. mFirstTouchTarget != null
     * 3. disallowIntercept = false
     * 当手指触碰到屏幕后,假如YeyHorizontalScrollView中的子View消耗了ACTION_DOWN事件,那么mFirstTouchTarget就被赋值会不等于null.
     * 如果后续的ACTION_MOVE事件被YeyHorizontalScrollView拦截,那么YeyHorizontalScrollView中的子View将首先收到一个MotionEvent.ACTION_CANCLE事件.
     * 然后YeyHorizontalScrollView中会将mFirstTouchTarget设置为null,那么当下一次ACTION_MOVE事件来之时onInterceptTouchEvent()将不会被调用(因为mFirstTouchTarget=null),
     * 然后该ACTION_MOVE事件会被YeyHorizontalScrollView消耗.此后该事件组中的事件YeyHorizontalScrollView中的子View将再也接收不到了.
     *
     * @param
     * @return
     */
    private float interceptLastX, interceptLastY;

    // 是否拦截该事件分发到子View中.比如ReyclerView.
    @Override
    public boolean onInterceptTouchEvent(MotionEvent e) {
        // 调用父类HorizontalScrollView.onInterceptTouchEvent(),该方法中不光做了判断事件是否拦截, 还对HorizontalScrollView其他的变量做了改变,
        // 在子类这里调用父类onInterceptTouchEvent()目的是减小对原有HorizontalScrollView影响,也是复用HorizontalScrollView中的事件拦截机制.
        super.onInterceptTouchEvent(e);
        boolean interceptChildeEvent = false;
        switch (e.getAction()) {
            case MotionEvent.ACTION_DOWN:
                interceptLastX = e.getX();
                interceptLastY = e.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                // 获取当前手指坐标
                float moveX = e.getX();
                float moveY = e.getY();
                // 移动后,计算出滑动后与上一个坐标点之间的距离.
                float slopX = moveX - interceptLastX;
                float slopY = moveY - interceptLastY;
                // 得到手指滑动距离绝对值
                float slopAbsX = Math.abs(slopX);
                float slopAbsY = Math.abs(slopY);
                if ((slopAbsX > 0 || slopAbsY > 0) && (slopAbsX - slopAbsY) >= 6) {
                    // 如果手指移动距离大于0,且横向移动距离减去纵向移动距离大于6像素
                    // 那么YeyHorizontalScrollView就将该事件拦截, 不分发给它的子View使用,留给自己使用了.
                    // 这样会导致mFirstTouchTarget=null,之后子View就再也接收不到事件组的其他事件了.
                    interceptChildeEvent = true;
                }
                interceptLastX = moveX;
                interceptLastY = moveY;
                break;
        }
        return interceptChildeEvent;
    }
    
    /**
     * 按照HorizontalScrollView是否可以向左滑动或者向右滑动的条件,请求父容器是否拦截剩下的事件.
     * @param e
     * @return
     */
    @Override
    public boolean onTouchEvent(MotionEvent e) {
        // 调用父类HorizontalScrollView.onTouchEvent(),该方法中不光做了判断事件是否拦截, 还做了其他的事情,比如移动HorizontalScrollView中的子控件.
        // 在子类这里调用父类onTouchEvent目的是减小对原有HorizontalScrollView影响.
        boolean disallowIntercepet = super.onTouchEvent(e);
        switch (e.getAction()) {
            case MotionEvent.ACTION_MOVE:
                // getScrollX() == 0:YeyHorizontalScrollView内容左边与YeyHorizontalScrollView控件左边距离为0,也就是YeyHorizontalScrollView状态无法再向右方滑动了.
                // 那么此时YeyHorizontalScrollView将不需要事件,请求父控件拦截事件,将事件交由父控件处理.
                if (getScrollX() <= 0) {
                    disallowIntercepet = false;// 拦截
                }
                // getScrollX() + scrollViewWidth:YeyHorizontalScrollView内容左边与YeyHorizontalScrollView控件左边距离大小加上YeyHorizontalScrollView控件本身宽度,
                // 这个值应该等于YeyHorizontalScrollView唯一子View的宽度.
                // childWidth <=(getScrollX() + yeyHorizontalScrollViewWidth):这个条件的意思就是YeyHorizontalScrollView状态无法再向左方滑动了.
                // 此时YeyHorizontalScrollView将不需要事件,请求父控件拦截事件,将事件交由父控件处理.
                if (((getScrollX() + yeyHorizontalScrollViewWidth)) >= childWidth) {
                    disallowIntercepet = false;// 拦截
                }
                // 这里是当YeyHorizontalScrollView既可以向左边滑动又可以向右侧滑动时,让父控件别拦截事件。
                if (getScrollX() > 0 && ((getScrollX() + yeyHorizontalScrollViewWidth) < childWidth)) {
                    disallowIntercepet = true;// 不拦截
                }
                break;
        }
        if (disallowIntercepet) {
            // 不拦截
            this.getParent().requestDisallowInterceptTouchEvent(true);
        } else {
            // 拦截
            this.getParent().requestDisallowInterceptTouchEvent(false);
        }
        return disallowIntercepet;
    }

     /**
     * 该方法中也可以做请求父控件不要拦截的操作,但这里实现会导致两个页面切换的时候新出来的界面会跳跃.
     * @param e
     * @return
     */
//    @Override
//    public boolean dispatchTouchEvent(MotionEvent e) {
//        boolean disallowIntercepet = super.dispatchTouchEvent(e);
//        switch (e.getAction()) {
//            case MotionEvent.ACTION_MOVE:
//                // getScrollX() == 0:YeyHorizontalScrollView内容左边与YeyHorizontalScrollView控件左边距离为0,也就是YeyHorizontalScrollView状态无法再向右方滑动了.
//                // 那么此时YeyHorizontalScrollView将不需要事件,请求父控件拦截事件,将事件交由父控件处理.
//                if (getScrollX() <= 0) {
//                    disallowIntercepet = false;// 拦截
//                }
//                // getScrollX() + scrollViewWidth:YeyHorizontalScrollView内容左边与YeyHorizontalScrollView控件左边距离大小加上YeyHorizontalScrollView控件本身宽度,
//                // 这个值应该等于YeyHorizontalScrollView唯一子View的宽度.
//                // childWidth <=(getScrollX() + yeyHorizontalScrollViewWidth):这个条件的意思就是YeyHorizontalScrollView状态无法再向左方滑动了.
//                // 此时YeyHorizontalScrollView将不需要事件,请求父控件拦截事件,将事件交由父控件处理.
//                if (((getScrollX() + yeyHorizontalScrollViewWidth)) >= childWidth) {
//                    disallowIntercepet = false;// 拦截
//                }
//
//                if (getScrollX() > 0 && ((getScrollX() + yeyHorizontalScrollViewWidth) < childWidth)) {
//                    disallowIntercepet = true;// 不拦截
//                }
//                break;
//            default:
//        }
//        if (disallowIntercepet) {
//            // 不拦截
//            this.getParent().requestDisallowInterceptTouchEvent(true);
//        } else {
//            // 拦截
//            this.getParent().requestDisallowInterceptTouchEvent(false);
//        }
//        return disallowIntercepet;
//    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值