5

我有一个SlidingUpPanelLayout将图像保存为顶视图,以及一个需要滑动的视图寻呼机。有 3 个片段,其中viewpager两个是列表视图。所以我希望能够在拉起时扩展视图寻呼机,一旦视图寻呼机启动,我希望能够滚动scrollviews片段内部。但是当下拉scrollview以防万一没有更多滚动时,我想开始折叠viewpager. 所以请建议如何SlidingUpPanelLayout在没有更多内容滚动的情况下拉动滚动视图时折叠?

在这里,我发布了一些代码:我尝试onInterceptTouchEvent通过以下方式捕获触摸事件并覆盖 SlidingUpPanel 函数:

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    if (isHandled) {
        Log.i("interceptToch", "HEREEE");
        return onTouchEvent(ev);
    }
    return false;
}

因此,当 SlidingUpPanelLayout 展开时,我设置了isHandled = false. 所以当slidingUpPanelLayout 展开时,所有的触摸事件都会传递给它的子视图。

我还onTouchEvent输入了scrollView, 以解除阻塞SlidingUpPanelLayout.onInterceptTouchEvent

public boolean onTouch(View v, MotionEvent event) {
    int action = event.getAction();
    if (action == MotionEvent.ACTION_DOWN) {
        scroll = 0;
        y = event.getY();
    } else if (action == MotionEvent.ACTION_MOVE) {
        if (scroll_view_summary.getScrollY() == 0 && event.getY() > y) {
            scroll = scroll + 1;
            if (scroll > 2) {
                // the user has pulled the list and the slidingUpPanelLauout 
                // should be able to handle the toch events again
                SlidingUpPanelLayoutCustom las = 
                    ((SaleDetailsActivity) getActivity()).getLayout();
                las.setHandle(true);
                scroll = 0;
                return false;
            }
        }
    }
    return false;
}

但这不起作用。问题是一旦scrollview.onTouch事件发生MotionEvent.ACTION_MOVE SlidingUpPanelLayout.onInterceptTouchEvent就不会被调用。SlidingUpPanelLayout.onInterceptTouchEvent之后调用MotionEvent.ACTION_CANCEL。这意味着事件不能传递给 SlidingUpPanelLayout 并且面板不能折叠。

4

7 回答 7

15

onInterceptTouchEvent不幸的是,由于上述原因,您不能依赖 SlidingUpPanelLayout 的方法。一旦子视图的onTouchEvent方法返回true,onInterceptTouchEvent就不再被调用。

我的解决方案有点复杂,但它可以让您准确地实现(我认为)您正在寻找的东西。单个触摸/拖动事件会将面板拖动到位,一旦到位,继续滚动子视图。同样,向下拖动时,单个触摸/拖动事件可以滚动子视图,一旦完全滚动,将开始向下拖动面板。

更新 2015-04-12更新到 SlidingUpPanelLayout 代码的 3.0.0 版。还考虑 ListViews 而不仅仅是 ScrollViews。

1)res/SlidingUpPanel的库项目文件夹中,打开attrs.xml并添加

<attr name="scrollView" format="reference" />

您将使用它来识别单个子视图,一旦面板被拖动到位,它将篡夺触摸事件。在您的布局 xml 文件中,您可以添加

sothree:scrollView="@+id/myScrollView"

或者你的 scrollView 的 ID 是什么。还要确保你没有声明一个sothree:dragViewID,所以整个视图都是可拖动的。

其余步骤均在SlidingUpPanelLayout.java...内完成

2) 声明以下变量:

View mScrollView;
int mScrollViewResId = -1;
boolean isChildHandlingTouch = false;
float mPrevMotionX;
float mPrevMotionY;

3)在构造函数中,在mDragViewResId设置之后,添加以下行:

mScrollViewResId = ta.getResourceId(R.styleable.SlidingUpPanelLayout_scrollView, -1);

4)onFinishInflate中,添加以下代码:

if (mScrollViewResId != -1) {
    mScrollView = findViewById(mScrollViewResId);
}

5) 添加以下方法:

private boolean isScrollViewUnder(int x, int y) {
    if (mScrollView == null)
        return false;

    int[] viewLocation = new int[2];
    mScrollView.getLocationOnScreen(viewLocation);
    int[] parentLocation = new int[2];
    this.getLocationOnScreen(parentLocation);
    int screenX = parentLocation[0] + x;
    int screenY = parentLocation[1] + y;
    return screenX >= viewLocation[0] && 
           screenX < viewLocation[0] + mScrollView.getWidth() && 
           screenY >= viewLocation[1] && 
           screenY < viewLocation[1] + mScrollView.getHeight();
}

6) 删除onInterceptTouchEvent

7) 修改onTouchEvent如下:

public boolean onTouchEvent(MotionEvent ev) {
    if (!isEnabled() || !isTouchEnabled()) {
        return super.onTouchEvent(ev);
    }
    try {
        mDragHelper.processTouchEvent(ev);

        final int action = ev.getAction();
        boolean wantTouchEvents = false;

        switch (action & MotionEventCompat.ACTION_MASK) {
            case MotionEvent.ACTION_UP: {
                final float x = ev.getX();
                final float y = ev.getY();
                final float dx = x - mInitialMotionX;
                final float dy = y - mInitialMotionY;
                final int slop = mDragHelper.getTouchSlop();
                View dragView = mDragView != null ? mDragView : mSlideableView;

                if (dx * dx + dy * dy < slop * slop &&
                        isDragViewUnder((int) x, (int) y) &&
                        !isScrollViewUnder((int) x, (int) y)) {
                    dragView.playSoundEffect(SoundEffectConstants.CLICK);

                    if ((PanelState.EXPANDED != mSlideState) && (PanelState.ANCHORED != mSlideState)) {
                        setPanelState(PanelState.ANCHORED);
                    } else {
                        setPanelState(PanelState.COLLAPSED);
                    }
                    break;
                }
                break;
            }
        }

        return wantTouchEvents;
    } catch (Exception ex) {
        ex.printStackTrace();
        return false;
    }
}

8) 添加以下方法:

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    // Identify if we want to handle the touch event in this class.
    // We do this here because we want to be able to handle the case
    // where a child begins handling a touch event, but then the
    // parent takes over. If we rely on onInterceptTouchEvent, we
    // lose control of the touch as soon as the child handles the event.
    if (mScrollView == null)
        return super.dispatchTouchEvent(ev);

    final int action = MotionEventCompat.getActionMasked(ev);

    final float x = ev.getX();
    final float y = ev.getY();

    if (action == MotionEvent.ACTION_DOWN) {
        // Go ahead and have the drag helper attempt to intercept
        // the touch event. If it won't be dragging, we'll cancel it later.
        mDragHelper.shouldInterceptTouchEvent(ev);

        mInitialMotionX = mPrevMotionX = x;
        mInitialMotionY = mPrevMotionY = y;

        isChildHandlingTouch = false;
    } else if (action == MotionEvent.ACTION_MOVE) {
        float dx = x - mPrevMotionX;
        float dy = y - mPrevMotionY;
        mPrevMotionX = x;
        mPrevMotionY = y;

        // If the scroll view isn't under the touch, pass the
        // event along to the dragView.
        if (!isScrollViewUnder((int) x, (int) y))
            return this.onTouchEvent(ev);

        // Which direction (up or down) is the drag moving?
        if (dy > 0) { // DOWN
            // Is the child less than fully scrolled?
            // Then let the child handle it.
            if (isScrollViewScrolling()) {
                isChildHandlingTouch = true;
                return super.dispatchTouchEvent(ev);
            }

            // Was the child handling the touch previously?
            // Then we need to rejigger things so that the
            // drag panel gets a proper down event.
            if (isChildHandlingTouch) {
                // Send an 'UP' event to the child.
                MotionEvent up = MotionEvent.obtain(ev);
                up.setAction(MotionEvent.ACTION_UP);
                super.dispatchTouchEvent(up);
                up.recycle();

                // Send a 'DOWN' event to the panel. (We'll cheat
                // and hijack this one)
                ev.setAction(MotionEvent.ACTION_DOWN);
            }

            isChildHandlingTouch = false;
            return this.onTouchEvent(ev);
        } else if (dy < 0) { // UP
            // Is the panel less than fully expanded?
            // Then we'll handle the drag here.
            if (mSlideOffset < 1.0f) {
                isChildHandlingTouch = false;
                return this.onTouchEvent(ev);
            }

            // Was the panel handling the touch previously?
            // Then we need to rejigger things so that the
            // child gets a proper down event.
            if (!isChildHandlingTouch) {
                mDragHelper.cancel();
                ev.setAction(MotionEvent.ACTION_DOWN);
            }

            isChildHandlingTouch = true;
            return super.dispatchTouchEvent(ev);
        }
    } else if ((action == MotionEvent.ACTION_CANCEL) ||
            (action == MotionEvent.ACTION_UP)) {
        if (!isChildHandlingTouch) {
            final float dx = x - mInitialMotionX;
            final float dy = y - mInitialMotionY;
            final int slop = mDragHelper.getTouchSlop();

            if ((mIsUsingDragViewTouchEvents) && (dx * dx + dy * dy < slop * slop))
                return super.dispatchTouchEvent(ev);

            return this.onTouchEvent(ev);
        }
    }

    // In all other cases, just let the default behavior take over.
    return super.dispatchTouchEvent(ev);
}

