SlidingUpPanelLayout 和 ScrollView [英] SlidingUpPanelLayout and ScrollView

查看:21
本文介绍了SlidingUpPanelLayout 和 ScrollView的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I have a SlidingUpPanelLayout that holds a image as a top view, and a view pager that needs to slide. The viewpager has 3 fragments and two of them are list views. So I want to be able to expand the view pager on pulling up, and once the view pager is up I want to be able to scroll the scrollviews inside the fragments. But when pulling down on the scrollview in case there is no more to scroll, I want to start collapsing the viewpager. So please suggest how to make the SlidingUpPanelLayout collapse on pulling the scrollview in case there is no more contents to scroll?

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

Here I post some of my code: I have tried capture the touch events and overwrite the SlidingUpPanel onInterceptTouchEvent function in the following way:

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

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

So when the SlidingUpPanelLayout is expanded I set isHandled = false. So when the slidingUpPanelLayout expands, all touch events are passed to its child views.

我还在scrollView中加入了onTouchEvent,以解除对SlidingUpPanelLayout.onInterceptTouchEvent的阻止:

And I also put onTouchEvent in the scrollView, in-order to unblock the 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.onInterceptTouchEventMotionEvent.ACTION_CANCEL 之后调用.这意味着无法将事件传递给 SlidingUpPanelLayout 并且面板无法折叠.

But this is not working. The problem is that once the scrollview.onTouch event is in MotionEvent.ACTION_MOVE SlidingUpPanelLayout.onInterceptTouchEvent is not called. SlidingUpPanelLayout.onInterceptTouchEvent is called after MotionEvent.ACTION_CANCEL. This means that the event can't be passed to the SlidingUpPanelLayout and the panel can't collapse.

推荐答案

很遗憾,由于上述原因,您不能依赖 SlidingUpPanelLayout 的 onInterceptTouchEvent 方法.一旦子视图的 onTouchEvent 方法返回 true,就不再调用 onInterceptTouchEvent.

Unfortunately you can't rely on SlidingUpPanelLayout's onInterceptTouchEvent method for the aforementioned reasons. Once a child view's onTouchEvent method returns true, onInterceptTouchEvent is no longer called.

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

My solution is a bit convoluted, but it allows you to achieve exactly what (I think) you're looking for. A single touch/drag event will drag the panel into place and, once in place, continue scrolling the child view. Likewise when dragging down, a single touch/drag event can scroll the child view and, once completely scrolled, will begin dragging the panel down.

已于 2015 年 4 月 12 日更新 已更新至 SlidingUpPanelLayout 代码的 3.0.0 版.还考虑了 ListViews 而不仅仅是 ScrollViews.

Updated 2015-04-12 Updated to version 3.0.0 of the SlidingUpPanelLayout code. Also accounting for ListViews instead of just ScrollViews.

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

1) In the res/ folder of SlidingUpPanel's library project, open the attrs.xml and add

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

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

You'll use this to identify a single child view that will usurp the touch event once the panel has been dragged into position. In your layout xml file, you can then add

sothree:scrollView="@+id/myScrollView"

或者无论你的滚动视图的 ID 是什么.还要确保您没有声明 sothree:dragView ID,因此整个视图都是可拖动的.

Or whatever the ID of your scrollView is. Also make sure that you do not declare a sothree:dragView ID, so the entire view is draggable.

其余的步骤都在SlidingUpPanelLayout.java...

2)声明以下变量:

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

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

3) In the constructor, just after mDragViewResId is set, add the following line:

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 修改为以下内容:

7) Modify onTouchEvent to the following:

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 的情况:

9) Add the following method to determine whether the scrollView is still scrolling. Handles cases for both ScrollView and 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你想滚动):

10) (Optional) Add the following method to allow you to set the scrollView at runtime (i.e. You want to put a fragment in the panel, and the fragment's child has a ScrollView/ListView you want to scroll):

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

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

We're now completely managing the handling of the touch event from within this class. If we're dragging the panel up and it slides fully into place, we cancel the drag and then spoof a new touch in the mScrollView child. If we're scrolling the child and reach the top, we spoof an "up" event in the child and spoof a new touch for the drag. This also allows tap events on other child widgets.

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

Known Issues The "up"/"down" events that we're spoofing can unintentionally trigger a click event on a child element of the scrollView.

这篇关于SlidingUpPanelLayout 和 ScrollView的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