SwipeRefreshLayout + ViewPager、水平スクロールのみを制限しますか?


94

私はアプリに実装SwipeRefreshLayoutViewPagerましたが、大きな問題があります。ページを切り替えるために左/右にスワイプするたびに、スクロールが非常に敏感です。少し下にスワイプすると、SwipeRefreshLayout更新もトリガーされます。

水平スワイプを開始する時間に制限を設定し、スワイプが終了するまで水平方向に強制します。つまり、指が水平方向に動いているときの垂直方向のスワイプをキャンセルしたいのです。

この問題はでのみ発生します。ViewPager下にスワイプしてSwipeRefreshLayout更新機能がトリガーされ(バーが表示されます)、指を水平方向に動かしても、垂直方向のスワイプしかできません。

ViewPagerクラスを拡張しようとしましたが、まったく機能していません。

public class CustomViewPager extends ViewPager {

    public CustomViewPager(Context ctx, AttributeSet attrs) {
        super(ctx, attrs);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        boolean in = super.onInterceptTouchEvent(ev);
        if (in) {
            getParent().requestDisallowInterceptTouchEvent(true);
            this.requestDisallowInterceptTouchEvent(true);
        }
        return false;
    }

}

レイアウトxml:

<android.support.v4.widget.SwipeRefreshLayout
    android:id="@+id/viewTopic"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <com.myapp.listloader.foundation.CustomViewPager
        android:id="@+id/topicViewPager"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
</android.support.v4.widget.SwipeRefreshLayout>

どんな助けでも感謝します、ありがとう


ビューページャー内のフラグメントの1つにSwipeRefreshLayout
Zapnologica、2015年

回答:


160

この問題がまだ発生するかどうかはわかりませんが、Google I / Oアプリioschedがこの問題をこのように解決します:

    viewPager.addOnPageChangeListener( new ViewPager.OnPageChangeListener() {
        @Override
        public void onPageScrolled( int position, float v, int i1 ) {
        }

        @Override
        public void onPageSelected( int position ) {
        }

        @Override
        public void onPageScrollStateChanged( int state ) {
            enableDisableSwipeRefresh( state == ViewPager.SCROLL_STATE_IDLE );
        }
    } );


private void enableDisableSwipeRefresh(boolean enable) {
    if (swipeContainer != null) {
            swipeContainer.setEnabled(enable);
    }
}

私は同じものを使用しており、かなりうまくいきます。

編集:setOnPageChangeListener()の代わりにaddOnPageChangeListener()を使用してください。


3
これは、ViewPagerの状態を考慮に入れるため、最良の答えです。それは、ViewPagerから発生する下方向へのドラッグを妨げません。これは、更新する意図を明確に示しています。
Andrew Gallasch 2015

4
最良の答えですが、enableDisableSwipeRefreshにコードを投稿するのは良いことです(はい、それは関数名から明らかです。しかし、確実にグーグルする必要があったのです...)
Greg Ennis

5
完全に動作しますが、setOnPageChangeListenerは廃止されました。代わりにaddOnPageChangeListenerを使用してください。
Yon Kornilov、2016

@nhasan最近の更新以降、この回答は機能しなくなりました。swiperefreshの有効状態をfalseに設定すると、swiperefreshが完全に削除されますが、以前は、swiperefreshが更新されている間にビューページャーのスクロール状態が変更された場合、レイアウトは削除されませんが、以前と同じ更新状態を維持しながら無効になります。
Michael Tedla、2016年

2
Google i / oアプリの「enableDisableSwipeRefresh」のソースコードへのリンク:android.googlesource.com/platform/external/iosched
+

37

何も拡張せずに非常に簡単に解決

mPager.setOnTouchListener(new View.OnTouchListener() {
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        mLayout.setEnabled(false);
        switch (event.getAction()) {
            case MotionEvent.ACTION_UP:
                mLayout.setEnabled(true);
                break;
        }
        return false;
    }
});

魅力のように働く


わかりました。ただし、トップレベルの子のみ(およびICS未満のAPIでは)の場合にのみ垂直方向のスクロールが可能になるため、内にある可能性のあるすべてのスクロール可能アイテムに同じ問題が発生することに注意してください。ViewViewPagerSwipeRefreshLayoutListView
corsair992 2014

@ corsair992lessヒントをありがとう
user3896501

問題に直面している@ corsair992。私はViewPager中にいSwipeRefrestLayoutて、ViewPagerは持っていListviewます!SwipeRefreshLayout下にスクロールさせますが、上にスクロールすると、更新の進行状況がトリガーされます。なにか提案を?
Muhammad Babar

1
mLayoutとは何ですか?
desgraci 16

2
viewPager.setOnTouchListener {_、event-> swipeRefreshLayout.isEnabled = event.action == MotionEvent.ACTION_UP false}
Axrorxo'ja Yodgorov

22

私はあなたの問題に会いました。SwipeRefreshLayoutをカスタマイズすると、問題が解決します。

public class CustomSwipeToRefresh extends SwipeRefreshLayout {

private int mTouchSlop;
private float mPrevX;

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();
            break;

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

            if (xDiff > mTouchSlop) {
                return false;
            }
    }

    return super.onInterceptTouchEvent(event);
}

ref:リンクを参照してください


これは、検出に勾配を含めるための最良のソリューションです。
nafsaka 2015年


12

私は以前の回答に基づいてこれを基にしましたが、これは少しうまくいくことがわかりました。モーションはACTION_MOVEイベントで始まり、私の経験ではACTION_UPまたはACTION_CANCELで終わります。

