简体   繁体   中英

Scroll Vertical ViewPager with ListView inside

I'm using castorflex/VerticalViewPager library which contains fragments with ListView inside. The problem is, that I can't change page of ViewPager, when I start swiping over the ListView element. Is there any way to pass touch event when ListView reach top/bottom of list to the ViewPager? I wan't to achieve smooth effect (when list cant be scrolled anymore, ViewPager starts scrolling, all in one touch event)

Here's my solution for the Vertical Pager class implemented here

Instead of a RecyclerView implement a custom class that extends it, and inside our custom RecyclerView Class override public boolean onTouchEvent(MotionEvent event)

Here we can pass the MotionEvent event into a class like follows to determine if we should intercept the event:

/**
 * Tests the MotionEvent for valid behavior to care about overriding the touch event with
 */
private boolean shouldDisableScrollableParent(final MotionEvent event) {
    boolean intercept = false;
    float x = event.getX();
    float y = event.getY();

    switch (event.getAction()) {
        case MotionEvent.ACTION_MOVE:

            float dx = x - mPreviousX;
            float dy = y - mPreviousY;

            // Here you can try to detect the swipe. It will be necessary to
            // store more than the previous value to check that the user move constantly in the same direction
            intercept = detectSwipe(dx, dy);
            break;

        case MotionEvent.ACTION_UP:
        case MotionEvent.ACTION_CANCEL:
        default:
            break;
    }

    mPreviousX = x;
    mPreviousY = y;

    return intercept;
}

/**
 * Tests if we want to disable the parent touch interception
 */
private boolean detectSwipe(final float dx, final float dy) {
    // True if we're scrolling in Y direction
    if (Math.abs(dx) < Math.abs(dy)) {
        FoundationLog.d(TAG, "Direction Y is yielding: " + dy);

        if (dy < 0) {
            // Touch from lower to higher
            return true;
        }

        // Top view isn't shown, can always scroll up
        final View view = getChildAt(0);
        if (view == null) {
            return true;
        }

        // Top view baseline doesn't equal to top of the RecyclerView
        if (view.getTop() < 0) {
            return true;
        }
    }

    return false;
}

Then in the public boolean onTouchEvent(MotionEvent event) method of the custom RecyclerView class, check to see if getParent() (and possibly iteratively so to the root parent) resolves to the ViewParent of the class of the View you want to override the touches from. If so, set requestDisallowInterceptTouchEvent() to true on that ViewParent

This is a bit of a late reply but I was recently asked to change our apps ViewPager to vertical and I had exactly the same problem with my list inside. I hope this helps someone else who might have the same problem.

Firstly, my Fragment was extension of ListFragment but it works the same way for any Fragment containing a list (with minor modification obviously). Firstly, I set an OnScrollListener on my list and in onScrollStateChanged I had a method updateListVisibility which called back to my Activity updating if the top of the top or the bottom of the bottom were visible in the list (updateListPositionVisibility).

public class MyListFragment extends ListFragment {

   .....
   private OnScrollStateChangedListener mListener;
   .....

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {

        super.onViewCreated(view, savedInstanceState);
        ......
        getListView().setOnScrollListener(new AbsListView.OnScrollListener() {

            @Override
            public void onScrollStateChanged(AbsListView view, int scrollState) {
                updateListVisibility();
            }

            @Override
            public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {

            }
        });
    }

    public void updateListVisibility() {

        if (getListView() != null && getListView().getChildCount() > 0) {

            boolean firstItemVisible = getListView().getFirstVisiblePosition() == 0;
            boolean topOfFirstItemVisible = getListView().getChildAt(0).getTop() == 0;

            boolean lastItemVisible = getListView().getLastVisiblePosition() == getListView().getAdapter().getCount() - 1;
            boolean bottomOfLastItemVisible = getListView().getChildAt(getListView().getChildCount() - 1).getBottom() <= getListView().getHeight();

            mListener.updateListPositionVisibility(firstItemVisible && topOfFirstItemVisible, lastItemVisible && bottomOfLastItemVisible);
        }
    }

    @Override   
    public void onAttach(Activity activity) {

        super.onAttach(activity);
        try {
            mListener = (OnScrollStateChangedListener) activity;
        } catch (ClassCastException e) {
            throw new ClassCastException(activity.toString() + " must implement OnScrollStateChangedListener");
        }
    }


    /**
     * Container Activity must implement this interface....
     */
    public interface OnScrollStateChangedListener {

        public void updateListPositionVisibility(boolean topIsVisible, boolean bottomIsVisible);
    }

    .....
}

In my activity I set an OnPageChangeListener on my ViewPager and in onPageSelected I used my adapter to get the Fragment so that I could call updateListVisibility on it which then called back to my activity. My callback implementation updated the ViewPager with the visibility status of the top and bottom of the list (see setListViewPositionsInViewPager).

public class VerticallyPagedActivity extends BaseFragmentActivity implements
    MyListFragment.OnScrollStateChangedListener {

    @Override
    protected void onCreate(Bundle savedInstanceState) {

        ......

        mPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
        @Override
        public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {

        }

        @Override
        public void onPageSelected(int position) {
            Object fragment = mAdapter.mItems.get(position);
            if (fragment instanceof MyListFragment) {
                ((MyListFragment) fragment).updateListVisibility();
            } else {
                setListViewPositionsInViewPager(false, false);
            }
        }

        @Override
        public void onPageScrollStateChanged(int state) {

        }
    });
}

public void setListViewPositionsInViewPager(boolean handleEventTop, boolean handleEventBottom) {
    mPager.setListVisibility(handleEventTop, handleEventBottom);
}

// Callback.
@Override
public void updateListPositionVisibility(boolean topIsVisible, boolean bottomIsVisible) {
    setListViewPositionsInViewPager(topIsVisible, bottomIsVisible);
}

My Adapter simply maintains an instance of the fragment in mItems.

public class MyAdapter extends FragmentStatePagerAdapter {

    .....
    public Map<Integer, Fragment> mItems;

    public MyAdapter(FragmentManager fm, List<Content> content) {
    super(fm);
    mItems = new HashMap<>();
    .....

    @Override
    public Fragment getItem(int position) {

        .....   
        MyListFragment fragment = new MyListFragment();
        .....
        mItems.put(position, fragment);
        return fragment;
    }

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        mItems.remove(position);
        super.destroyItem(container, position, object);
    }
}

Then in my Castorflex vertical view pager I set the list visibility values using a method called setListVisibility. These are updated every time the list is scrolled or the ViewPager is paged meaning they're always up to date. The most important part is then for the pager to intercept the events when either the top was visible and the user attempted to go up or the bottom was visible and the user attempted to go down. This is easily achived using the dy value that is calculated inside the switch statement under MotionEvent.ACTION_MOVE.

public class VerticalPager extends ViewGroup {

    .....
    private boolean topOfListIsVisible;
    private boolean bottomOfListIsVisible;

    public void setListVisibility(boolean pTop, boolean pBottom) {
        topOfListIsVisible = pTop;
        bottomOfListIsVisible = pBottom;
    }

    .....

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {

        ......

        switch (action) {
            case MotionEvent.ACTION_MOVE: {

            ......
            final float y = MotionEventCompat.getY(ev, pointerIndex);
            final float dy = y - mLastMotionY;
            final float yDiff = Math.abs(dy);
            ......

            // Pager needs to handle the event.
            if (topOfListIsVisible && bottomOfListIsVisible || bottomOfListIsVisible && dy < 0 || topOfListIsVisible && dy > 0) {
                return true;
            }

        }
        .....
    }

    .....
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM