ViewPager не перерисовывает содержимое, остается / становится пустым

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

Что мы наблюдаем, так это то, что иногда страница не обновляет свое дерево просмотра, то есть остается пустой, а иногда даже исчезает при переходе на нее. При переходе назад и вперед несколько раз контент неожиданно появляется снова. Похоже, что Android отсутствует обновление представления здесь. Я также заметил, что при отладке с помощью средства просмотра иерархии при выборе представления оно всегда будет появляться снова, по-видимому, потому что средство просмотра иерархии заставляет выбранное представление перерисовывать себя.

Я не мог заставить это работать программно, хотя; аннулирование представления списка или даже всего пейджера представления не дало результатов.

Это с библиотекой compatibility-v4_r7. Я также попытался использовать последнюю версию, так как в ней утверждается, что она устраняет многие проблемы, связанные с просмотром пейджера, но это еще больше ухудшило ситуацию (например, жесты были сломаны, и из-за этого иногда я не мог просматривать все страницы).

Кто-нибудь еще сталкивается с этими проблемами, или у вас есть представление о том, что может быть причиной этого?

11 ответов

Решение

Нам наконец удалось найти решение. По-видимому, наша реализация страдала от двух проблем:

  1. наш адаптер не удалил вид в destroyItem(),
  2. мы кэшировали представления так, чтобы нам пришлось раздувать наш макет только один раз, и, поскольку мы не удаляли представление в destroyItem()мы не добавляли его в instantiateItem() но просто возвращаем кешированное представление, соответствующее текущей позиции.

Я не смотрел слишком глубоко в исходном коде ViewPager - и это не совсем ясно, что вы должны это сделать, - но документы говорят:

destroyItem ()
Удалить страницу для данной позиции. Адаптер отвечает за удаление представления из своего контейнера, хотя он должен только гарантировать, что это будет сделано к тому времени, как он вернется из finishUpdate(ViewGroup).

а также:

Очень простой PagerAdapter может выбрать использование самих представлений страницы в качестве ключевых объектов, возвращая их из instantiateItem(ViewGroup, int) после создания и добавляя их в родительский ViewGroup. Соответствующая реализация destroyItem(ViewGroup, int, Object) удалит View из родительского ViewGroup, а isViewFromObject(View, Object) может быть реализован как return view == object;.

Итак, мой вывод таков, что ViewPager полагается на базовый адаптер для явного добавления / удаления своих дочерних элементов в instantiateItem()/destroyItem(), То есть, если ваш адаптер является подклассом PagerAdapterВаш подкласс должен реализовать эту логику.

Примечание: знайте об этом, если вы используете списки внутри ViewPager,

Если ViewPager устанавливается внутри фрагмента с FragmentPagerAdapterиспользовать getChildFragmentManager() вместо getSupportFragmentManager() в качестве параметра для инициализации вашего FragmentPagerAdapter,

mAdapter = new MyFragmentPagerAdapter(getChildFragmentManager());

Вместо

mAdapter = new MyFragmentPagerAdapter(getSupportFragmentManager());

У меня была точно такая же проблема, но я фактически уничтожил представление в destroyItem (я думал). Проблема, однако, заключалась в том, что я уничтожил его, используя viewPager.removeViewAt(index); поставленный viewPager.removeView((View) object);

Неправильно:

@Override
public void destroyItem(ViewGroup viewPager, int position, Object object) {
    viewPager.removeViewAt(position);
}

Правильно:

@Override
public void destroyItem(ViewGroup viewPager, int position, Object object) {
    viewPager.removeView((View) object);
}

ViewPager пытается делать умные вещи вокруг повторного использования предметов, но он требует, чтобы вы возвращали новые позиции предметов, когда все изменилось. Попробуйте добавить это в свой PagerAdapter:

public int getItemPosition (Object object) { return POSITION_NONE; }

Это в основном говорит ViewPager, что все изменилось (и заставляет его заново создавать все). Это единственное, что я могу думать о макушке головы.

Перепробовал слишком много решений но неожиданно viewPager.post() работал

 mAdapter = new NewsVPAdapter(getContext(), articles);
    viewPager.post(new Runnable() {
        @Override
        public void run() {
            viewPager.setAdapter(mAdapter);
        }
    });

У меня была проблема с теми же симптомами, но с другой причиной, которая оказалась глупой ошибкой с моей стороны. Думаю, я добавлю это сюда на случай, если это кому-нибудь поможет.

