解决ViewPager与父ViewGrop的事件冲突

来源:互联网 发布:tim 知乎 编辑:程序博客网 时间:2024/06/03 16:08

在做公司项目的时候,遇到这样一个需求: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,所以感觉很奇怪!

0 0
原创粉丝点击