mViewPager.setOnTouchListener(new View.OnTouchListener() {
    @Override
    public boolean onTouch(View v, MotionEvent event) {

        switch (event.getAction()) {
            case MotionEvent.ACTION_MOVE:
                mSwipeRefreshLayout.setEnabled(false);
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                mSwipeRefreshLayout.setEnabled(true);
                break;
        }
        return false;
    }
});

Thnxx for the solution
Hitesh Kushwah

9

何らかの理由で彼らだけがよく知っているため、サポートライブラリの開発チームは、子が特にイベントの所有権を要求した場合でも、子のレイアウトからすべての垂直ドラッグモーションイベントを強制的にインターセプトするの適していると考えSwipeRefreshLayoutました。彼らがチェックする唯一のことは、そのメインの子の垂直スクロール状態がゼロであることです(その子が垂直にスクロール可能な場合)。このrequestDisallowInterceptTouchEvent()メソッドは空のボディでオーバーライドされており、(そうではない)明快なコメント「Nope」があります。

この問題を解決する最も簡単な方法は、サポートライブラリからプロジェクトにクラスをコピーし、メソッドのオーバーライドを削除することです。ViewGroupの実装は処理onInterceptTouchEvent()に内部状態を使用するため、メソッドを再度オーバーライドして複製することはできません。あなたが本当にしたい場合は上書きサポートライブラリの実装を、あなたはへの呼び出し時にカスタムフラグを設定する必要がありますrequestDisallowInterceptTouchEvent()し、オーバーライドonInterceptTouchEvent()onTouchEvent()(おそらくハックまたはcanChildScrollUp())それに基づいて行動。


男、それは大雑把だ。彼らがそうしなかったらよかったのに。プルして更新できるようにするリストと、行アイテムをスワイプする機能があります。彼らがSwipeRefreshLayoutを作成した方法は、いくつかのクレイジーな回避策なしではほとんど不可能です。
ジェシーA.モリス

3

ViewPager2の解決策を見つけました。私はこのようにドラッグ感度を下げるために反射を使用しています:

/**
 * Reduces drag sensitivity of [ViewPager2] widget
 */
fun ViewPager2.reduceDragSensitivity() {
    val recyclerViewField = ViewPager2::class.java.getDeclaredField("mRecyclerView")
    recyclerViewField.isAccessible = true
    val recyclerView = recyclerViewField.get(this) as RecyclerView

    val touchSlopField = RecyclerView::class.java.getDeclaredField("mTouchSlop")
    touchSlopField.isAccessible = true
    val touchSlop = touchSlopField.get(recyclerView) as Int
    touchSlopField.set(recyclerView, touchSlop*8)       // "8" was obtained experimentally
}

それは私にとって魅力のように機能します。


2

nhasanの解法には1つの問題があります。

がPull-to-Reloadをすでに認識していて、まだ通知コールバックを呼び出していないときsetEnabled(false)に、SwipeRefreshLayoutで呼び出しをトリガーする水平スワイプOnPageChangeListenerが発生した場合SwipeRefreshLayout、アニメーションは消えますが、内部状態はSwipeRefreshLayout「更新中」のままで、状態をリセットできる通知コールバックが呼び出されます。ユーザーの観点からは、これはすべてのプルジェスチャーが認識されないため、Pull-to-Reloadが機能しなくなったことを意味します。

ここでの問題は、disable(false)呼び出しがスピナーのアニメーションを削除し、通知コールバックがonAnimationEndそのように順不同に設定されているそのスピナーの内部AnimationListenerのメソッドから呼び出されることです。

確かに、私たちのテスターが最も速い指でこの状況を引き起こしたのは確かですが、現実的なシナリオでも時々発生する可能性があります。

これを修正する解決策onInterceptTouchEventSwipeRefreshLayout、次のようにメソッドをオーバーライドすることです:

public class MySwipeRefreshLayout extends SwipeRefreshLayout {

    private boolean paused;

    public MySwipeRefreshLayout(Context context) {
        super(context);
        setColorScheme();
    }

    public MySwipeRefreshLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        setColorScheme();
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (paused) {
            return false;
        } else {
            return super.onInterceptTouchEvent(ev);
        }
    }

    public void setPaused(boolean paused) {
        this.paused = paused;
    }
}

MySwipeRefreshLayoutレイアウトで-ファイルを使用し、mhasanのソリューションのコードを次のように変更します。

...

@Override
public void onPageScrollStateChanged(int state) {
    swipeRefreshLayout.setPaused(state != ViewPager.SCROLL_STATE_IDLE);
}

...

1
私もあなたと同じ問題を抱えていました。ただ、これでnhasanのソリューション修正pastebin.com/XmfNsDKQときにそのさわやかなので、それの理由をチェック、問題を作成しません注スワイプリフレッシュレイアウトを。
Amit Jayant 2017

0

ViewPagerが垂直スクロール可能なコンテナに配置され、次にSwiprRefreshLayoutに配置されている場合、@ huu duy回答に問題がある可能性があります。コンテンツのスクロール可能なコンテナが完全にスクロールアップされていない場合は、同じスクロールアップジェスチャーでスワイプして更新をアクティブにします。実際、内側のコンテナのスクロールを開始して指を水平方向にmTouchSlopよりも意図せずに(デフォルトでは8 dp)移動すると、提案されたCustomSwipeToRefreshはこのジェスチャーを拒否します。したがって、ユーザーは更新を再開するためにもう一度試す必要があります。これはユーザーにとって奇妙に見えるかもしれません。元のSwipeRefreshLayoutのソースコードをサポートライブラリからプロジェクトに抽出し、onInterceptTouchEvent()を書き直しました。

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

@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で私のサンプルプロジェクトを参照してください。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.