У меня был ViewPager с использованием FragmentStatePagerAdapter, который имел два фрагмента, но позже я добавил третий. Однако я забыл, что ограничение по умолчанию для страниц за пределами экрана равно 1, поэтому, когда я переключаюсь на новый третий фрагмент, первый будет уничтожен, а затем воссоздан после переключения назад. Проблема заключалась в том, что моя деятельность была направлена ​​на уведомление этих фрагментов для инициализации их состояния пользовательского интерфейса. Это сработало, когда жизненные циклы активности и фрагментов были одинаковыми, но чтобы исправить это, мне пришлось изменить фрагменты, чтобы инициализировать их собственный пользовательский интерфейс во время их жизненного цикла запуска. В конце концов, я также изменил setOffscreenPageLimit на 2, чтобы все три фрагмента всегда оставались в живых (в данном случае это безопасно, поскольку они не очень требовательны к памяти).

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

В Eclipse (с Android Dev Tools r20):

  1. Выбрать New > Android Sample Project
  2. Выберите целевой уровень API (я предлагаю самый новый из доступных)
  3. Выбрать Support4Demos
  4. Щелкните правой кнопкой мыши проект и выберите Android Tools > Add Support Library
  5. Запустите приложение и выберите Fragment а потом Pager

Код для этого находится в src/com.example.android.supportv4.app/FragmentPagerSupport.java, Удачи!

Я столкнулся с этой же проблемой при использовании ViewPager и FragmentStatePagerAdapter. Я попытался использовать обработчик с 3-секундной задержкой для вызова invalidate() и requestLayout(), но это не сработало. Что сработало, так это сбросил цвет фона viewPager следующим образом:

MyFragment.java

    private Handler mHandler;
    private Runnable mBugUpdater;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        View rootView = new ViewPager(getActivity());
        //...Create your adapter and set it here...

        mHandler = new Handler();
        mBugUpdater = new Runnable(){
            @Override
            public void run() {
                mVp.setBackgroundColor(mItem.getBackgroundColor());
                mHandler = null;
                mBugUpdater = null;
            }           
        };
        mHandler.postDelayed(mBugUpdater,50);

        return rootView;
    }

    @Override
    public void onPause() {
        if(mHandler != null){
            //Remove the callback if it hasn't triggered yet
            mHandler.removeCallbacks(mBugUpdater);
            mHandler = null;
            mBugUpdater = null;
        }
        super.onPause();
     }

Я столкнулся с этим и имел очень похожие проблемы. Я даже спросил об переполнении стека.

Для меня в родительском родителя моего взгляда кто-то подкласс LinearLayout и перебор requestLayout() без звонка super.requestLayout(), Это помешало onMeasure а также onLayout от того, чтобы быть вызванным на моем ViewPager (хотя просмотрщик иерархии вызывает их вручную). Без измерения они будут отображаться как пустые в ViewPager.

Так что проверьте ваши содержащие взгляды. Убедитесь, что они являются подклассами из View и не игнорируют вслепую requestLayout или что-то подобное.

Была такая же проблема, которая как-то связана с ListView (потому что мой пустой вид отображается нормально, если список пуст). я только что звонил requestLayout() на проблемных ListView, Теперь он хорошо рисует!

У меня была похожая проблема. Я кэширую представления, потому что мне нужно только 3 просмотра ViewPager, Когда я двигаюсь вперед, все в порядке, но когда я начинаю скользить назад, возникает ошибка, она говорит, что "у моего вида уже есть родитель". Решение состоит в том, чтобы удалить ненужные элементы вручную.

@Override
    public Object instantiateItem(ViewGroup container, int position) {
        int localPos = position % SIZE;
        TouchImageView view;
        if (touchImageViews[localPos] != null) {
            view = touchImageViews[localPos];
        } else {
            view = new TouchImageView(container.getContext());
            view.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
            touchImageViews[localPos] = view;
        }
        view.setImageDrawable(mDataModel.getPhoto(position));
        Log.i(IRViewPagerAdpt.class.toString(), "Add view " + view.toString() + " at pos: " + position + " " + localPos);
        if (view.getParent() == null) {
        ((ViewPager) container).addView(view);
    }
        return view;
    }

    @Override
    public void destroyItem(ViewGroup container, int position, Object view) {
        //      ((ViewPager) container).removeView((View) view);
        Log.i(IRViewPagerAdpt.class.toString(), "remove view " + view.toString() + " at pos: " + position);
    }

..................

private static final int SIZE = 3;
private TouchImageView[] touchImageViews = new TouchImageView[SIZE];

Для пользователей Kotlin:

В ваших фрагментах; Использовать childFragmentManager вместо viewPagerAdapter

Для меня проблема возвращалась к активности после того, как процесс приложения был убит. Я использую пользовательский адаптер просмотра пейджера, модифицированный из исходников Android. Пейджер представления встроен непосредственно в действие.

призвание viewPager.setCurrentItem(position, true);

(с анимацией) после установки данных и notifyDataSetChanged(), кажется, работает, но если для параметра установлено значение false, это не так, и фрагмент остается пустым. Это крайний случай, который может помочь кому-то.

Другие вопросы по тегам