5

画像をトップ ビューとして保持するSlidingUpPanelLayoutと、スライドする必要があるビュー ページャーがあります。にviewpagerは 3 つのフラグメントがあり、そのうちの 2 つはリスト ビューです。そのため、プルアップ時にビュー ページャーを展開できるようにしたいと考えています。ビュー ページャーがアップしたら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ます。そのため、slideUpPanelLayout が展開されると、すべてのタッチ イベントがその子ビューに渡されます。

また、 のブロックを解除するために、も入れonTouchEventました。scrollViewSlidingUpPanelLayout.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は呼び出されなくなります。

私の解決策は少し複雑ですが、探しているもの (と思います) を正確に達成することができます。1 回のタッチ/ドラッグ イベントにより、パネルが所定の位置にドラッグされ、所定の位置に配置されると、子ビューのスクロールが続行されます。同様に、下にドラッグする場合、単一のタッチ/ドラッグ イベントで子ビューをスクロールできます。スクロールが完了すると、パネルを下にドラッグし始めます。

2015-04-12更新 SlidingUpPanelLayout コードのバージョン 3.0.0 に更新しました。また、ScrollViews だけでなく、ListViews も考慮します。

1)res/ SlidingUpPanel のライブラリ プロジェクトのフォルダーで、 を開き、 追加しattrs.xmlます。

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

これを使用して、パネルが所定の位置にドラッグされると、タッチ イベントを奪う単一の子ビューを識別します。レイアウト xml ファイルで、次を追加できます。

sothree:scrollView="@+id/myScrollView"

または、scrollView の ID が何であれ。sothree:dragViewまた、ビュー全体をドラッグできるように、ID を宣言しないようにしてください。

残りの手順はすべて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 を設定できるようにします (つまり、パネルにフラグメントを配置し、フラグメントの子にスクロールする ScrollView/ListView がある場合)。

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

これで、このクラス内からタッチ イベントの処理を完全に管理できるようになりました。mScrollViewパネルを上にドラッグしていて、完全に所定の位置にスライドする場合は、ドラッグをキャンセルしてから、子で新しいタッチをスプーフィングします。子をスクロールして一番上に到達すると、子の「up」イベントをスプーフィングし、ドラッグの新しいタッチをスプーフィングします。これにより、他の子ウィジェットでのタップ イベントも許可されます。

既知 の問題 私たちがスプーフィングしている「up」/「down」イベントは、scrollView の子要素で意図せずにクリック イベントをトリガーする可能性があります。

于 2014-03-28T18:09:23.460 に答える
6

私は同じ問題を抱えていましたが、私のアプリには ScrollView の代わりに ListView があります。私の問題を解決するために元帥の答えを適用できませんでした。しかし、元帥、クリスの回答、およびマリア・サハロワのコメントに基づいて解決策を見つけました

まず、変数 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;
    }
}

2 本の指を適用すると例外が発生するため、try/catch が必要です。

2 番目の Chris の回答は、必ず満たす必要があります。

そして、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 マップがあり、Maria Sakharova が this.onTouchEvent(ev) || を返す必要があると言ったように、MotionEvent も取得する必要があります。2 か所で、this.onTouchEvent(ev) の代わりに super.dispatchTouchEvent(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) が必要です。

そして2番目のコード:

} 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 に答える
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の回答に追加したいと思います。

https://github.com/umano/AndroidSlidingUpPanel#scrollable-sliding-views の指示に従ってNestedScrollableViewHelper拡張する新しいクラスを作成しますScrollableViewHelper

次に、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));
}

スクロール可能なビューはRecyclerView、などListViewです。ScrollView

于 2017-11-23T16:48:55.330 に答える