69

I have an Activity with a ViewPager which displays a bunch of pictures. When it starts the ViewPager's position is set based on what the user selected in a previous Activity. Similar to a gallery.

I want the onPageSelected to be called every time a new page is selected, i.e. when the Activity is first opened or when the user slides to a new page.

I set the starting point like so:

 mPager.setCurrentItem(index);

Everything works, except when setCurrentItem called with index set to 0 as this will not trigger onPageSelected.

mPager.setOnPageChangeListener(new OnPageChangeListener() {
  @Override
  public void onPageSelected(int index) {
    Log.d(TAG, "onPageSelected " + index);
  }
  ...
}

So my question is; is this a bug, and if so what can I do about it?

jpihl
  • 7,941
  • 3
  • 37
  • 50

10 Answers10

92

The cleanest solution I've found to this so far is to take a reference to the onPageChangeListener you set on the ViewPager (since I don't think there's a ViewPager.getOnPageChangeListener() method), then after you've set the ViewPager's adapter, call:

onPageChangeListener.onPageSelected(viewPager.getCurrentItem());

However, the fragment for the page at the current index won't have been instantiated yet (at least if you're using FragmentStatePagerAdapter), so you may need to wrap it in a runnable, ala:

viewPager.post(new Runnable(){
@Override
    public void run() {
        onPageChangeListener.onPageSelected(viewPager.getCurrentItem());
    }
});

Also, if within the onPageSelected handler you need a reference to the fragment, you'll have to do that yourself. I use an abstract base class for my FragmentStatePagerAdapter which overrides the instantiate and destroy methods, and adds/removes the fragments from a SparseArray.

technicalflaw
  • 1,219
  • 12
  • 8
35

Ok, so I haven't been able to figure out whether this is a bug (or a feature). But I thought I'd share how a possible solution to the problem could look..

Write the functionality you wish to perform in a method in the Activity and then call this in the onPageSelected method.

mPager.setOnPageChangeListener(new OnPageChangeListener() {
    @Override
    public void onPageSelected(int index) {
        myOnPageSelectedLogic(index);
    }
    ...
}

And then right after calling

setCurrentItem(index);

in the Activity, add the following if statement

if(index == 0) {
    myOnPageSelectedLogic(0);
}

It's not super pretty but I hope it helps someone :)

jpihl
  • 7,941
  • 3
  • 37
  • 50
  • And if i want to listen to page change on fargment itself, what can i do? – Dr.jacky Jul 26 '14 at 14:07
  • 2
    Actually this is a bad one, since OnPageChangeListener is called after some async work, and with this "trick" you won't have proper work in many cases – Defuera Aug 20 '14 at 09:57
  • @Defuera, I don't understand your concern. Could you elaborate, or maybe submit a better solution? – jpihl Aug 20 '14 at 11:22
  • 3
    better solution is to call setCurrentItem(index); inside view.post(new Runnable(){ setCurrentItem(index); } ); this code guarantee that OnPageChangeListener will be called. at least in my cases – Defuera Aug 20 '14 at 11:52
  • @jpihl, what would myOnPageSelectedLogic be? – Kala J Nov 05 '14 at 01:51
  • That is specific to your application. The functionality that you originally wanted to be in the OnPageSelected callback. – jpihl Nov 05 '14 at 07:43
27

Actually I think this is a BUG. I've check the source code of ViewPager and found the only trigger of onPageSelected:

if (dispatchSelected && mOnPageChangeListener != null) {
            mOnPageChangeListener.onPageSelected(item);
        }

Above, variable dispatchSelected is determined by code:

final boolean dispatchSelected = mCurItem != item; 
//item is what you passed to setCurrentItem(item)

And, variable mCurItem is defined and initialized as:

private int mCurItem; // Index of currently displayed page.

So mCurItem is default to 0. When calling setCurrentItem(0), dispatchSelected will be false thus onPageSelected() will not be dispatched.

Now we should understand why calling setCurrentItem(0) is a problem while setCurrentItem(1, or 2, 3...) is not.

How to solve this problem:

  1. Copy the code of ViewPager to your own project and fix it:

    from 
    final boolean dispatchSelected = mCurItem != item; //the old line
    to
    final boolean dispatchSelected = mCurItem != item || mFirstLayout; //the new line
    

    then use your own ViewPager.

  2. Since you consciously called setCurrentItem(0) and have a refrence to the OnPageChangedListener, dispatch the onPageSelected() by yourself.

  3. 听楼下怎么说...

ldn0x7dc
  • 280
  • 4
  • 8
3

The following solution seems to work for me. i.e. I get a callback at position 0 when the viewpager is first loaded and for all subsequent selections either from user scrolling or a setCurrentItem(x) call. I haven't observed any undesired behaviour.

viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
            @Override
            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {

                if (positionOffsetPixels == 0) {
                 //Do something on selected page at position
                }

            }

            @Override
            public void onPageSelected(int position) {}

            @Override
            public void onPageScrollStateChanged(int state) {}
        });
