Search code examples
androidandroid-viewpagerslidingpanelayout

Using Android's SlidingPaneLayout with ViewPager


I'm trying to use the SlidingPaneLayout with ViewPager, like so

<?xml version="1.0" encoding="utf-8"?>

<android.support.v4.widget.SlidingPaneLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/scientific_graph_slidingPaneLayout"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

    <!--
         The first child view becomes the left pane.
    -->

    <ListView
            android:id="@+id/left_pane"
            android:layout_width="240dp"
            android:layout_height="match_parent"
            android:layout_gravity="left" />
    <!--
         The second child becomes the right (content) pane.
    -->

    <android.support.v4.view.ViewPager
            android:id="@+id/scientific_graph_viewPager"
            android:layout_width="match_parent"
            android:layout_height="match_parent">
    </android.support.v4.view.ViewPager>

</android.support.v4.widget.SlidingPaneLayout>

The SlidingPaneLayout slides when I pull from the left edge; however, I can't seem to get the ViewPager to slide when I pull from the right edge. When I pull from the right edge, it slides very little and then snaps back.

Is doing this even possible? Is there a better way to do this?

I found that by moving my finger up and the to the left, I can swipe the view pager.


Solution

  • The root cause is the implementation of #onInterceptTouchEvent. An older implementation of SlidingPaneLayout made a call to #canScroll, which would check if the touch target could scroll, and if so, would scroll the touch target instead of sliding the panel. The most recent implementation looks like it always intercepts the motion event, once the drag threshold exceeds the slop, except in the case where the X drag exceeds the slop and the Y drag exceeds the X drag (as noted by the OP).

    One solution to this is to copy SlidingPaneLayout and make a few changes to get this to work. Those changes are:

    1. Modify the ACTION_MOVE case in #onInterceptTouchEvent to also check #canScroll,

      if (adx > slop && ady > adx || 
          canScroll(this, false, Math.round(x - mInitialMotionX), Math.round(x), Math.round(y)))
      { ... }
      
    2. Modify the final check in #canScroll to special case ViewPager. This modification could also be done in a subclass by overriding #canScroll, since it doesn't access any private state.

      protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) {
          ...
          /* special case ViewPagers, which don't properly implement the scrolling interface */
          return checkV && (ViewCompat.canScrollHorizontally(v, -dx) ||
              ((v instanceof ViewPager) && canViewPagerScrollHorizontally((ViewPager) v, -dx)))
      }
      
      boolean canViewPagerScrollHorizontally(ViewPager p, int dx) {
          return !(dx < 0 && p.getCurrentItem() <= 0 ||
              0 < dx && p.getAdapter().getCount() - 1 <= p.getCurrentItem());       
      }
      

    There is likely a more elegant way to do this by fixing the ViewDragHelper, but this is something Google should address in a future update of the support package. The hacks above should get the layout working with ViewPagers (and other horizontally scrolling containers?) now.