36

I am using the ViewPager consisting of 6 pages to display some data. I want to be able to call a method when the user is at position 0 and tries to swipe to the right (backwards), or at position 5 and tries to swipe to the left (forward), even though no more pages exist for these directions. Is there any way I can listen for these scenarios?

Flávio Faria
  • 6,575
  • 3
  • 39
  • 59
Nicklas
  • 445
  • 1
  • 5
  • 8
  • Hi all, I have used the code but the onSwipeOutLister method is not called in my activity even if I call the method. please see my question posted http://stackoverflow.com/questions/25889115/android-viewpager-detect-swipe-beyond-the-bounds – M.A.Murali Sep 17 '14 at 11:21

6 Answers6

51

Extend ViewPager and override onInterceptTouchEvent() like this:

public class CustomViewPager extends ViewPager {

    float mStartDragX;
    OnSwipeOutListener mListener;


    public void setOnSwipeOutListener(OnSwipeOutListener listener) {
        mListener = listener;
    }


    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        float x = ev.getX();
        switch (ev.getAction()) {
        case MotionEvent.ACTION_DOWN:
            mStartDragX = x;
            break;
        case MotionEvent.ACTION_MOVE:
            if (mStartDragX < x && getCurrentItem() == 0) {
                mListener.onSwipeOutAtStart();
            } else if (mStartDragX > x && getCurrentItem() == getAdapter().getCount() - 1) {
                mListener.onSwipeOutAtEnd();
            }
            break;
        }
        return super.onInterceptTouchEvent(ev);
    }

    public interface OnSwipeOutListener {
        public void onSwipeOutAtStart();
        public void onSwipeOutAtEnd();
    }

}
Flávio Faria
  • 6,575
  • 3
  • 39
  • 59
  • I have created a class called CustomViewPager and implemented the method above. How do I communicate back with my activity when one of the If statement conditions are met? Is there some sort of way of setting up a listener or something in my activity using the ViewPager object? – Nicklas Nov 14 '12 at 12:47
  • I updated my answer. You just need to call `setOnSwipeOutListener()` in your activity. I didn't run this code, so it may need to make a few ajustments. – Flávio Faria Nov 14 '12 at 14:33
  • Hi all, I have used the code but the onSwipeOutLister method is not called in my activity even if I call the method. please see my question posted http://stackoverflow.com/questions/25889115/android-viewpager-detect-swipe-beyond-the-bounds – M.A.Murali Sep 17 '14 at 11:20
  • For me onInterceptTouchEvent not generating continuous MOVE event. Any suggestion here http://stackoverflow.com/q/28605921/2624806. – CoDe Feb 19 '15 at 13:04
  • @M.A.Murali Check my answer to see how to hook up your activity to this `CustomViewPager` - http://stackoverflow.com/a/31275349/1617737 – ban-geoengineering Jul 07 '15 at 17:23
  • @FlávioFaria its gettting called twice – Narendra Singh Oct 24 '15 at 09:52
18

Thanks a lot to @Flavio, although i had to do some changes to his code because the callbacks methods were firing twice, also I added code to check if there was any registered listener, to avoid app crashing when there is no listener registered. This is the code I used to make it work, with both onSwipeOutAtStart and onSwipeOutAtEnd:

public class CustomViewPager extends android.support.v4.view.ViewPager {

    float mStartDragX;
    OnSwipeOutListener mOnSwipeOutListener;

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

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

    public void setOnSwipeOutListener(OnSwipeOutListener listener) {
        mOnSwipeOutListener = listener;
    }

    private void onSwipeOutAtStart() {
        if (mOnSwipeOutListener!=null) {
            mOnSwipeOutListener.onSwipeOutAtStart();
        }
    }

    private void onSwipeOutAtEnd() {
        if (mOnSwipeOutListener!=null) {
            mOnSwipeOutListener.onSwipeOutAtEnd();
        }
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        switch(ev.getAction() & MotionEventCompat.ACTION_MASK){
            case MotionEvent.ACTION_DOWN:
                mStartDragX = ev.getX();
                break;
        }
        return super.onInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev){

        if(getCurrentItem()==0 || getCurrentItem()==getAdapter().getCount()-1){
            final int action = ev.getAction();
            float x = ev.getX();
            switch(action & MotionEventCompat.ACTION_MASK){
                case MotionEvent.ACTION_MOVE:
                    break;
                case MotionEvent.ACTION_UP:
                    if (getCurrentItem()==0 && x>mStartDragX) {
                        onSwipeOutAtStart();
                    }
                    if (getCurrentItem()==getAdapter().getCount()-1 && x<mStartDragX){
                        onSwipeOutAtEnd();
                    }
                    break;
            }
        }else{
            mStartDragX=0;
        }
        return super.onTouchEvent(ev);

    }

