12

I have a ViewPager on the root-level of an activity.

Each page of the pager contains a ListFragment (backed by a FragmentPagerAdapter).

Some of the list view items should contain additionally ViewPagers to support swiping the content of those items (e. g. a horizontal gallery inside a list item).

How can I nest view pagers? ViewPager -> ListView (in a page) -> ViewPager (inside a list item)

I can swipe between the ListFragments horizontally and I can swipe the whole list vertically, but I cannot swipe inside list items.

Kevin Vermeer
  • 2,736
  • 2
  • 27
  • 38
Thorsten
  • 151
  • 1
  • 5

3 Answers3

11

I added an OnTouchListener to the interior ViewPager:

private OnTouchListener mSuppressInterceptListener = new OnTouchListener() {

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        if(
                event.getAction() == MotionEvent.ACTION_DOWN &&
                v instanceof ViewGroup
        ) {
                ((ViewGroup) v).requestDisallowInterceptTouchEvent(true);
        }
        return false;
    }
};

This just detects ACTION_DOWN touch events on the inner ViewPager and prevents the outer one from intercepting it. Because it returns false, only the ACTION_DOWN event should be hit; all the other events will be ignored. You can add this listener to every element you want to "protect" from the outer ViewPager's scrolling, though obviously if you want to pick up any other touch behaviour on those elements you'll need to deal with them inside the touch listener and possibly implement a better listener.

Credit to @Rodja who gave me the idea in the first place.

Andrew Wyld
  • 7,133
  • 7
  • 54
  • 96
  • 2
    Wow. Much cleaner. Thanks! – Rodja May 13 '13 at 17:17
  • By far the most easy solution. – OcuS May 28 '13 at 12:58
  • 1
    Just can't believe how easily it works. Much thanks, Andrew and Rodja! – Aman Alam Feb 15 '14 at 19:46
  • I am facing similar problem, this allow me to swipe left and right. What if I also want to enable on tap then open the selected view? – Eric Aug 12 '14 at 09:10
  • You can write your own click listener by listening for ACTION_DOWN followed by ACTION_UP; if the sum of all ACTION_MOVE events in between is less than the system **touch slop** (see below) call it a click. http://developer.android.com/reference/android/view/ViewConfiguration.html has constants for touch slop, long press and generally whatever you need. – Andrew Wyld Aug 12 '14 at 10:28
  • I had a follow up question on this... What if we disable the paging on the parent viewpager (by calling setPagingEnabled(false)) and use the PagerTabStrip to navigate in it. Would the child/nested ViewPager still receive the touch event? – Vaibhav Singhal Apr 15 '15 at 14:22
3

While it's not the best interaction design, it is possible to implement this by overwriting the dispatchTouchEvent(MotionEvent ev) method of the root-level Activity and using requestDisallowInterceptTouchEvent(true) on the mainPager and the current ListView to prevent other scrolling. Look at this example:

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {

    Fragment listFragment = getSupportFragmentManager().findFragmentByTag(
            "android:switcher:" + R.id.pager + ":" + (mainPager.getCurrentItem()));
    mainPager.getChildAt(mainPager.getCurrentItem());
    if (listFragment == null)
        return super.dispatchTouchEvent(ev);
    ViewPager embeddedPager = (ViewPager) listFragment.getView().findViewById(R.id.videopager);
    if (embeddedPager != null) {
        int[] position = new int[2];
        embeddedPager.getLocationOnScreen(position);
        if (ev.getY() > position[1] && ev.getY() < position[1] + embeddedPager.getHeight()) {
            mainPager.requestDisallowInterceptTouchEvent(true);
            if (embeddedPager.getScrollX() % embeddedPager.getWidth() != 0) {
                ListView listView = (ListView) listFragment.getView().findViewById(
                        android.R.id.list);
                listView.requestDisallowInterceptTouchEvent(true);
            }
        }
    }

    return super.dispatchTouchEvent(ev);
}
Rodja
  • 7,998
  • 8
  • 48
  • 55
  • Thanks for this—I modified the idea slightly to let you put the code in elsewhere than the `Activity#dispatchTouchEvent(MotionEvent)` function but the idea I used was basically yours :) – Andrew Wyld May 08 '13 at 18:46
0

You can't really nest elements that need the same gestures to control them. Since the view pager is already capturing the horizontal motion, your nested elements will not get it. You could probably do a lot of work to get around this by managing focus and the like - but in the end your app will be confusing for users. Its really better to not nest elements that would use the same interaction... in this case two view pagers both watching for a side to side motion.

Matthew Runo
  • 1,387
  • 3
  • 20
  • 43