Best way to disable ViewPager paging

GuilhE picture GuilhE · Sep 19, 2014 · Viewed 10.3k times · Source

I'm developing an app which have a ViewPager as a menu. Each fragment has a bunch of child views which can be ListView, ScrollView, LinearLayout, etc... One of this fragments have a settings button which toggles a settings panel (LinearLayout wrapper) with a ScrollView and some LinearLayout (buttons) and SeekBar as childs. This settings panel is animated with a slide up or down animation (when dismissed) and when it's visible I disable the ViewPager paging:

@Override
public boolean onTouchEvent(MotionEvent event) {
    if (!pagingEnabled && event.getAction() == MotionEvent.ACTION_MOVE) {
        return true;
    }
    return super.onTouchEvent(event);
}

@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
    if (!pagingEnabled && event.getAction() == MotionEvent.ACTION_MOVE) {
        return true;
    }
    return super.onInterceptTouchEvent(event);
}

public boolean isPagingEnabled() {
    return pagingEnabled;
}

public void setPagingEnabled(boolean pagingEnabled) {
    this.pagingEnabled = pagingEnabled;
}

But this came with a problem, every time the panel is up all it child views wouldn't receive the OnTouchEvent and that's why I've added a GestureDetector.SimpleOnGestureListener:

protected class YScrollDetector extends GestureDetector.SimpleOnGestureListener {
    @Override
    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
        Log.i(C.TAG, "distanceX " + distanceX + " distanceY " + distanceY);
        return Math.abs(distanceY) < Math.abs(distanceX);
    }
}

and changed my ViewPager onInterceptTouchEvent to:

@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
    if (!pagingEnabled && event.getAction() == MotionEvent.ACTION_MOVE && (mGestureDetector != null && mGestureDetector.onTouchEvent(event))) {
        return true;
    }
    return super.onInterceptTouchEvent(event);
}

This works, the panel buttons receive their onClick, the ListView swipes, etc... but this doesn't work so perfect because Math.abs(distanceY) < Math.abs(distanceX) it's not that accurate. If I fast swipe up and down or diagonally or if I touch a button with a minor swipe the onInterceptTouchEvent will return true because mGestureDetector.onTouchEvent(event) will return true too.

After some google search I came across this:

viewPager.requestDisallowInterceptTouchEvent(true);

And I tried something like this:

myListView.setOnTouchListener(new View.OnTouchListener() {
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        viewPager.requestDisallowInterceptTouchEvent(true);
        return false;
    }
});

And it works really well because ViewPager.onInterceptTouchEvent it's called first with MotionEvent.ACTION_DOWN and then myListView.setOnTouchListener it's called right after and disallows the remaining MotionEvent actions (MOVE and UP) and I can fast swipe up, down, sides ways, etc. The ViewPager wont page but the ListView swipes like a charm.

But a problem still remains, I've to add this requestDisallowInterceptTouchEvent(true) to all child views onTouchEvent, and it's not elegant code.

So my question is, I am on the right path? Is there anyway to avoid adding this listener to all the panel child views (of course if I have to I'll do it in the most generic way)?

Thanks for your time.

Answer

Karakuri picture Karakuri · Sep 19, 2014

Disabling paging should be as simple as returning false from both onInterceptTouchEvent and onTouchEvent. You shouldn't need extra gesture detection to get around the ViewPager. Extend ViewPager like this:

public class MyViewPager extends ViewPager {
    private boolean pagingEnabled = true;

    public MyViewPager(Context context) {
        super(context);
    }

    public MyViewPager(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public void setPagingEnabled(boolean pagingEnabled) {
        this.pagingEnabled = pagingEnabled;
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        return pagingEnabled && super.onInterceptTouchEvent(event);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        return pagingEnabled && super.onTouchEvent(event);
    }

}

I use the same thing (with a different name) for the same reason in one of my apps and it definitely works.