在ViewPager中使用Android的SlidingPaneLayout

时间:2013-07-05 14:09:54

标签: android android-viewpager slidingpanelayout

我正在尝试将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滑动。当我从右边缘拉出时,它滑动很小,然后快速向后移动。

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

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

2 个答案:

答案 0 :(得分:18)

根本原因是#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());       
    }
    

  3. 通过修复ViewDragHelper,可能有一种更优雅的方式来实现这一点,但这是Google在未来支持包更新中应该解决的问题。上面的hacks现在应该使用ViewPagers(和其他水平滚动容器?)进行布局。

答案 1 :(得分:7)

基于@Brien Colwell的解决方案,我已经编写了一个SlidingPaneLayout的自定义子类来处理这个问题,并且还添加了边缘滑动,以便当用户向右滚动时,他们不会#39; t必须一直向左滚动才能打开窗格。

由于这是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);
    }
}