maturecheese
  • 92
  • 1
  • 9
0

onPageSelected is not triggered for page which is opened first. For this case I manually perform all needed actions. Also, I suppose that call to onPageSelected(0) should work too.

marwinXXII
  • 1,456
  • 14
  • 21
  • 1
    Well, it is triggered for the page which is opened first, that is if the page is not the first one. I know it sounds weird, but that is the reason for my question. – jpihl Aug 03 '12 at 17:57
  • 2
    Triggering 'onPageSelected()' might result in a NullPointer! – paulgavrikov Oct 20 '13 at 05:48
0

You can extend the ViewPager and call the Listener by yourself when the position is changed from 0 to 0

public class BetterViewPager extends ViewPager {

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

        // we some the listner
        protected OnPageChangeListener listener;

        @Override
        public void setOnPageChangeListener(OnPageChangeListener listener) {
            super.setOnPageChangeListener(listener);
            this.listener = listener;
        }

        @Override
        public void setCurrentItem(int item) {
            // when you pass set current item to 0,
            // the listener won't be called so we call it on our own
            boolean invokeMeLater = false;

            if(super.getCurrentItem() == 0 && item == 0)
                invokeMeLater = true;

            super.setCurrentItem(item);

            if(invokeMeLater && listener != null)
                listener.onPageSelected(0);
        }

}
Community
  • 1
  • 1
Kirill Kulakov
  • 10,035
  • 9
  • 50
  • 67
  • This is really close to an answer, but the child fragment hasn't been attached yet, so if you attempt to call getParentFragment or getActivity it will fail. Is there somewhere else this can be done so guarantee that the fragment has been attached? – mtmurdock Nov 05 '14 at 21:21
  • who's talking about activity and fragments in this answer? – Marian Paździoch Jan 07 '19 at 11:10
0
public class PageChangeListener implements ViewPager.OnPageChangeListener {
    @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {

    }

    @Override
    public void onPageSelected(int position) {
    }

    @Override
    public void onPageScrollStateChanged(int state) {
        if (state == ViewPager.SCROLL_STATE_IDLE && getCurrentItem() == 0) {
         //this indicate viewpager finish scroll and page at position 0 is selected.
        }
Dartan Li
  • 217
  • 2
  • 5
0

The simplest solution will be using of function e.g.

    viewpager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {

        @Override
        public void onPageScrollStateChanged(int state) {}

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

        @Override
        public void onPageSelected(int position) {
            onPage(position);
        }
    });

    // call first time
    onPage(0); 

    private void onPage(int position) {
        //...
    }
Vlad
  • 7,997
  • 3
  • 56
  • 43
0

If the ViewPager is in a fragment, in the onViewCreated method, call mPager.setOnPageChangeListener(this)

Erik K.
  • 1,024
  • 12
  • 13
-5

if you don't care screen animotion try

pager.setCurrentItem(1);
pager.setCurrentItem(0);
羅旭辰
  • 54
  • 2