    public interface OnSwipeOutListener {
        void onSwipeOutAtStart();
        void onSwipeOutAtEnd();
    }
}
RmK
  • 1,408
  • 15
  • 28
spacebiker
  • 3,777
  • 4
  • 30
  • 49
  • 3
    Works a treat (with only one callback for each swipe!). BTW, you may want to make your activity `implements CustomViewPager.OnSwipeOutListener` and, also in your activity, don't forget to call `myCustomViewPager.setOnSwipeOutListener(this);` on your CustomViewPager object. – ban-geoengineering Jul 07 '15 at 17:33
  • The best solution! Thank you! – Yuliia Ashomok Jul 26 '16 at 09:35
  • It is not work, setCurrentItem performed but not swipe, on MotionEvent.ACTION_UP it is not work but on MotionEvent.ACTION_MOVE swipe but not correctly. On activity I use setCurrentItem with thread and worked, I will add answer. – Kanan Jul 18 '23 at 09:58
  • Dear @Kanan, did you make your activity implement CustomViewPager.OnSwipeOutListener and in your activity, do you call myCustomViewPager.setOnSwipeOutListener(this); on your CustomViewPager object? – spacebiker Aug 20 '23 at 09:04
  • @spacebiker yes – Kanan Aug 25 '23 at 15:15
17

The answer from Flávio Faria doesn't work for me. The only event I get in onInterceptTouchEvent() is ACTION_DOWN event. So I override the onTouchEvent() method to get it work.

Here is the code. Note that I only have onSwipeOutAtEnd() in the listener. You can add your code to support swiping left on first vier.

public class CustomViewPager extends ViewPager {

float mStartDragX;
OnSwipeOutListener mListener;


public void setOnSwipeOutListener(OnSwipeOutListener listener) {
    mListener = listener;
}

@Override
public boolean onTouchEvent(MotionEvent ev){
    if(getCurrentItem()==getAdapter().getCount()-1){
        final int action = ev.getAction();
        float x = ev.getX();
        switch(action & MotionEventCompat.ACTION_MASK){
        case MotionEvent.ACTION_DOWN:
            mStartDragX = x;
            break;
        case MotionEvent.ACTION_MOVE:
            break;
        case MotionEvent.ACTION_UP:
            if (x<mStartDragX){
                mListener.onSwipeOutAtEnd();
            }else{
                mStartDragX = 0;
            }
            break;
        }
    }else{
        mStartDragX=0;
    }
    return super.onTouchEvent(ev);
}    
public interface OnSwipeOutListener {
    public void onSwipeOutAtEnd();
}
tjlian666
  • 343
  • 5
  • 17
  • You'll be receiving only `ACTION_DOWN` in `onInterceptTouchEvent()` if you return `true` from that method. – Flávio Faria Jan 06 '14 at 20:01
  • Hi all, I have used the code but the onSwipeOutLister method is not called in my activity even if I call the method. please see my question posted http://stackoverflow.com/questions/25889115/android-viewpager-detect-swipe-beyond-the-bounds – M.A.Murali Sep 17 '14 at 11:20
  • 1
    I actually found out that receiving `move` depends on the contents of the pager. I used to add `LinearLayout` and I was not receiving the move. When I wrapped the layout in `ScrollView` then it started working - a bit more complex layout, though. – Boris Strandjev Nov 29 '14 at 09:19
  • You saved my day my friend ! Thank you ! – anthony Nov 26 '16 at 15:27
16

How about setting an OnPageChangeListener on your ViewPager? Then you can modify your navigation arrows or whatever in the onPageScrolled.

viewPager.setOnPageChangeListener(new OnPageChangeListener() {
    @Override
    public void onPageSelected(int position) {
    }

    @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
            updateNavigationArrows();
    }

    @Override
    public void onPageScrollStateChanged(int state) {
    }
});
MrMox
  • 191
  • 2
  • 4
  • 5
    The method `setOnPageChangeListener()` is deprecated in newer SDKs and was replaced with `addOnPageChangeListener()`. – Leukipp Dec 16 '15 at 11:44
4

I wonder how it worked for others, it's not working for me.

onInterceptTouchEvent() only takes ACTION_DOWN event. While onTouchEvent() only takes one event at a time either ACTION_DOWN, ACTION_UP and others.

