Scroll behavior at RecyclerView into vertical view

2019-06-13 21:03发布

问题:

I am using VerticalViewPager and two fragments in it.

One of this fragment contains RecyclerView with just regular vertical list. The problem is that I need this list to get to end and then change page at view pager and now view pager always try to intercept RecyclerView scroll.

My VerticalViewPager class:

public class VerticalViewPager extends ViewPager {

    public VerticalViewPager(Context context) {
        super(context);
        init();
    }

    public VerticalViewPager(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    private void init() {
        setPageTransformer(true, new VerticalPageTransformer());
        setOverScrollMode(OVER_SCROLL_NEVER);
        requestDisallowInterceptTouchEvent(false);
    }

    private class VerticalPageTransformer implements ViewPager.PageTransformer {

        @Override
        public void transformPage(View view, float position) {

            if (position < -1) {
                view.setAlpha(0);

            } else if (position <= 1) {
                view.setAlpha(1);

                view.setTranslationX(view.getWidth() * -position);

                float yPosition = position * view.getHeight();
                view.setTranslationY(yPosition);

            } else {
                view.setAlpha(0);
            }
        }
    }

    private MotionEvent swapXY(MotionEvent ev) {
        float width = getWidth();
        float height = getHeight();

        float newX = (ev.getY() / height) * width;
        float newY = (ev.getX() / width) * height;

        ev.setLocation(newX, newY);

        return ev;
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        boolean intercepted = super.onInterceptTouchEvent(swapXY(ev));
        swapXY(ev);
        return intercepted;
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        return super.onTouchEvent(swapXY(ev));
    }

}

And my layout tree:

  • -Fragment
  • --ViewPager
  • ---Fragment_A
  • ----RecyclerView
  • ---Fragment_B

I already tried different combinations of

.setNestedScrollingEnabled(false/true);
.requestDisallowInterceptTouchEvent(false/true);

Tried to wrap RecyclerView with NestedScrollView, but scroll is still messy and laggy, ViewPager change its page at the list beginning or in the middle.

Thanks for your help!

回答1:

I figure it with writing custom OnTouchListener for my RecyclerView:

    public class VerticalTouchListener implements View.OnTouchListener {

        private float lastMotionY = Float.MIN_VALUE;
        private float downY = Float.MIN_VALUE;

        private final OnScrollableViewPager scrollableViewPager;

        public VerticalTouchListener(@NonNull OnScrollableViewPager scrollableViewPager) {
            this.scrollableViewPager = scrollableViewPager;
        }

        @Override
        public boolean onTouch(@NonNull View v, @NonNull MotionEvent event) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    downY = event.getRawY();
                    break;
                case MotionEvent.ACTION_MOVE:
                    if (downY == Float.MIN_VALUE && lastMotionY == Float.MIN_VALUE) {
                        downY = event.getRawY();
                        break;
                    }

                    float diff = event.getRawY() - (lastMotionY == Float.MIN_VALUE ? downY : lastMotionY);
                    lastMotionY = event.getRawY();

                    if (scrollableViewPager.getScrollX() != scrollableViewPager.getBaseScrollX()) {
                        if (fakeDragVp(v, event, diff)) return true;
                    } else {
                        if (ViewCompat.canScrollVertically(v, (-diff) > 0 ? 1 : -1)) {
                            break;
                        } else {
                            scrollableViewPager.beginFakeDrag();
                            fakeDragVp(v, event, diff);
                            adjustDownMotion(v, event);
                            return true;
                        }
                    }

                    break;
                case MotionEvent.ACTION_UP:
                case MotionEvent.ACTION_CANCEL:
                    if (scrollableViewPager.isFakeDragging()) {
                        try {
                            scrollableViewPager.endFakeDrag();
                        } catch (Exception e) {
                            e.printStackTrace();
                        }


              }
                reset();
                break;
        }

        return false;
    }

    private boolean fakeDragVp(@NonNull View v, @NonNull MotionEvent e, float diff) {
        if (scrollableViewPager.isFakeDragging()) {
            float step = diff;
            int expScrollX = (int) (scrollableViewPager.getScrollX() - step);
            if (isDiffSign(expScrollX - scrollableViewPager.getBaseScrollX(), scrollableViewPager.getScrollX() - scrollableViewPager.getBaseScrollX())) {
                step = scrollableViewPager.getScrollX() - scrollableViewPager.getBaseScrollX();
            }
            scrollableViewPager.fakeDragBy(step);
            adjustDownMotion(v, e);

            return true;
        }
        return false;
    }

    private void adjustDownMotion(@NonNull View v, @NonNull MotionEvent e) {
        MotionEvent fakeDownEvent = MotionEvent.obtain(e);
        fakeDownEvent.setAction(MotionEvent.ACTION_DOWN);
        v.dispatchTouchEvent(fakeDownEvent);
    }

    private boolean isDiffSign(float a, float b) {
        return Math.abs(a + b) < Math.abs(a - b);
    }


    private void reset() {
        downY = Float.MIN_VALUE;
        lastMotionY = Float.MIN_VALUE;
    }

    public interface OnScrollableViewPager {
        int getScrollX();

        int getBaseScrollX();

        boolean isFakeDragging();

        boolean beginFakeDrag();

        void fakeDragBy(float step);

        void endFakeDrag();
    }
}

and changed my VerticalViewPager:

    public class VerticalViewPager extends ViewPager implements VerticalTouchListener.OnScrollableViewPager {

        private int baseScrollX;

        public VerticalViewPager(@NonNull Context context) {
            super(context);
            init();
        }

        public VerticalViewPager(@NonNull Context context, @NonNull AttributeSet attrs) {
            super(context, attrs);
            init();
        }

        private void init() {
            setPageTransformer(false, new VerticalTransformer());
            setOverScrollMode(OVER_SCROLL_NEVER);
            addOnPageChangeListener(new SimpleOnPageChangeListener() {
                @Override
                public void onPageScrollStateChanged(int state) {
                    super.onPageScrollStateChanged(state);
                    if (state == ViewPager.SCROLL_STATE_IDLE) {
                        baseScrollX = getScrollX();
                    }
                }
            });
        }

        @Override
        public boolean onInterceptTouchEvent(MotionEvent event) {
            return false;
        }

        @Override
        public boolean onTouchEvent(MotionEvent ev) {
            return super.onTouchEvent(swapXY(ev));
        }

        @Override
        public int getBaseScrollX() {
            return baseScrollX;
        }

        private MotionEvent swapXY(MotionEvent ev) {
            float width = getWidth();
            float height = getHeight();

            float newX = (ev.getY() / height) * width;
            float newY = (ev.getX() / width) * height;

            ev.setLocation(newX, newY);

            return ev;
        }

        private class VerticalTransformer implements ViewPager.PageTransformer {
            @Override
            public void transformPage(@NonNull View view, float position) {
                float alpha = 0;

                if (0 <= position && position <= 1) {


               alpha = 1 - position;
            } else if (-1 < position && position < 0) {
                alpha = position + 1;
            }

            view.setAlpha(alpha);
            view.setTranslationX(view.getWidth() * -position);
            view.setTranslationY(position * view.getHeight());
        }

    }
}

This repo helped me a little bit.