9)添加如下方法判断scrollView是否还在滚动。处理 ScrollView 和 ListView 的情况:

/**
 * Computes the scroll position of the the scrollView, if set.
 * @return
 */
private boolean isScrollViewScrolling() {
    if (mScrollView == null)
        return false;

    // ScrollViews are scrolling when getScrollY() is a value greater than 0.
    if (mScrollView instanceof ScrollView) {
        return (mScrollView.getScrollY() > 0);
    }
    // ListViews are scrolling if the first child is not displayed, or if the first child has an offset > 0
    else if (mScrollView instanceof ListView) {
        ListView lv = (ListView) mScrollView;

        if (lv.getFirstVisiblePosition() > 0)
            return true;

        View v = lv.getChildAt(0);
        int top = (v == null) ? (0) : (-v.getTop() + lv.getFirstVisiblePosition() * lv.getHeight());
        return top > 0;
    }

    return false;
}

10)(可选)添加如下方法让你在运行时设置scrollView(即你想在面板中放置一个fragment,并且fragment的child有一个你想要滚动的ScrollView/ListView):

public void setScrollView(View scrollView) {
    mScrollView = scrollView;
}

我们现在在这个类中完全管理触摸事件的处理。如果我们向上拖动面板并且它完全滑入到位,我们取消拖动,然后欺骗mScrollView孩子的新触摸。如果我们滚动孩子并到达顶部,我们会在孩子中欺骗“向上”事件并欺骗新的拖动触摸。这也允许其他子小部件上的点击事件。

已知问题 我们欺骗的“向上”/“向下”事件可能会无意中触发滚动视图子元素上的单击事件。

于 2014-03-28T18:09:23.460 回答
6

我有同样的问题,但在我的应用程序中有 ListView 而不是 ScrollView。我无法应用元帅的答案来解决我的问题。但我根据元帅、克里斯的回答和玛丽亚·萨哈罗娃的评论找到了解决方案

首先我找不到变量 mCanSlide 和 mIsSlidingEnabled 以及方法 expandPane(mAnchorPoint) 和 collapsePane() 所以我使用下一个代码:

