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

86

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

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

Я не мог сделать это программно; аннулирование представления списка или даже всей страницы просмотра не имело никакого эффекта.

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

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

Матиас
источник

Ответы:

44

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

  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.

футтетенниста
источник
2
У меня такая же проблема, но с FragmentPagerAdapter, который обрабатывает onDestroy за меня. Ни одно из других решений здесь тоже не работает.
Грег Эннис
14
Что еще может быть проблемой, так это то, что кто-то использует getFragmentManger вместо GetChildFragmentManager
Мальчик
@ Мальчик, что такое GetChildFragmentManager?
пожар в яме
1
@Boy Wooooow .... Я выдергивал волосы целых два дня, пытаясь понять, почему я не могу ничего обновить. СПАСИБО! (они действительно должны поместить уведомление в документацию Google об использовании childfragmentmanager, это совсем не то, что вам нужен другой менеджер).
user0721090601
@futtetennista Я делаю то же самое .. столкнулся с этой проблемой .. вы можете проверить .. stackoverflow.com/questions/61727835/…
AskQ
55

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

mAdapter = new MyFragmentPagerAdapter(getChildFragmentManager());

Вместо того

mAdapter = new MyFragmentPagerAdapter(getSupportFragmentManager());
глазок
источник
2
Хороший улов, похоже, это решает мою проблему при использовании FragmentPagerAdapter
Tobliug
1
Спасибо, это сработало. Я пытался навязать getItem()объект, FragmentPagerAdapterкоторый был вложен в воссозданный фрагмент.
kosiara - Bartosz Kosarzycki
вау, эта работа как шарм. я предполагаю, что проблема связана с раздуванием окна просмотра во фрагменте
Thecarisma
В такие времена я хотел бы, чтобы у нас были аплодисменты, такие как Medium, чтобы я мог дать вам 1000+ хлопков за это. Хорошая работа, это должен быть общепринятый ответ
Codelicious
21

У меня была такая же проблема, но я фактически уничтожил представление в destroyItem (я думал). Однако проблема заключалась в том, что я уничтожил его, используя viewPager.removeViewAt(index);insted ofviewPager.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);
}
Ringen
источник
Спасибо за подсказку. Долгое время разбирался в проблеме.
Moritz
2
У меня это сработало, но знаете ли вы, почему первая проблема?
HannahMitt 01
@HannahMitt removeViewAt удалит любое представление, находящееся в этой конкретной позиции в текущем списке дочерних элементов ViewPager, и страницы могут быть добавлены в ViewPager в любом порядке (поэтому страница 0 может фактически находиться в ViewPager с индексом 1 или чем-то еще). removeView будет перебирать список дочерних элементов и удалять именно тот объект, который указан.
2
Не работает для меня: невозможно преобразовать представление во фрагмент. объект вот фрагмент.
FRK
viewpager должен быть статическим?
Канагалингам
10

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

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

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

Крис Бэйнс
источник
1
Да, я читал об этом варианте в этой теме: stackoverflow.com/questions/7263291/… - однако это похоже на подход кувалды. Неужто должен быть более элегантный способ? Интересно, связано ли это с использованием списков в качестве страниц пейджера?
Matthias
Это что-то вроде кувалды, но от этого можно поработать. т.е. вернуть правильное значение из метода.
Chris Banes
@ChrisBanes Как вы сказали, вызывая return POSITION_NONE;, forces it to re-instantiate everythingно в моем случае я не хочу повторно создавать все , поэтому можно ли удалить, viewне очищая существующие вещи? Пожалуйста, дайте мне знать
Ритеш Адулкар
ты
вкус
2

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

 mAdapter = new NewsVPAdapter(getContext(), articles);
    viewPager.post(new Runnable() {
        @Override
        public void run() {
            viewPager.setAdapter(mAdapter);
        }
    });
Zeero0
источник
1
о Господи. почему никто не проголосовал за это? У меня странное поведение, когда я повторно вхожу во фрагмент, а окно просмотра внутри не отображает себя, и задержка его, как будто это действительно решает проблему!
Фугогуго,
0

В библиотеке поддержки Android есть демонстрация Activity, которая включает 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. Удачи!

Спарки
источник
спасибо - я посмотрю на это! Возможно, я замечу, что мы ошибаемся.
Matthias
0

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

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

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

Тим О'Брайен
источник
0

Имел ту же проблему, что связано с ListView(потому что мое пустое представление отображается нормально, если список пуст). Я просто позвонил requestLayout()проблемному ListView. Теперь отлично рисует!

Олег Васкевич
источник
0

Я столкнулся с этой же проблемой при использовании 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();
     }
Крис Спраг
источник
0

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

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

dfinn
источник
-1

У меня была аналогичная проблема. Я кэширую просмотры, потому что мне нужно всего 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];
Роман Назаревич
источник
-1

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

Вызов viewPager.setCurrentItem(position, true);

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

Meanman
источник