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

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

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

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

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

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

Ответ 1

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

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

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

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

и:

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

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

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

Ответ 2

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

mAdapter = new MyFragmentPagerAdapter(getChildFragmentManager());

Вместо

mAdapter = new MyFragmentPagerAdapter(getSupportFragmentManager());

Ответ 3

У меня была такая же проблема, но я фактически уничтожил представление в destroyItem (я думал). Однако проблема заключалась в том, что я уничтожил ее с помощью viewPager.removeViewAt(index); insted 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);
}

Ответ 4

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

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

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

Ответ 5

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

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

Ответ 6

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

В Eclipse (с Android Dev Tools r20):

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

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

Ответ 7

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

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

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

Ответ 8

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

Ответ 9

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

Ответ 10

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

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

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

Ответ 11

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