@Override
public boolean onTouchEvent(MotionEvent ev) {
    if (!isEnabled() || !isTouchEnabled()) {
        return super.onTouchEvent(ev);
    }
    try {
        mDragHelper.processTouchEvent(ev);

        final int action = ev.getAction();
        boolean wantTouchEvents = false;

        switch (action & MotionEventCompat.ACTION_MASK) {
            case MotionEvent.ACTION_UP: {
                final float x = ev.getX();
                final float y = ev.getY();
                final float dx = x - mInitialMotionX;
                final float dy = y - mInitialMotionY;
                final int slop = mDragHelper.getTouchSlop();
                View dragView = mDragView != null ? mDragView : mSlideableView;

                if (dx * dx + dy * dy < slop * slop &&
                        isDragViewUnder((int) x, (int) y) &&
                        !isScrollViewUnder((int) x, (int) y)) {
                    dragView.playSoundEffect(SoundEffectConstants.CLICK);
                    if (!isExpanded() && !isAnchored()) {
                        //expandPane(mAnchorPoint);
                        setPanelState(PanelState.ANCHORED);
                    } else {
                        //collapsePane();
                        setPanelState(PanelState.COLLAPSED);
                    }
                    break;
                }
                break;
            }
        }

        return wantTouchEvents;
    } catch (Exception ex){
        ex.printStackTrace();
        return false;
    }
}

try/catch 是必需的,因为应用两个手指时会引发异常。

第二克里斯的回答是必须履行的。

然后由于 ListView 的方法 getScrollY() 总是返回零,我在方法 dispatchTouchEvent(MotionEvent ev) 处稍微更改了代码:

这个:

if (mScrollView.getScrollY() > 0) {
   isChildHandlingTouch = true;
   return super.dispatchTouchEvent(ev);
}

至:

if (((ListView)mScrollView).getFirstVisiblePosition() > 0 ||             getFirstChildTopOffset((ListView) mScrollView) > 0){
   isChildHandlingTouch = true;
   return super.dispatchTouchEvent(ev);
} 

//at some other place in class SlidingUpPanelLayout 
public int getFirstChildTopOffset(ListView list){
    View v = list.getChildAt(0);
    int top = (v == null) ? 0 : (list.getPaddingTop() - v.getTop());
    return top;
}

此外,我的应用程序将 Google Map 作为主要内容,它还必须获取 MotionEvent,因此 Maria Sakharova 说我们必须返回 this.onTouchEvent(ev) || super.dispatchTouchEvent(ev) 而不是 this.onTouchEvent(ev) 在两个地方。我们必须更改此代码:

if (!isScrollViewUnder((int) x, (int) y))
   return this.onTouchEvent(ev);

至:

if (!isScrollViewUnder((int) x, (int) y))
   return this.onTouchEvent(ev) || super.dispatchTouchEvent(ev);

在这种情况下,如果主要内容必须获取 MotionEvent,则需要 super.dispatchTouchEvent(ev)。

第二个代码:

} else if ((action == MotionEvent.ACTION_CANCEL) ||
            (action == MotionEvent.ACTION_UP)) {
    if (!isChildHandlingTouch) {
        final float dx = x - mInitialMotionX;
        final float dy = y - mInitialMotionY;
        final int slop = mDragHelper.getTouchSlop();

        if ((mIsUsingDragViewTouchEvents) &&
                    (dx * dx + dy * dy < slop * slop))
            return super.dispatchTouchEvent(ev);

        return this.onTouchEvent(ev);
    }
}

至:

} else if ((action == MotionEvent.ACTION_CANCEL) ||
            (action == MotionEvent.ACTION_UP)) {
   if (!isChildHandlingTouch) {
        final float dx = x - mInitialMotionX;
        final float dy = y - mInitialMotionY;
        final int slop = mDragHelper.getTouchSlop();

        if ((mIsUsingDragViewTouchEvents) &&
                    (dx * dx + dy * dy < slop * slop))
            return super.dispatchTouchEvent(ev);

        return this.onTouchEvent(ev) || super.dispatchTouchEvent(ev);
    }
}

在这种情况下,需要 super.dispatchTouchEvent(ev) 才能展开面板。

