35

I want to allow the user swipe in a ViewPager only from right to left. So once he passed a page he can't come back to it. How can this be done?

I tried this solution:

public class CustomViewPager extends ViewPager {

float lastX = 0;

boolean lockScroll = false;

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

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

@Override
public boolean onTouchEvent(MotionEvent ev) {

    switch (ev.getAction()) {
    case MotionEvent.ACTION_DOWN:
        lastX = ev.getX();
        lockScroll = false;
        return super.onTouchEvent(ev);
    case MotionEvent.ACTION_MOVE:

        if (lastX > ev.getX()) {
            lockScroll = false;
        } else {
            lockScroll = true;
        }

        lastX = ev.getX();
        break;
    }

    lastX = ev.getX();

    if(lockScroll) {
        return false;
    } else {
        return super.onTouchEvent(ev);
    }
}
}

But it still allows me to poorly swipe in the other direction.

Community
  • 1
  • 1
Emil Adz
  • 40,709
  • 36
  • 140
  • 187
  • What happens if you add a scrolling listener to the pager? (and in the onScrolled you do something about it?) – Martin Marconcini Oct 26 '13 at 04:31
  • 1
    possible duplicate of [One side ViewPager swiping only](http://stackoverflow.com/questions/12276846/one-side-viewpager-swiping-only) – C-- Oct 26 '13 at 05:10
  • 1
    @MartínMarconcini, could you show a little code snippet of what you mean? and where should I add this listener? in the Activity or the ViewPager? – Emil Adz Oct 26 '13 at 13:24
  • 1
    @SubinSebastian, the solution suggested there is to modify the ViewPager class and take it as a whole to you project. I looking for something simpler. – Emil Adz Oct 26 '13 at 14:20
  • Well, no, if you want to stop it from scrolling, you have to subclass your own ViewPager and avoid it from moving to the left… I'd have to think a little bit more about this. It may not be too easy. – Martin Marconcini Oct 31 '13 at 06:48
  • 1
    Got exactly the same task today. Solved the problem by overriding `ViewPager.canScroll` method and returning `true` if scroll should be disabled. You can find the solution here - http://pastebin.com/sCcFu0Yn. Is has some problems and sometimes still allows the scrolling but it's perfectly suitable for me. Let me know if it helps you and I will post the answer. – Vladimir Mironov Nov 17 '13 at 16:10
  • @vmironov, Thanks. I'm currently working on another project, but I will try you solutions and let you know. – Emil Adz Nov 17 '13 at 16:15

4 Answers4

78

There is one more event you miss: onInterceptTouchEvent. It`s must contain the same logic as onTouchEvent.

My complete solution is based on this answer. It will allow you to enable/disable paging in any direction in any time you need.

1. Create enum

 public enum SwipeDirection {
    ALL, LEFT, RIGHT, NONE ;
}

2.Extend ViewPager (in Java)

public class CustomViewPager extends ViewPager {

    private float initialXValue;
    private SwipeDirection direction;

    public CustomViewPager(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.direction = SwipeDirection.ALL;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (this.isSwipeAllowed(event)) {
            return super.onTouchEvent(event);
        }

        return false;
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        if (this.isSwipeAllowed(event)) {
            return super.onInterceptTouchEvent(event);
        }

        return false;
    }

    private boolean isSwipeAllowed(MotionEvent event) {
        if(this.direction == SwipeDirection.ALL) return true;

        if(direction == SwipeDirection.NONE )//disable any swipe
            return false;

        if(event.getAction()==MotionEvent.ACTION_DOWN) {
            initialXValue = event.getX();
            return true;
        }

        if (event.action === MotionEvent.ACTION_MOVE) {
            val diffX = event.x - initialXValue
            if (diffX > 0 && direction === SwipeDirection.RIGHT) {
                // swipe from left to right detected
                return false
            } else if (diffX < 0 && direction === SwipeDirection.LEFT) {
                // swipe from right to left detected
                return false
            }
        }

        return true;
    }

    public void setAllowedSwipeDirection(SwipeDirection direction) {
        this.direction = direction;
    }
}

2.Extend ViewPager (in Kotlin)

enum class SwipeDirection {
    ALL, LEFT, RIGHT, NONE
}

class SingleDirectionViewPager @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet?
) : ViewPager(context, attrs) {

    private var initialXValue = 0f
    private var direction: SwipeDirection = SwipeDirection.ALL

    override fun onTouchEvent(event: MotionEvent): Boolean =
        if (isSwipeAllowed(event)) {
            super.onTouchEvent(event)
        } else {
            false
        }

    override fun onInterceptTouchEvent(event: MotionEvent): Boolean =
        if (isSwipeAllowed(event)) {
            super.onInterceptTouchEvent(event)
        } else {
            false
        }

    private fun isSwipeAllowed(event: MotionEvent): Boolean {
        if (direction == SwipeDirection.ALL) {
            return true
        }

        if (direction == SwipeDirection.NONE) {
            return false
        }

        if (event.action == MotionEvent.ACTION_DOWN) {
            initialXValue = event.x
            return true
        }

        if (event.action == MotionEvent.ACTION_MOVE) {
            val diffX = event.x - initialXValue

            if (diffX > 0 && direction === SwipeDirection.RIGHT) {
                // swipe from left to right detected
                return false
            } else if (diffX < 0 && direction === SwipeDirection.LEFT) {
                // swipe from right to left detected
                return false
            }
        }

        return true
    }

    fun setAllowedSwipeDirection(direction: SwipeDirection) {
        this.direction = direction
    }
}

3.Use your viewPager in a layout

 <package_name.customviewpager 
     android:id="@+id/customViewPager" 
     android:layout_height="match_parent" 
     android:layout_width="match_parent" />

4.Enable any swipe direction in code. Default is all (right and left)

mViewPager.setAllowedSwipeDirection(SwipeDirection.RIGHT);
Boken
  • 4,825
  • 10
  • 32
  • 42
andre719mv
  • 1,478
  • 13
  • 14
  • Using the example that I have provided below, the bug is still there but the pager will never allow the user to go back to old page, sure you can swipe one way and then back the other and see the old page but the Pager will never switch back to the old page and will bounce forward to the new page selected. So effectively, you CANNOT swipe back to an old page. – JamisonMan111 Aug 09 '17 at 06:55
2
package com.contacts_app.jamison.contacts__proprivacy4;

import android.content.Context;
import android.content.res.Resources;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;

public class ViewPager_Settings extends ViewPager
{
private final String TAG = ViewPager_Settings.class.getSimpleName();
public float startX;

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

 ////////////////////////////////////////////////////////////////////////////////////////////////
public static int dpTOpx(double dp)
{
    return (int) (dp * Resources.getSystem().getDisplayMetrics().density);
}
public static int pxTOdp(double px)
{
    return (int) (px / Resources.getSystem().getDisplayMetrics().density);
}
////////////////////////////////////////////////////////////////////////////////////////////////

/*****DispatchTouchEvent for the View Pager to intercept and block swipes Right*****/
@Override
public boolean dispatchTouchEvent(MotionEvent ev)
{
    final int actionMasked = ev.getActionMasked() & MotionEvent.ACTION_MASK;
    //int movement_limit = pxTOdp(50);
    switch (actionMasked)
    {
        case (MotionEvent.ACTION_DOWN):
        {
            startX = ev.getX();
            Log.i(TAG, "startX: " + startX);

            /*Should always be this below*/
            return super.dispatchTouchEvent(ev);
        }
        case (MotionEvent.ACTION_MOVE):
        {
            Log.i(TAG, "ev.getX() - startX:" + (ev.getX() - startX));

            /*Switching directional changes would be a matter of flipping the  "<" sign in the line below.*/
            if (ev.getX() - startX > 0)
            {
                /*The result is that the ViewPager will not swipe from left*/
                ev.setAction(MotionEvent.ACTION_CANCEL);;
            }

            /*Should always be this below*/
            super.dispatchTouchEvent(ev);
        }
        /**The ACTION_UP case statement is only needed if you don't want to pass down the touch event 
        * to buttons that may receive the click after the swipe is blocked.*/
        /*case (MotionEvent.ACTION_UP):
        {
            //Log.i(TAG, "movement_limit: " + movement_limit);

            //(-50) may need to be changed to something more broader in scope to accompany all screen densities
            if ( (ev.getX() - startX) < (-50) )
            {
                ev.setAction(MotionEvent.ACTION_CANCEL);
            }

            //Should always be this below
            super.dispatchTouchEvent(ev);
        }*/
    }
    /*Should always be this below*/
    return super.dispatchTouchEvent(ev);
}
 ////////////////////////////////////////////////////////////////////////////////////////////////

}/*****END OF FILE*****/

Don't forget to change the line at the top to put the package name of your App. Also most, if not all, of the comments give insight into what the code is doing in case you decide you want to tinker with things.

0

Define your adapter like this

public class MyFragmentStatePagerAdapter extends FragmentStatePagerAdapter {

    private final int totalPages = 10;
    private int currentPage = 0;

    public MyFragmentStatePagerAdapter(FragmentManager fm) {
        super(fm);
    }

    @Override
    public Fragment getItem(int position) {
        // Use whatever logic you want here to
        // to select a fragment based on
        // currentPage instead of position

        if (currentPage % 2 == 0) {
            return new Fragment1();
        } else {
            return new Fragment2();
        }
    }

    @Override
    public int getCount() {
        return currentPage == totalPages ? 1 : 2;
    }

    @Override
    public int getItemPosition(Object object){
        return PagerAdapter.POSITION_NONE;
    }

    public void nextPage() {
        currentPage++;
        notifyDataSetChanged();
    }
}

In the fragment that is using the view pager, do this

@Override
public void onPageSelected(int arg0) {
    if (arg0 > 0) {
        pagerAdapter.nextPage();
        pager.setCurrentItem(0, false);
    }
}
Emil Adz
  • 40,709
  • 36
  • 140
  • 187
mpkuth
  • 6,994
  • 2
  • 27
  • 44
  • Note that extending `FragmentStatePagerAdapter` is a mistake here, as it assumes constant items for it's state management. It would be better to extend `PagerAdapter`, and adapt the code from `FragmentStatePagerAdapter` to properly manage the saving and restoration of the dynamic `Fragment` items. – corsair992 Dec 15 '14 at 04:00
  • @corsair992 Can you explain what exactly this breaks or what the undesirable behavior would be? – mpkuth Dec 15 '14 at 04:25
  • `FragmentStatePagerAdapter` saves and restores the state of all the `Fragment` items based on their position, on the assumption that they are statically defined and constant. Therefore dynamically changing a `Fragment` will cause it to be assigned the state of the previous `Fragment` at that position. – corsair992 Dec 15 '14 at 04:34
  • That is why I override `getItemPosition` to always return `POSITION_NONE`. It tells the adapter that it should recreate everything rather than assuming nothing has changed. [This](http://stackoverflow.com/questions/10849552/update-viewpager-dynamically/) seems to agree with me and also provides a better way to implement that override if performance is an issue. – mpkuth Dec 15 '14 at 04:50
  • As I said, `FragmentStatePagerAdapter` isn't designed to handle dynamically changing items. Returning `POSITION_NONE` from `getItemPosition()` would cause the `ViewPager` to refresh the item, but `FragmentStatePagerAdapter` will still assign it the obsolete state associated with the position. The answer that you link to only uses it to dynamically reinstantiate the _same_ `Fragment`, so there is no issue in state management (however the last paragraph about `FragmentPagerAdapter` is incorrect). You can test this by checking the saved state at the creation of a dynamically assigned `Fragment`. – corsair992 Dec 15 '14 at 05:10
  • The dynamics of `FragmentStatePagerAdapter` state management are explained in detail in this blog post that you yourself linked to in another [answer](http://stackoverflow.com/questions/27474270/android-viewpager-fragments-with-dynamic-listviews/27475913#27475913): http://speakman.net.nz/blog/2014/02/20/a-bug-in-and-a-fix-for-the-way-fragmentstatepageradapter-handles-fragment-restoration – corsair992 Dec 15 '14 at 06:47
  • Yes, I am just putting two and two together now. So, if any of the fragments used in the adapter relied on saved instance state it would cause a crash. I didn't see a crash when I tested this (admittedly briefly), so would you have to explicitly use saved instance state in one of the fragments to see an issue or did I just get lucky? – mpkuth Dec 15 '14 at 06:54
  • It will probably not cause a crash if the `View`s have implemented the state restoration in a fail-safe manner. However, you will certainly always have the wrong state restored to the `Fragment`s on swiping. You can see some examples of this in the blog post. Whether the states would actually even apply or not depends on how similar the `Fragment` structures and states are. – corsair992 Dec 15 '14 at 07:05
-1

Try to add (the same logic like in onTouchEvent )

@Override
public boolean onInterceptTouchEvent(MotionEvent arg0) {
    // allow/ not allow swiping to switch between pages
    return !lockScroll ;
}
NickF
  • 5,637
  • 12
  • 44
  • 75