WebViewをSwipeRefreshLayoutやViewPager等に配置し、NestedScrollできるようにする方法

# SwipeRefreshLayoutでPull to Refresh

Pull to Refreshを実装する場合はSwipeRefreshLayoutで囲むだけだとちょっと問題が起こります。

```xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/swipe_refresh_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >

    <WebView
        android:id="@+id/web_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        />

</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
```

こういう普通の縦スクロールコンテンツなら、コンテンツのスクロールとPull to Refreshの操作はうまく切り分けてくれます。

<img width="400" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155171/a810b91c-5d76-8fc1-657b-91217d59d07f.png">

ところがMapのような、ページ自体はスクロールしないで中身だけがスクロールするコンテンツは、スワイプ処理をPull to Refreshが奪ってしまうため、ページの操作が正しくできません。

<img width="400" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155171/5b28c74d-bf6b-9fe1-6611-c3590106232f.png">

## 解決策

WebViewを継承したクラスを作り、[ViewParent#requestDisallowInterceptTouchEvent(boolean)](https://developer.android.com/reference/android/view/ViewParent#requestDisallowInterceptTouchEvent(boolean)) を使って、WebViewコンテンツの操作中はSwipeRefreshLayoutにタッチイベントを奪わないようにリクエストします。
「Webコンテンツの操作中」という判定が難しそうですが、[WebView#onOverScrolled(int, int, boolean, boolean)](https://developer.android.com/reference/android/webkit/WebView#onOverScrolled(int,%20int,%20boolean,%20boolean)) をOverrideすることで判定できます。コンテンツがこれ以上動かないところからのスクロールの時に、横方向に動かない場合は第3引数のclampedX、縦方向に動かない場合は第4引数のclampedYがtrueでコールされます。
逆に言えば両方がfalseの場合は、Webコンテンツのスクロールが発生している最中と言うことになります。

```kotlin
class MyWebView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyle: Int = 0
): WebView(context, attrs, defStyle) {

    init {
        isNestedScrollingEnabled = true
    }

    override fun onTouchEvent(event: MotionEvent): Boolean {
        if (event.action == MotionEvent.ACTION_DOWN) {
            parent.requestDisallowInterceptTouchEvent(true)
        }
        return super.onTouchEvent(event)
    }

    override fun onOverScrolled(scrollX: Int, scrollY: Int, clampedX: Boolean, clampedY: Boolean) {
        if (clampedX || clampedY) {
            parent.requestDisallowInterceptTouchEvent(false)
        }
        super.onOverScrolled(scrollX, scrollY, clampedX, clampedY)
    }
}
```

`onTouchEvent`で`ACTION_DOWN`の時、つまりタッチの開始時に`requestDisallowInterceptTouchEvent(true)`をコールし、タッチイベントを奪わないようにリクエストし、`onOverScrolled`の`clampedX`または`clampedY`が`true`の時に `requestDisallowInterceptTouchEvent(false)`をコールし、親のSwipeRefreshLayoutにタッチイベントを奪えるようリクエストします。
SwipeRefreshLayoutのことだけを考えるなら横方向のスクロールは必要ないのでclampedYだけで良いです。

注意としては、SwipeRefreshLayoutの場合は、`isNestedScrollingEnabled = true` のコールが必要である点です。

[SwipeRefreshLayoutの実装](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-master-dev:swiperefreshlayout/swiperefreshlayout/src/main/java/androidx/swiperefreshlayout/widget/SwipeRefreshLayout.java;l=837-848)を調べれば分かりますが、子ViewのisNestedScrollingEnabledがfalseの場合、`requestDisallowInterceptTouchEvent`のコールを無視するようになっているためです。

# ViewPager上に置いたWebViewでカルーセルの操作もできるようにする

ViewPager上に置いて、それ自体が横スクロールできるようになっているWebViewでカルーセルを表示した場合、横スクロールの操作はViewPagerに奪われてしまい、Webコンテンツのカルーセルの操作ができなくなります。

<img width="400" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155171/6d92090f-1115-3c46-1d62-740f43f11300.png">

## 解決策

まあ、SwipeRefreshLayoutの解決策と同じです。今度は横方向のスクロール操作の主導権の話なので、clampedXの方を見る必要があります。
そういうわけで、最初から両方見るようにしていたというわけで、isNestedScrollingEnabledはコッチは不要ですが、つけていても不具合とかにはならないかと。

```kotlin
class MyWebView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyle: Int = 0
): WebView(context, attrs, defStyle) {
    override fun onTouchEvent(event: MotionEvent): Boolean {
        if (event.action == MotionEvent.ACTION_DOWN) {
            parent.requestDisallowInterceptTouchEvent(true)
        }
        return super.onTouchEvent(event)
    }

    override fun onOverScrolled(scrollX: Int, scrollY: Int, clampedX: Boolean, clampedY: Boolean) {
        if (clampedX || clampedY) {
            parent.requestDisallowInterceptTouchEvent(false)
        }
        super.onOverScrolled(scrollX, scrollY, clampedX, clampedY)
    }
}
```

以上になります。