总之方法 dispatchTouchEvent(MotionEvent ev) 将是下一个:

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    // Identify if we want to handle the touch event in this class.
    // We do this here because we want to be able to handle the case
    // where a child begins handling a touch event, but then the
    // parent takes over. If we rely on onInterceptTouchEvent, we
    // lose control of the touch as soon as the child handles the event.
    if (mScrollView == null)
        return super.dispatchTouchEvent(ev);

    final int action = MotionEventCompat.getActionMasked(ev);

    final float x = ev.getX();
    final float y = ev.getY();

    if (action == MotionEvent.ACTION_DOWN) {
        // Go ahead and have the drag helper attempt to intercept
        // the touch event. If it won't be dragging, we'll cancel it later.
        mDragHelper.shouldInterceptTouchEvent(ev);

        mInitialMotionX = mPrevMotionX = x;
        mInitialMotionY = mPrevMotionY = y;

        isChildHandlingTouch = false;
    } else if (action == MotionEvent.ACTION_MOVE) {
        float dx = x - mPrevMotionX;
        float dy = y - mPrevMotionY;
        mPrevMotionX = x;
        mPrevMotionY = y;

        // If the scroll view isn't under the touch, pass the
        // event along to the dragView.
        if (!isScrollViewUnder((int) x, (int) y))
            //return this.onTouchEvent(ev);
            return this.onTouchEvent(ev) || super.dispatchTouchEvent(ev);

        // Which direction (up or down) is the drag moving?
        if (dy > 0) { // DOWN
            // Is the child less than fully scrolled?
            // Then let the child handle it.
            //if (mScrollView.getScrollY() > 0) {
            if (((ListView)mScrollView).getFirstVisiblePosition() > 0 || getFirstChildTopOffset((ListView) mScrollView) > 0){
                isChildHandlingTouch = true;
                return super.dispatchTouchEvent(ev);
            }

            // Was the child handling the touch previously?
            // Then we need to rejigger things so that the
            // drag panel gets a proper down event.
            if (isChildHandlingTouch) {
                // Send an 'UP' event to the child.
                MotionEvent up = MotionEvent.obtain(ev);
                up.setAction(MotionEvent.ACTION_UP);
                super.dispatchTouchEvent(up);
                up.recycle();

                // Send a 'DOWN' event to the panel. (We'll cheat
                // and hijack this one)
                ev.setAction(MotionEvent.ACTION_DOWN);
            }

            isChildHandlingTouch = false;
            return this.onTouchEvent(ev);
        } else if (dy < 0) { // UP
            // Is the panel less than fully expanded?
            // Then we'll handle the drag here.
            //if (mSlideOffset > 0.0f) {
            if (mSlideOffset < 1.0f) {
                isChildHandlingTouch = false;
                return this.onTouchEvent(ev);
                //return this.onTouchEvent(ev) || super.dispatchTouchEvent(ev);
            }

            // Was the panel handling the touch previously?
            // Then we need to rejigger things so that the
            // child gets a proper down event.
            if (!isChildHandlingTouch) {
                mDragHelper.cancel();
                ev.setAction(MotionEvent.ACTION_DOWN);
            }

            isChildHandlingTouch = true;
            return super.dispatchTouchEvent(ev);
        }
    } else if ((action == MotionEvent.ACTION_CANCEL) ||
            (action == MotionEvent.ACTION_UP)) {
        if (!isChildHandlingTouch) {
            final float dx = x - mInitialMotionX;
            final float dy = y - mInitialMotionY;
            final int slop = mDragHelper.getTouchSlop();

            if ((mIsUsingDragViewTouchEvents) &&
                    (dx * dx + dy * dy < slop * slop))
                return super.dispatchTouchEvent(ev);

            //return this.onTouchEvent(ev);
            return this.onTouchEvent(ev) || super.dispatchTouchEvent(ev);
        }
    }

    // In all other cases, just let the default behavior take over.
    return super.dispatchTouchEvent(ev);
}
于 2015-01-24T10:26:57.927 回答
4

