解决ViewPager与父ViewGrop的事件冲突

在做公司项目的时候,遇到这样一个需求:activity最下层是一个地图控件,可以从下方划出一个layout占据下半个屏幕,也可以下滑收起状态(占1/5屏幕),该layout用于文字显示一条路线,可以左右滑动,切换不同路线(类似画廊效果),展开和收起状态都可以左右滑动,切换后地图上显示对应的路线。
我的思路是自定义一个layout,实现滑动展开和收起,这个简单,不一会就敲了一个demo:

public class SlideLayout extends RelativeLayout implements GestureDetector.OnGestureListener {

    private Scroller scroller;
    private GestureDetector detector;
    private int height;//此layout的高度
    private int up;//展开的scrollY值
    private int down;//收起的scrollY值
    private int middle;//中间scrollY值

    public SlideLayout(Context context) {
        this(context, null);
    }

    public SlideLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public SlideLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    private void init(Context context) {
        scroller = new Scroller(context);
        detector = new GestureDetector(context, this);
        getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                getViewTreeObserver().removeOnGlobalLayoutListener(this);
                height = getHeight();
                up = 0;
                down = -height * 2 / 3;
                middle = (up + down) / 2;
                Log.i("tag", "init");
            }
        });
    }

    public int dp2px(Context context, float dp) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dp * scale + 0.5f);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        //事件交给GestureDetector处理
        return detector.onTouchEvent(event);
    }

    @Override
    public void computeScroll() {
        if (scroller.computeScrollOffset()) {
            scrollTo(scroller.getCurrX(), scroller.getCurrY());
            invalidate();
        }
    }

    @Override
    public boolean onDown(MotionEvent e) {
        return e.getY() >= Math.abs(getScrollY());
    }

    @Override
    public void onShowPress(MotionEvent e) {

    }

    @Override
    public boolean onSingleTapUp(MotionEvent e) {
        return false;
    }

    @Override
    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
//        Log.i("tag", "onScroll:" + distanceY);
        if (getScrollY() > up || getScrollY() < down) {
            return false;
        }
        scrollBy(0, Math.round(distanceY * 0.5f));
        return true;
    }

    @Override
    public void onLongPress(MotionEvent e) {

    }

    @Override
    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
        Log.i("tag", "onFling:" + velocityY);
        //确定滑动方向
        int target;
        if (velocityY > 500) {
            target = down;
        } else if (velocityY < -500) {
            target = up;
        } else {
            if (e2.getY() > Math.abs(middle)) {
                target = down;
            } else {
                target = up;
            }
        }
        //开始滑动
        smoothScrollTo(target);
        return true;
    }

    private void smoothScrollTo(int target) {
        scroller.startScroll(0, getScrollY(), 0, target - getScrollY());
        invalidate();
    }
}

测试了下,非常不错,已经实现了滑动展开和收起的效果,接下来只要在里面放一个ViewPager就可以了~
马上又写了一个放进去,结果发现layout的滑动效果没了,只有ViewPager响应了事件,想到可能是ViewPager抢夺了事件,于是马上自定义一个ViewPager,只消耗水平方向的时间,仍然没有效果。
奇怪了,不应该啊!
断点看了一下VIewPager的onTouchEvent方法,发现如果是竖直方向的事件并没有消耗,确实返回false!又看下SlideLayout的onTouchEvent方法没有执行,说明没有接收到事件。
尼玛!我的事件被谁吃了!按理说子View的onTouchEvent返回false,应该会把事件还给父View,执行父View的onTouchEvent才对啊!
静下几分钟后,既然ViewPager没有把事件还给SlideLayout,那我就在SlideLayout中拦截合适的事件,于是有了下面的改动:

public class SlideLayout extends RelativeLayout implements GestureDetector.OnGestureListener {

    private Scroller scroller;
    private GestureDetector detector;
    private int height;//此layout的高度
    private int up;//展开的scrollY值
    private int down;//收起的scrollY值
    private int middle;//中间scrollY值
    private int slideHead;//可以触发此layout滑动的高度

    public SlideLayout(Context context) {
        this(context, null);
    }

    public SlideLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public SlideLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    private void init(Context context) {
        scroller = new Scroller(context);
        detector = new GestureDetector(context, this);
        getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                getViewTreeObserver().removeOnGlobalLayoutListener(this);
                height = getHeight();
                up = 0;
                down = -height * 2 / 3;
                middle = (up + down) / 2;
                slideHead = dp2px(getContext(), 100);
                Log.i("tag", "init");
            }
        });
    }

    public int dp2px(Context context, float dp) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dp * scale + 0.5f);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            int abs = Math.abs(getScrollY());
            //如果down的位置在在设定范围内则直接拦截事件
            if (ev.getY() < abs + slideHead && ev.getY() > abs) {
                Log.i("tag", "intercept");
                return true;
            }
        }
        return super.onInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        //事件交给GestureDetector处理
        return detector.onTouchEvent(event);
    }

    @Override
    public void computeScroll() {
        if (scroller.computeScrollOffset()) {
            scrollTo(scroller.getCurrX(), scroller.getCurrY());
            invalidate();
        }
    }

    @Override
    public boolean onDown(MotionEvent e) {
        return e.getY() >= Math.abs(getScrollY());
    }

    @Override
    public void onShowPress(MotionEvent e) {

    }

    @Override
    public boolean onSingleTapUp(MotionEvent e) {
        return false;
    }

    @Override
    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
//        Log.i("tag", "onScroll:" + distanceY);
        if (getScrollY() > up || getScrollY() < down) {
            return false;
        }
        scrollBy(0, Math.round(distanceY * 0.5f));
        return true;
    }

    @Override
    public void onLongPress(MotionEvent e) {

    }

    @Override
    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
        Log.i("tag", "onFling:" + velocityY);
        //确定滑动方向
        int target;
        if (velocityY > 500) {
            target = down;
        } else if (velocityY < -500) {
            target = up;
        } else {
            if (e2.getY() > Math.abs(middle)) {
                target = down;
            } else {
                target = up;
            }
        }
        //开始滑动
        smoothScrollTo(target);
        return true;
    }

    private void smoothScrollTo(int target) {
        scroller.startScroll(0, getScrollY(), 0, target - getScrollY());
        invalidate();
    }
}

我设定一个范围,只要在范围的事件就会触发layout的滑动,而范围外的事件则分发给ViewPager去消耗。虽然实现业务的需求,但是我始终没有明白为什么ViewPager消耗了它不需要的事件,不知道哪位大神可以告诉我原因?

ps:一个小问题onFling方法是必然执行的吗?(onDown返回了true)我在测试的时候发现,在一个测试机中onFling是必然触发的(无论是多慢地离开手指),而在另一个测试机上,慢慢滑动后手指离开屏幕不会触发onFling,所以感觉很奇怪!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值