I had to override both onInterceptTouchEvent() and onTouchEvent() to make it work properly. The ACTION_DOWN of onInterceptTouchEvent grabs initial X point of touch, and OnTouchEvent grabs final X2 point. Which I compared it to detect swipe direction.

Get initial X value of the touch:

float x1 = 0;
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
        switch(ev.getAction() & MotionEventCompat.ACTION_MASK){
        case MotionEvent.ACTION_DOWN:
            x1 = ev.getX();
            break;
        }
        return super.onInterceptTouchEvent(ev);
    }

From tjlian616's code, the onTouchEvent() listens to ACTION_UP and gets it's last X Value. While I've set my current item's value to be 0 to get swipe out at start listener. Compared it and add fired up the listener.

 @Override
 public boolean onTouchEvent(MotionEvent ev){
        if(getCurrentItem()==0){
            final int action = ev.getAction();
            switch(action & MotionEventCompat.ACTION_MASK){
            case MotionEvent.ACTION_MOVE:
                break;
            case MotionEvent.ACTION_UP:
                mStartDragX = ev.getX();
                if (x1<mStartDragX){
                    Log.i("TOUCH: ", "ACTION UP " + x1 + " : " + mStartDragX );
                    mListener.onSwipeOutAtStart();
                }else{
                    Log.i("TOUCH ELSE : ", "ACTION UP " + x1 + " : " + mStartDragX );
                    mStartDragX = 0;
                }
                break;
            }
        }else{
            mStartDragX=0;
        }
        return super.onTouchEvent(ev);
    }    
user936597
  • 191
  • 2
  • 9
1

I used @spacebiker answer but It is not work, setCurrentItem performed but not swipe, on MotionEvent.ACTION_UP it is not work but on MotionEvent.ACTION_MOVE swipe but not correctly. On activity I use setCurrentItem with thread and worked.

public class ViewPagerFixed extends androidx.viewpager.widget.ViewPager {

    float mStartDragX;
    OnSwipeOutListener mOnSwipeOutListener;

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

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

    public void setOnSwipeOutListener(OnSwipeOutListener listener) {
        mOnSwipeOutListener = listener;
    }

    private void onSwipeOutAtStart() {
        if (mOnSwipeOutListener != null) {
            mOnSwipeOutListener.onSwipeOutAtStart();
        }
    }

    private void onSwipeOutAtEnd() {
        if (mOnSwipeOutListener != null) {
            mOnSwipeOutListener.onSwipeOutAtEnd();
        }
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if ((ev.getAction() & MotionEventCompat.ACTION_MASK) == MotionEvent.ACTION_DOWN) {
            mStartDragX = ev.getX();
        }
        try {
            return super.onInterceptTouchEvent(ev);
        } catch (IllegalArgumentException e) {
            return false;
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {

        if (getCurrentItem() == 0 || getCurrentItem() == getAdapter().getCount() - 1) {
            float x = ev.getX();
            if ((ev.getAction() & MotionEventCompat.ACTION_MASK) == MotionEvent.ACTION_UP) {
                if (getCurrentItem() == 0 && x > mStartDragX) {
                    onSwipeOutAtStart();
                }
                if (getCurrentItem() == getAdapter().getCount() - 1 && x < mStartDragX) {
                    onSwipeOutAtEnd();
                }
            }
        } else {
            mStartDragX = 0;
        }
        return super.onTouchEvent(ev);
    }

    public interface OnSwipeOutListener {
        void onSwipeOutAtStart();

        void onSwipeOutAtEnd();
    }
}

On Activity implements OnSwipeOutListener

public class ExapleActivity extends AppCompatActivity implements ViewPagerFixed.OnSwipeOutListener{

    private ViewPagerFixed viewPager;
    private WormDotsIndicator dot;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.example_layout);
        
        viewPager = findViewById(R.id.view_pager);
        dot = findViewById(R.id.dot);

        ViewPagerAdapterUploadImage adapter = new ViewPagerAdapterUploadImage(this, this, viewPager);
        viewPager.setAdapter(adapter);
        dot.setViewPager(viewPager);
        viewPager.setOnSwipeOutListener(this);
    }

    @Override
    public void onSwipeOutAtStart() {
        new Thread(() -> {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }

            this.runOnUiThread(() -> {
                viewPager.setCurrentItem(viewPager.getAdapter().getCount() - 1, true);
            });

        }).start();
    }
    
    @Override
    public void onSwipeOutAtEnd() {
        new Thread(() -> {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }

            this.runOnUiThread(() -> {
                viewPager.setCurrentItem(0, true);
            });

        }).start();
    }
}
Kanan
  • 203
  • 1
  • 15