10

我正在尝试将 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 滑动。当我从右边缘拉出时,它滑动得很少,然后又弹回来。

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

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

4

2 回答 2

19

根本原因是#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(和其他水平滚动容器?)一起使用。

于 2013-07-17T01:20:43.520 回答
8

在@Brien Colwell 的解决方案的基础上,我编写了一个自定义的 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);
    }
}
于 2015-01-15T21:57:41.260 回答