Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Horizontally Scrolling WebView Inside ViewPager

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.

  1. Why is there not a method to get the actual content width of the WebView's HTML content like there is with getContentHeight()? This would give us what we need, but unfortunately it's not provided in the API.
  2. Has anyone seen a workaround for this issue that actually works?
like image 634
airowe Avatar asked Jan 26 '17 14:01

airowe


2 Answers

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

        }

    })
like image 110
airowe Avatar answered Oct 30 '22 07:10

airowe


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.

like image 42
Todor Kostov Avatar answered Oct 30 '22 06:10

Todor Kostov