android - SlidingUpPanelLayout 和 ScrollView

标签 android android-scrollview slideup android-event android-sliding

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

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

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

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

而且我还在 scrollView 中放置了 onTouchEvent,以便解锁 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 并且面板无法折叠。

最佳答案

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

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

2015-04-12 更新 SlidingUpPanelLayout 代码更新到版本 3.0.0。还考虑了 ListView 而不仅仅是 ScrollView。

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

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

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

sothree:scrollView="@+id/myScrollView"

或者你的 scrollView 的 ID 是什么。还要确保您没有声明 sothree:dragView ID,这样整个 View 都是可拖动的。

其余的步骤都在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 的子级有一个 ScrollView/ListView想要滚动):

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

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

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

关于android - SlidingUpPanelLayout 和 ScrollView,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22478361/

相关文章:

jQuery 取消绑定(bind)文档单击绑定(bind)到滚动位置

android - 如何向 SlidingTabLayout 添加标签?

android - 使用 ActionBarSherlock 和 SQLite CursorLoader 在 ListView fragment 中调用 restartLoader 的位置

android - ScrollView 和 ConstraintLayout 在底部留下巨大的空白

android - 如何在大于屏幕尺寸的 Canvas 上绘制并能够在 android 中水平和垂直滚动/平移?

javascript - JQuery SlideUp 在 Chrome 中使整个页面滚动

SlideUp 的列表选择器中的 jquery 列表

java - 使用 Android 设备通过 REST API 保护身份验证?

android - NDK 级别的 Android 低级 USB API,可在 Qt Android 上使用

android - 在 ScrollView 中更改滚动条的颜色