4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Posted at

SwipeRefreshLayoutでPull to Refresh

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

<?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の操作はうまく切り分けてくれます。

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

解決策

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

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)
    }
}

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

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

SwipeRefreshLayoutの実装を調べれば分かりますが、子ViewのisNestedScrollingEnabledがfalseの場合、requestDisallowInterceptTouchEventのコールを無視するようになっているためです。

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

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

解決策

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

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)
    }
}

以上になります。

4
2
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
4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?