1
0

ViewPager2のrequestDisallowInterceptTouchEventを実行してもViewPager2には効果がない件

Posted at

AndroidのViewにおいて、タッチイベントは子の方が優先的に処理できますが、親Viewは子ViewがonTouchEventを処理している中、onInterceptTouchEventが呼び出されており、trueを返すことで処理を奪うことができます。
例えば横スクロールのViewAの上に、縦スクロールのViewBを配置するという画面構成の場合、ViewAはonInterceptTouchEventで操作を監視し、操作方向が横方向であると判定すると、ViewBから操作の優先権を奪うことで、縦横両方の操作が両立できるという仕組みになっています。

概ねこれでうまくいくのですが、場合によっては親View側で処理を奪ってほしくない場合があります。その場合に使うのが、requestDisallowInterceptTouchEvent(true)です。
名前の通り、このメソッドがコールされたView(およびその親のView)はInterceptしなくなり、子はタッチイベントの優先権を奪われなくなります。

例えば、チョット複雑なレイアウトを作るとします

  • 横スクロールのViewPager2
  • その上に縦スクロールのRecyclerView
  • RecyclerViewのセルの一つが横スクロールのViewを持っている

みたいな構成の場合(そんな構成作るなというのは置いておいて)
このセルの横方向操作をViewPager2に奪われてしまうとカルーセルの操作ができなくなってしまうので、その操作をしている間はViewPager2のIntercept動作を停止させたいですよね。
一方で、このセルは横方向の操作は受け取りたいけど、縦方向の場合は自身ではなく親のRecyclerViewに奪ってもらって、縦スクロールさせたいところです。
なので、単純に親に対してrequestDisallowInterceptTouchEvent(true)を実行する訳にはいきません。

失敗例

ということで、そのセルのViewを拡張し、以下のように、親のViewPager2を探して、requestDisallowInterceptTouchEvent(true)を実行して……ということをやっても、ViewPager2の動作を抑制させることができません。

override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
    if (ev.action == MotionEvent.ACTION_DOWN) {
        findViewPager2(this)?.requestDisallowInterceptTouchEvent(true)
    }
    return super.dispatchTouchEvent(ev)
}

private fun findViewPager2(view: View): View? {
    val parent = view.parent
    if (parent is ViewPager2) {
        return parent
    }
    if (parent is ViewGroup) {
        return findViewPager2(parent)
    }
    return null
}

うまくいかない理由

Viewの階層を見れば分かるのですが、ViewPager2は直接PageのViewを持っているわけではありません。
ViewPager2直下にViewPager2.RecyclerViewImplというViewがあります。

ViewPager2の中にあるprivate classで、RecyclerViewを拡張したクラスです。

ViewPager2自身はページスクロールなどの機能は持っておらず、それらを行っているのが、このRecyclerViewImplなのですね。ということは、タッチイベントを奪ったりしているのは、RecyclerViewImplの方、ということです。

requestDisallowInterceptTouchEvent(true)は、そのViewGroupおよびそれより親に対する命令なので、ViewPager2のrequestDisallowInterceptTouchEvent(true)をコールしても、肝心のRecyclerViewImplに伝わらず、意図した動作にならなかった訳です。

解決策

ということで、先のコードはViewPager2まで遡ってしまったのが問題でした、その一つ下にあるRecyclerViewImplを探すように変更します。ただし、RecyclerViewImplはprivate classなので、直接調べるのが難しいです。
ということで、親を遡っていき、親がViewPager2であるViewを返すように変更。

override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
    if (ev.action == MotionEvent.ACTION_DOWN) {
        findViewPager2(this)?.requestDisallowInterceptTouchEvent(true)
    }
    return super.dispatchTouchEvent(ev)
}

private fun findRecyclerViewImpl(view: View): View? {
    val parent = view.parent
    if (parent is ViewPager2) {
        return view
    }
    if (parent is ViewGroup) {
        return findViewPager2(parent)
    }
    return null
}

これでViewPager2に操作を奪われないようにすることができます。

以上です。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0