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)
}
}
onTouchEvent
でACTION_DOWN
の時、つまりタッチの開始時にrequestDisallowInterceptTouchEvent(true)
をコールし、タッチイベントを奪わないようにリクエストし、onOverScrolled
のclampedX
またはclampedY
がtrue
の時に 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)
}
}
以上になります。