onPageSelected не запускается при вызове setCurrentItem(0)

У меня есть активность с ViewPager, которая отображает кучу картинок. Когда он запускается, позиция ViewPager устанавливается на основе того, что пользователь выбрал в предыдущем действии. Похоже на галерею.

Я хочу, чтобы onPageSelected вызывался каждый раз при выборе новой страницы, т. е. при первом открытии Activity или при переходе пользователя на новую страницу.

Я установил начальную точку следующим образом:

 mPager.setCurrentItem(index);

Все работает, за исключением случаев, когда setCurrentItem вызывается с индексом, установленным на 0, поскольку это не сработает onPageSelected.

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

Итак, мой вопрос; является ли это ошибкой, и если да, то что я могу с этим поделать?


person jpihl    schedule 03.08.2012    source источник


Ответы (9)


Самое чистое решение, которое я нашел для этого до сих пор, - это взять ссылку на onPageChangeListener, который вы установили на ViewPager (поскольку я не думаю, что есть метод ViewPager.getOnPageChangeListener()), а затем после того, как вы установили адаптер ViewPager , вызов:

onPageChangeListener.onPageSelected(viewPager.getCurrentItem());

Однако фрагмент страницы с текущим индексом еще не будет создан (по крайней мере, если вы используете FragmentStatePagerAdapter), поэтому вам может потребоваться обернуть его в исполняемый файл, например:

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

Кроме того, если в обработчике onPageSelected вам нужна ссылка на фрагмент, вам придется сделать это самостоятельно. Я использую абстрактный базовый класс для моего FragmentStatePagerAdapter, который переопределяет методы создания и уничтожения экземпляров и добавляет/удаляет фрагменты из SparseArray.

person technicalflaw    schedule 29.11.2013
comment
публикация runnable - это просто секретный соус, который мне нужен, чтобы убедиться, что представления viewPager уже созданы, прежде чем вызывать вызов onPageSelected. Хороший! - person RobP; 13.06.2015
comment
Идеально. Наконец я нашел этот ответ - person flosk8; 16.03.2016
comment
Работает как шарм. - person Moeen Kashisaz; 20.07.2017

Итак, я не смог понять, является ли это ошибкой (или функцией). Но я решил поделиться тем, как может выглядеть возможное решение проблемы.

Напишите функциональность, которую вы хотите выполнить, в методе Activity, а затем вызовите ее в методе onPageSelected.

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

А потом сразу после звонка

setCurrentItem(index);

в Activity добавьте следующий оператор if

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

Это не супер красиво, но я надеюсь, что это поможет кому-то :)

person jpihl    schedule 22.08.2012
comment
И если я хочу прослушать смену страниц на самом фаргменте, что я могу сделать? - person Dr.jacky; 26.07.2014
comment
На самом деле это плохо, так как OnPageChangeListener вызывается после некоторой асинхронной работы, и с этим трюком во многих случаях у вас не будет правильной работы. - person Defuera; 20.08.2014
comment
@Defuera, я не понимаю твоего беспокойства. Не могли бы вы уточнить или, может быть, представить лучшее решение? - person jpihl; 20.08.2014
comment
лучшим решением является вызов setCurrentItem(index); внутри view.post (новый Runnable () { setCurrentItem (index); } ); этот код гарантирует, что будет вызван OnPageChangeListener. по крайней мере в моих случаях - person Defuera; 20.08.2014
comment
@jpihl, каким будет myOnPageSelectedLogic? - person Kala J; 05.11.2014
comment
Это специфично для вашего приложения. Функциональность, которую вы изначально хотели иметь в обратном вызове OnPageSelected. - person jpihl; 05.11.2014

На самом деле я думаю, что это БАГ. Я проверил исходный код ViewPager и нашел единственный триггер onPageSelected:

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

Выше переменная dispatchSelected определяется кодом:

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

И переменная mCurItem определяется и инициализируется как:

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

Таким образом, mCurItem по умолчанию равен 0. При вызове setCurrentItem(0) dispatchSelected будет равно false, поэтому onPageSelected() не будет отправлен.

Теперь мы должны понять, почему вызов setCurrentItem(0) является проблемой, а setCurrentItem(1, или 2, 3...) — нет.

Как решить эту проблему:

  1. Скопируйте код ViewPager в свой проект и исправьте его:

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

    затем используйте свой собственный ViewPager.

  2. Поскольку вы сознательно вызвали setCurrentItem(0) и имеете ссылку на OnPageChangedListener, отправьте onPageSelected() самостоятельно.

  3. 听楼下怎么说...

person ldn0x7dc    schedule 17.07.2013
comment
А вот ссылка на исходный код: grepcode.com/file/repository.grepcode.com/java/ext/ - person Nicramus; 23.05.2014
comment
Есть ли способ сделать это без копирования кода? Есть ли способ исправить это в подклассе? - person mtmurdock; 05.11.2014
comment
он говорит, что PagerAdapter.DataSetObserver не может быть разрешен к типу? - person CoDe; 24.12.2014
comment
Хорошая находка. Вы сообщили об этом как о проблеме? - person dumbfingers; 08.04.2015

Следующее решение, кажется, работает для меня. т. е. я получаю обратный вызов в позиции 0 при первой загрузке viewpager и для всех последующих выборов либо из прокрутки пользователя, либо из вызова setCurrentItem(x). Нежелательного поведения не наблюдал.

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) {}
        });
person maturecheese    schedule 22.09.2016

onPageSelected не срабатывает для страницы, которая открывается первой. В этом случае я вручную выполняю все необходимые действия. Кроме того, я полагаю, что вызов onPageSelected(0) тоже должен работать.

person marwinXXII    schedule 03.08.2012
comment
Ну, он срабатывает для страницы, которая открывается первой, то есть если страница не первая. Я знаю, это звучит странно, но это причина моего вопроса. - person jpihl; 03.08.2012
comment
Запуск onPageSelected() может привести к NullPointer! - person paulgavrikov; 20.10.2013

Вы можете расширить ViewPager и вызвать Listener самостоятельно, когда позиция изменится с 0 на 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);
        }

}
person Kirill Kulakov    schedule 16.07.2013
comment
Это действительно близко к ответу, но дочерний фрагмент еще не присоединен, поэтому, если вы попытаетесь вызвать getParentFragment или getActivity, это не удастся. Где-то еще это можно сделать, чтобы гарантировать, что фрагмент был прикреплен? - person mtmurdock; 06.11.2014
comment
кто говорит об активности и фрагментах в этом ответе? - person Marian Paździoch; 07.01.2019

Самым простым решением будет использование функции, например.

    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) {
        //...
    }
person Vlad    schedule 04.05.2018

Если ViewPager находится во фрагменте, в методе onViewCreated вызовите mPager.setOnPageChangeListener(this)

person Erik K.    schedule 15.05.2018

если вам все равно анимация экрана попробуйте

pager.setCurrentItem(1);
pager.setCurrentItem(0);
person 羅旭辰    schedule 04.02.2015
comment
А если на вашем пейджере только один элемент...? - person RobP; 13.06.2015