2013-07-05 50 views
9

我试图使用与ViewPager的SlidingPaneLayout,像这样使用Android的SlidingPaneLayout与ViewPager

<?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> 

当我从左边拉SlidingPaneLayout滑动;然而,当我从右边拉时,我似乎无法让ViewPager滑动。当我从右边缘拉出时,它滑动很小,然后折回。

这样做甚至有可能吗?有一个更好的方法吗?

我发现通过将手指向上移动到左侧,我可以滑动视图寻呼机。

+0

我一直在尝试做同样的事情。有没有人知道如何使滑动窗格只在从边缘拉出时打开?所以如果不从边缘拉出,viewpager会滑动。 – Zul

回答

17

根本原因是#onInterceptTouchEvent的实现。较老的SlidingPaneLayout实现调用#canScroll,它将检查触摸目标是否可以滚动,如果是,则滚动触摸目标而不是滑动面板。除了X拖动超过坡度并且Y拖动超过X拖动(如OP所指出的情况)之外,最近的实施看起来像它总是拦截运动事件,一旦拖动阈值超过坡度。

对此的一个解决方案是复制SlidingPaneLayout并进行一些更改以使其起作用。这些变化包括:

  1. 修改#onInterceptTouchEvent的ACTION_MOVE情况下,还检查#canScroll,

    if (adx > slop && ady > adx || 
        canScroll(this, false, Math.round(x - mInitialMotionX), Math.round(x), Math.round(y))) 
    { ... } 
    
  2. 修改#canScroll特殊情况下ViewPager最后检查。此修改也可以通过覆盖#canScroll在子类中完成,因为它不访问任何私有状态。

    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());  
    } 
    

有可能是一个更优雅的方式通过固定ViewDragHelper要做到这一点,但是这是谷歌应该在支持包的更新版本解决。上面的黑客应该可以使用ViewPagers(和其他水平滚动容器?)进行布局。

+0

开箱即用!谢谢! –

+0

非常好的答案!谢谢! – hooloovoo

7

建立@Brien科尔韦尔的解决方案,我写了一个自定义的SlidingPaneLayout的子类来处理这个问题,并且还添加了边缘滑动,这样当用户向右滚动时,他们不必滚动一直回到左侧以打开窗格。

由于这是SlidingPaneLayout的子类,因此不需要在Java中更改任何引用,只需确保实例化此类的实例(通常在您的XML中)。

package com.ryanharter.android.view; 

import android.content.Context; 
import android.support.v4.view.MotionEventCompat; 
import android.support.v4.widget.SlidingPaneLayout; 
import android.util.AttributeSet; 
import android.view.MotionEvent; 
import android.view.ViewConfiguration; 

/** 
* SlidingPaneLayout that, if closed, checks if children can scroll before it intercepts 
* touch events. This allows it to contain horizontally scrollable children without 
* intercepting all of their touches. 
* 
* To handle cases where the user is scrolled very far to the right, but should still be 
* able to open the pane without the need to scroll all the way back to the start, this 
* view also adds edge touch detection, so it will intercept edge swipes to open the pane. 
*/ 
public class PagerEnabledSlidingPaneLayout extends SlidingPaneLayout { 

    private float mInitialMotionX; 
    private float mInitialMotionY; 
    private float mEdgeSlop; 

    public PagerEnabledSlidingPaneLayout(Context context) { 
     this(context, null); 
    } 

    public PagerEnabledSlidingPaneLayout(Context context, AttributeSet attrs) { 
     this(context, attrs, 0); 
    } 

    public PagerEnabledSlidingPaneLayout(Context context, AttributeSet attrs, int defStyle) { 
     super(context, attrs, defStyle); 

     ViewConfiguration config = ViewConfiguration.get(context); 
     mEdgeSlop = config.getScaledEdgeSlop(); 
    } 

    @Override 
    public boolean onInterceptTouchEvent(MotionEvent ev) { 

     switch (MotionEventCompat.getActionMasked(ev)) { 
      case MotionEvent.ACTION_DOWN: { 
       mInitialMotionX = ev.getX(); 
       mInitialMotionY = ev.getY(); 
       break; 
      } 

      case MotionEvent.ACTION_MOVE: { 
       final float x = ev.getX(); 
       final float y = ev.getY(); 
       // The user should always be able to "close" the pane, so we only check 
       // for child scrollability if the pane is currently closed. 
       if (mInitialMotionX > mEdgeSlop && !isOpen() && canScroll(this, false, 
         Math.round(x - mInitialMotionX), Math.round(x), Math.round(y))) { 

        // How do we set super.mIsUnableToDrag = true? 

        // send the parent a cancel event 
        MotionEvent cancelEvent = MotionEvent.obtain(ev); 
        cancelEvent.setAction(MotionEvent.ACTION_CANCEL); 
        return super.onInterceptTouchEvent(cancelEvent); 
       } 
      } 
     } 

     return super.onInterceptTouchEvent(ev); 
    } 
} 
+0

这段代码太神奇了! – Gilian