从 3.1.0 开始,Umano SlidingUpPanelLayout 支持开箱即用的 ScrollView、ListView 和 RecyclerView 嵌套滚动。

在大多数情况下,只需sothree:umanoScrollableView在 XML 布局文件中添加属性,如下所示:

<com.sothree.slidinguppanel.SlidingUpPanelLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:sothree="http://schemas.android.com/apk/res-auto"
    android:id="@+id/sliding_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    sothree:umanoScrollableView="@+id/my_scrollable_view"
    android:gravity="bottom"
    sothree:umanoAnchorPoint="0.3"
    sothree:umanoPanelHeight="@dimen/bottom_playlist_height"
    sothree:umanoShadowHeight="4dp"
    android:paddingTop="?attr/actionBarSize">

有关更多信息,请查看此链接:https ://github.com/umano/AndroidSlidingUpPanel#scrollable-sliding-views

于 2016-07-12T09:44:57.297 回答
2

哇!4年过去了!但这个问题仍然是相关的。至少对我来说。我在一小部分代码中找到了解决方案,而无需修改库。它适用于 ScrollView。

public class MySlidingUpPanelLayout extends SlidingUpPanelLayout {
    public void setScrollViewInside(final ScrollViewInsideSlidingUpPanelLayout scroll){
        this.addPanelSlideListener(new PanelSlideListener() {
            @Override public void onPanelSlide(View panel, float slideOffset) {}

            @Override
            public void onPanelStateChanged(View panel, PanelState previousState, PanelState newState) {
                if(scroll!=null) {
                    scroll.setScrollable(getPanelState() == PanelState.EXPANDED);
                }
            }
        });
    }
}

public class ScrollViewInsideSlidingUpPanelLayout extends ScrollView {
    private boolean scrollable = false;
    public void setScrollable(boolean scrollable){
        this.scrollable = scrollable;
    }

    @Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
        super.onScrollChanged(l, t, oldl, oldt);

        if (getScrollY() == 0) {
            scrollable = false;
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if(scrollable)
            return super.onTouchEvent(event);
        else
            return false;
    }
}

用法:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    ...
    sliding_layout.setScrollViewInside(scroll);
}
于 2018-03-02T21:36:42.337 回答
0

为了让 JJD 的答案起作用,您需要添加另一个步骤

8)mScrollViewResId = ta.getResourceId(R.styleable.SlidingUpPanelLayout_scrollView, -1); 在SlidingPanelLayout的构造函数中添加这个方法

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

      ...

    if (attrs != null) {
            ...

        if (ta != null) {

                   ...

            mScrollViewResId = ta.getResourceId(R.styleable.SlidingUpPanelLayout_scrollView, -1);

                   ...
        }

        ta.recycle();
    }

}
于 2015-01-08T16:59:43.543 回答
0

想补充一下 PAD 的答案。

创建一个新NestedScrollableViewHelper类,该类将按照https://github.com/umano/AndroidSlidingUpPanel#scrollable-sliding-viewsScrollableViewHelper 中的指示进行扩展

然后在你的 mainActivity 中设置它:

slidingUpPanelLayout = findViewById(R.id.slidingpanel); slidingUpPanelLayout.setScrollableViewHelper(new NestedScrollableViewHelper());

没有这个,SlidingUpPanel 在内部嵌套滚动视图或滚动视图中向下滚动时不会拦截触摸,并且正在向下滚动 SlidingUpPanel 本身。

向上滚动工作正常。

于 2019-06-01T12:35:06.347 回答
0

只需使用setScrollableView

例子:

public View onCreateView(
    @NonNull LayoutInflater inflater, 
    ViewGroup container,
    Bundle savedInstanceState) {
    ((SlidingUpPanelLayout)findViewById(R.id.view))
        .setScrollableView(findViewById(R.id.scrollable));
}

可滚动视图可以是 is RecyclerView,ListViewScrollView

于 2017-11-23T16:48:55.330 回答