Implementing BiDirectional ViewPager by overriding onInterceptTouchEvent & onTouchEvent

Tan Jun Rong
Android Programmer who likes writing blogs, reading, coffee, snowboarding.
Oct 21, 2018 1090 words
Edit

BiDirectional ViewPager

Android provides us with many widgets for the UI, but sometimes we need a particular behaviour that cannot be achieved by the provided ones. That is when we need to extend the existing one and make a custom view.

Today I need to code a ViewPager which can be swiped in both horizontal and vertical direction. So I need to make a custom view for it. In this post, I will share how it is made. 😃

Wireframe of BiDirectional ViewPager
Wireframe of BiDirectional ViewPager

Demo

BiDirection ViewPager Demo
BiDirection ViewPager Demo

Vertical ViewPager

To achieve this, we need to nest 2 ViewPagers together. The first ViewPager is for the vertical scrolling, let's call it VerticalViewPager. I found an answer in Stackoverflow for achieving this.

In each page of the VerticalViewPager, I place a horizontal ViewPager in it.
Here's a quick explanation of how it works:

Horizontal ViewPager

Now we have a vertical ViewPager. Next, to achieve the bi directional scrolling, we need to nest horizontal ViewPager inside the vertical one.

The diagram shows how it works:

VerticalViewPager contains a horizontal ViewPager in each page
VerticalViewPager contains a horizontal ViewPager in each page

Since ViewPager works horizontally out of the box, so we don't need any special tricks to make it move horizontally. However, when we nest ViewPagers like this, there is a problem:

PROBLEM: both of the ViewPagers are trying to listen to the event!

Therefore, one of the ViewPager won't be working, because the touch event is stolen by the other ViewPager.

overriding onTouchEvent & onInterceptEvent

To solve the problem of both VerticalViewPager and horizontal ViewPager trying to listen to the swipe event, we need to properly distribute the touch event. The 2 important methods for achieving this is the onTouchEvent and onInterceptEvent event. I learned about how these 2 methods work from this tutorial: http://balpha.de/2013/07/android-development-what-i-wish-i-had-known-earlier/

To understand the rest of following post, you can head over to this link above.☝

Distributing the touch event from Vertical ViewPager to Horizontal ViewPager

After reading the link, you should understand that parent view's onInterceptEvent will be run first, followed by the children's onTouchEvent, and followed back to the parent view's onTouchEvent.

In our case, the touch event travels like this:

  1. from VerticalViewPager's onInterceptEvent
  2. then to HorizontalViewPager's onTouchEvent
  3. then finally to VerticalViewPager's onTouchEvent

Let's walkthrough the responsibility of each of them.

  1. from VerticalViewPager's onInterceptEvent
    • if we return true here, VerticalViewPager will intercept the event until ACTION_UP is received (lifting of the finger)
    • we should return true if it is a vertical swipe
    • we should return false, if it is a horizontal swipe
    • so in this method, we need to write a simple gesture detection to determine if the swipe is Vertical or Horizontal
  2. then to HorizontalViewPager's onTouchEvent
    • touch event will reach here, if step 1 return false
    • since ViewPager works horizontally, we do nothing here
  3. then finally to VerticalViewPager's onTouchEvent
    • touch event will reach here, if step 1 return true
    • we need to swap the X and Y input of the touch event to make it scroll vertically
    • we need to inject ACTION_DOWN because it is consumed in step 2 in the previous cycle (this can be hard to understand, but you can move on, more details in)
Flow Chart of Touch Event
Flow Chart of Touch Event

Show Me The Code!

Code for Step 1

So finally, the code for step 1 looks like this:
(Reference: BiDirectionViewPager#onInterceptTouchEvent()#L41)

BiDirectionViewPager#onInterceptTouchEvent()

override fun onInterceptTouchEvent(event: MotionEvent): Boolean { val action = event.actionMasked val currentPoint = Point(event.x.toInt(), event.y.toInt()) if (action == MotionEvent.ACTION_DOWN) { // mark the beginning, when finger touched down initialTouchPoint = Point(currentPoint) } else if (action == MotionEvent.ACTION_UP) { // reset the marking, when finger is lifted up initialTouchPoint = Point(0, 0) } else { val moveDistance = currentPoint.distanceFrom(initialTouchPoint) if (moveDistance > FINGER_MOVE_THRESHOLD) { val direction = MotionUtil.getDirection(initialTouchPoint, currentPoint) // check if the scrolling is vertical if (direction == MotionUtil.Direction.up || direction == MotionUtil.Direction.down) { return true } } } return false }
Explanation

The chunk of code can be divided into 3 if...else... block:

Code for Step 2

We don't need any code here, just use the normal ViewPager!

Code for Step 3

The code for step 3 can be found here:
(Reference: BiDirectionViewPager#onTouchEvent#L65)

BiDirectionViewPager#onTouchEvent()

override fun onTouchEvent(event: MotionEvent): Boolean { // swapping the motionEvent's x and y, so that when finger moves right, it becomes moving down // for VerticalViewPager effect event.swapXY() // this portion is used for injection ACTION_DOWN if (firstTime && event.actionMasked == MotionEvent.ACTION_MOVE) { injectActionDown(event) firstTime = false } if (event.actionMasked == MotionEvent.ACTION_UP) { firstTime = true } super.onTouchEvent(event) return true }
Explanation

We only need to do 2 things here. Firstly, we need to swap the X and Y input of the touch event to make Vertical ViewPager. Secondly, we need to inject ACTION_DOWN event because it is already consumed by the horizontal ViewPager. After that, just delegate the event to super.touchEvent() to do it's job, and return true saying that we've consumed the event.

Caveat (optional read)

The following few points took me days to figure out. Perhaps you won't understand it on the first read, but if you are stuck, come back and read the following few points, it might help!

Tips

Developer options: Show touches & Pointer Location
Developer options: Show touches & Pointer Location

Code Sample is Available at Github!

The link is here: BiDirectionViewPager.kt. Once you clone the project and run it, there will be an example that looks like the demo at the top of this post.

Hope you enjoy the post,

See you in the next post! 👋


Written By

Tan Jun Rong

Android Programmer who likes writing blogs, reading, coffee, snowboarding.

Enjoyed the post?

Clap to support the author, help others find it, and make your opinion count.

Comments

To leave a comment, you need to login first 😉