読者です 読者をやめる 読者になる 読者になる

ViewPagerで右スクロールを禁止して、左スクロールだけにする

Android Kotlin

概要

ViewPager を使用して複数の Fragment を横スクロールで切り替える時、右スクロールは禁止して、左スクロール(戻る)のみを有効にする方法です。

確認環境

  • Compile SDK Version 22
  • Build Tools Version 22.0.1
  • 実行環境 Sony SOV32 Android 6.0, API 23

参考情報

onTouchEvent メソッドと onInterceptTouchEvent メソッドをオーバーライドすることで実現している。

At the end the solution was in the adapter. I changed the count of the PagerAdapter and this way blocks the user from passing the max page:

PagerAdapter の getCount メソッドで返す値を変えることにより実現している。

対処方法

両方とも試してみた。

  • onTouchEvent, onInterceptTouchEvent メソッドをオーバーライドする方法

スワイプでは、右に全くスクロールできない。(左スクロールはスワイプ、右スクロールはボタンで行っている。)

  • PagerAdapter の getCount メソッドで返す値を変える方法

スワイプで右にスクロールしようとすると、スクロールできるかに見えるが跳ね返る。(左スクロールはスワイプ、右スクロールはボタンで行っている。)

補足

onTouchEvent, onInterceptTouchEvent の違いがわからないので、ソースコードを読んでみた。 要約すると次のようになる。

  • onInterceptTouchEvent が true を返したら自身の dispatchTouchEvent を行い、false を返したら子 View の dispatchTouchEvent を行う。
  • ACTION_MOVE の時は、ACTION_DOWN の時にイベントを handle した子 View の dispatchTouchEvent を行う。
    • この際、onInterceptTouchEvent が true ならば、ACTION_CANCEL として子 View の dispatchTouchEvent を行う。

以下、要点のみ疑似コードで記載。

ViewGroup::dispatchTouchEvent は下記のように振る舞いっていると思われる。

ACTION_DOWN の時:
    intercepted = onInterceptTouchEvent
    intercepted が false の時:
        全ての childView に対して: ...(A)
            handled = childView.dispatchTouchEvent
            handled が true の時:
                TouchTarget の先頭に childView を追加して、(A) 終了
    TouchTarget がない時:
        handled = View::dispatchTouchEvent
    handled を返して、終了

ACTION_MOVE の時:
    TouchTarget がない時:
        handled = View::dispatchTouchEvent
    TouchTarget がある時:
        intercepted = onInterceptTouchEvent
        intercepted が true の時:
            event.action を ACTION_CANCEL として、全ての TouchTarget(childView) に対して:
                handled = childView.dispatchTouchEvent
                childView を TouchTarget から取り除く
        intercepted が false の時:
            全ての TouchTarget(childView) に対して:
                handled = childView.dispatchTouchEvent
    いずれかの handled が true ならば true を返して、終了

ACTION_HOVER_MOVE の時:
    TouchTarget がある時:
        intercepted = onInterceptTouchEvent
    TouchTarget がない時:
        intercepted = false
    intercepted が false の時:
        全ての childView に対して: ...(A)
            TouchView に childView が含まれているなら、(A) 終了
            handled = childView.dispatchTouchEvent
            handled が true の時:
                TouchTarget の先頭に childView を追加して、(A) 終了
    TouchTarget がない時:
        handled = View::dispatchTouchEvent
    TouchTarget がある時:
        intercepted が true の時:
            event.action を ACTION_CANCEL として、全ての TouchTarget(childView) に対して:
                handled = childView.dispatchTouchEvent
                childView を TouchTarget から取り除く
        intercepted が false の時:
            全ての TouchTarget(childView) に対して:
                handled = childView.dispatchTouchEvent
    いずれかの handled が true ならば true を返して、終了

View::dispatchTouchEvent は下記のように振る舞いっていると思われる。

OnTouchListener が登録されている時:
    handled = OnTouchListener::onTouch
handled が false で、TouchDelegate が登録されている時:
    handled = TouchDelegate::onTouchEvent
handled が false の時:
    handled = イベントを処理する
handled を返して、終了