There have been a few questions asked on this issue, but none have given a true solution that is working for us. We have a ViewPager containing a dynamic number of webviews (inside fragments) that will sometimes need to be scrolled horizontally.
The problem we're seeing is that when scrolling horizontally, those scroll events (or drag, or fling) are being intercepted by the ViewPager, which then prompts a page switch in the direction of the action.
The WebView is either not reporting the width of its content to its parent, or the ViewPager is indiscriminately capturing the actions before it should.
I tried using the computeHorizontalScroll() solutions, which seemed promising, but that method is always returning 0 and does me no good.
So, I ended up developing a solution by disallowing propagation of touch events to the ViewPager and scrolling pages when the overScrollBy event of the WebView is triggered. You can determine the direction you want to scroll in by checking the deltaX of the event. Here's a kotlin solution:
override fun overScrollBy(deltaX: Int, deltaY: Int, scrollX: Int, scrollY: Int, scrollRangeX: Int, scrollRangeY: Int, maxOverScrollX: Int, maxOverScrollY: Int, isTouchEvent: Boolean): Boolean {
if(parent.parent is WebViewPager) {
val viewPager: WebViewPager = (parent.parent as WebViewPager)
if(viewPager.scrollState == ViewPager.SCROLL_STATE_IDLE) {
if(deltaX > 0) {
if(checkAdapterLimits(1, viewPager.currentItem)) //right
(parent.parent as ViewPager).setCurrentItem(viewPager.currentItem + 1,true)
}
else {
if(checkAdapterLimits(-1, viewPager.currentItem)) //left
(parent.parent as ViewPager).setCurrentItem(viewPager.currentItem - 1,true)
}
}
}
return false
}
override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
parent.requestDisallowInterceptTouchEvent(true)
return super.onInterceptTouchEvent(event)
}
private fun checkAdapterLimits(direction: Int, position: Int) : Boolean {
return if(direction < 0) //left
position >= 1
else //right
position < (parent.parent as ViewPager).adapter.count - 1
}
Edited, in order to stop scrolling to just one page at a time, you need to check the scroll state of the ViewPager to make sure it's idle.
You'll need to add a property to your custom ViewPager and toggle it with an OnPageChangedListener on the ViewPager instance you're using.
ViewPager:
class WebViewPager(context: Context, attrs: AttributeSet) : ViewPager(context, attrs) {
var scrollState: Int = SCROLL_STATE_IDLE
fun setDurationScroll(millis: Int) {
try {
val viewpager = ViewPager::class.java
val scroller = viewpager.getDeclaredField("mScroller")
scroller.isAccessible = true
scroller.set(this, OwnScroller(context, millis))
} catch (e: Exception) {
e.printStackTrace()
}
}
inner class OwnScroller(context: Context, durationScroll: Int) : Scroller(context, DecelerateInterpolator()) {
private var durationScrollMillis = 1
init {
this.durationScrollMillis = durationScroll
}
override fun startScroll(startX: Int, startY: Int, dx: Int, dy: Int, duration: Int) {
super.startScroll(startX, startY, dx, dy, durationScrollMillis)
}
}
}
OnPageChangedListener:
viewPager!!.setDurationScroll(300)
viewPager!!.addOnPageChangeListener(object : ViewPager.OnPageChangeListener {
override fun onPageScrollStateChanged(state: Int) {
viewPager.scrollState = state
}
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
}
override fun onPageSelected(position: Int) {
}
})
The ViewPager is the parent of your WebView. You can try to detect the swipe motion in your ViewPager and pass it directly to it's child.
You can check the official documentation for Managing Touch Events and you can check this Stack Overflow discussion. Check the accepted answer and more specifically the onInterceptTouchEvent() method.
That can give you some ideas.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With