web-dev-qa-db-ja.com

SwipeRefreshLayout内のHorizo​​ntalScrollView

アプリケーションに新しいSwipeRefreshLayoutコンポーネントを実装しましたが、ListViewGridViewScrollViewなどの任意の垂直ビューでうまく機能します。

HorizontalScrollViewのような水平ビューでは非常に悪い動作をします。右または左にスクロールすると、SwipeRefreshLayoutビューはタッチをキャッシュし、HorizontalScrollViewがタッチを受け取らないようにし、垂直スクロールを開始して更新を実行します。

ScrollViewを使用してViewPagerを含む垂直requestDisallowInterceptTouchEventの問題を以前に解決したため、この問題を解決しようとしましたが、うまくいきませんでした。また、元のSwipeRefreshLayoutクラスでは、スーパーを返すことなく、このメソッドがオーバーライドされることに気付きました。 Googleのデベロッパーは代わりにコメントを残しました "//Nope. ":)

SwipeRefreshLayoutコンポーネントは比較的新しいため、スワイプでビューを更新して垂直スクロールを追跡および処理できるようにしながら、水平スクロールの問題を修正するソリューションが見つかりませんでした。それは誰かを1時間か2時間節約します。

50
Lior Iluz

ACTION_MOVEイベントを既に辞退したという事実を覚えていない場合、ユーザーが最初のmPrevXの近くに戻ると、最終的にそれを受け入れます。

ブール値を追加して記憶します。

public class CustomSwipeToRefresh extends SwipeRefreshLayout {

    private int mTouchSlop;
    private float mPrevX;
    // Indicate if we've already declined the move event
    private boolean mDeclined;

    public CustomSwipeToRefresh(Context context, AttributeSet attrs) {
        super(context, attrs);

        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {

        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mPrevX = MotionEvent.obtain(event).getX();
                mDeclined = false; // New action
                break;

            case MotionEvent.ACTION_MOVE:
                final float eventX = event.getX();
                float xDiff = Math.abs(eventX - mPrevX);

                if (mDeclined || xDiff > mTouchSlop) {
                    mDeclined = true; // Memorize
                    return false;
                }
        }

        return super.onInterceptTouchEvent(event);
    }
}
27
Twibit

OnInterceptTouchEvent()をオーバーライドするLior Iluzによって提案されたソリューションには重大な問題があります。コンテンツのスクロール可能なコンテナが完全にスクロールアップされていない場合、同じスクロールアップジェスチャでスワイプトゥリフレッシュをアクティブにできない可能性があります。実際、内側のコンテナーのスクロールを開始し、mTouchSlopよりも指を水平方向に動かすと(デフォルトでは8dp)、提案されたCustomSwipeToRefreshはこのジェスチャーを拒否します。そのため、ユーザーは更新を開始するためにもう一度試す必要があります。これはユーザーにとって奇妙に見えるかもしれません。

元のSwipeRefreshLayoutのソースコードをサポートライブラリからプロジェクトに抽出し、onInterceptTouchEvent()を書き直しました。新しいクラス名はTouchSafeSwipeRefreshLayout

private boolean mPendingActionDown;
private float mInitialDownY;
private float mInitialDownX;
private boolean mGestureDeclined;

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    ensureTarget();
    final int action = ev.getActionMasked();
    int pointerIndex;

    if (mReturningToStart && action == MotionEvent.ACTION_DOWN) {
        mReturningToStart = false;
    }

    if (!isEnabled() || mReturningToStart || mRefreshing ) {
        // Fail fast if we're not in a state where a swipe is possible
        if (D) Log.e(LOG_TAG, "Fail because of not enabled OR refreshing OR returning to start. "+motionEventToShortText(ev));
        return false;
    }

    switch (action) {
        case MotionEvent.ACTION_DOWN:
            setTargetOffsetTopAndBottom(mOriginalOffsetTop - mCircleView.getTop());
            mActivePointerId = ev.getPointerId(0);

            if ((pointerIndex = ev.findPointerIndex(mActivePointerId)) >= 0) {

                if (mNestedScrollInProgress || canChildScrollUp()) {
                    if (D) Log.e(LOG_TAG, "Fail because of nested content is Scrolling. Set pending DOWN=true. "+motionEventToShortText(ev));
                    mPendingActionDown = true;
                } else {
                    mInitialDownX = ev.getX(pointerIndex);
                    mInitialDownY = ev.getY(pointerIndex);
                }
            }
            return false;

        case MotionEvent.ACTION_MOVE:
            if (mActivePointerId == INVALID_POINTER) {
                if (D) Log.e(LOG_TAG, "Got ACTION_MOVE event but don't have an active pointer id.");
                return false;
            } else if (mGestureDeclined) {
                if (D) Log.e(LOG_TAG, "Gesture was declined previously because of horizontal swipe");
                return false;
            } else if ((pointerIndex = ev.findPointerIndex(mActivePointerId)) < 0) {
                return false;
            } else if (mNestedScrollInProgress || canChildScrollUp()) {
                if (D) Log.e(LOG_TAG, "Fail because of nested content is Scrolling. "+motionEventToShortText(ev));
                return false;
            } else if (mPendingActionDown) {
                // This is the 1-st Move after content stops scrolling.
                // Consider this Move as Down (a start of new gesture)
                if (D) Log.e(LOG_TAG, "Consider this move as down - setup initial X/Y."+motionEventToShortText(ev));
                mPendingActionDown = false;
                mInitialDownX = ev.getX(pointerIndex);
                mInitialDownY = ev.getY(pointerIndex);
                return false;
            } else if (Math.abs(ev.getX(pointerIndex) - mInitialDownX) > mTouchSlop) {
                mGestureDeclined = true;
                if (D) Log.e(LOG_TAG, "Decline gesture because of horizontal swipe");
                return false;
            }

            final float y = ev.getY(pointerIndex);
            startDragging(y);
            if (!mIsBeingDragged) {
                if (D) Log.d(LOG_TAG, "Waiting for dY to start dragging. "+motionEventToShortText(ev));
            } else {
                if (D) Log.d(LOG_TAG, "Dragging started! "+motionEventToShortText(ev));
            }
            break;

        case MotionEvent.ACTION_POINTER_UP:
            onSecondaryPointerUp(ev);
            break;

        case MotionEvent.ACTION_UP:
        case MotionEvent.ACTION_CANCEL:
            mIsBeingDragged = false;
            mGestureDeclined = false;
            mPendingActionDown = false;
            mActivePointerId = INVALID_POINTER;
            break;
    }

    return mIsBeingDragged;
}

Github のプロジェクト例を参照してください。

1

Tim Roes EnhancedListViewを使用する場合

こちらをご覧ください issues 。スワイプの開始時と終了時を検出する機能が追加されているため、私は非常に役に立ちました。

スワイプを開始すると、SwipeRefreshLayoutが無効になり、スワイプが終了すると、swipeRefreshLayoutが有効になります。

0
JND