ViewPager PagerAdapter не обновляет представление - PullRequest
564 голосов
/ 01 сентября 2011

Я использую ViewPager из библиотеки совместимости. У меня, к счастью, есть несколько просмотров, которые я могу пролистать.

Однако мне трудно понять, как обновить ViewPager новым набором видов.

Я пробовал всевозможные вещи, например, звонить mAdapter.notifyDataSetChanged(), mViewPager.invalidate(), даже создавая новый адаптер каждый раз, когда я хочу использовать новый Список данных.

Ничего не помогло, текстовые представления остаются неизменными по сравнению с исходными данными.

Обновление: Я сделал небольшой тестовый проект, и я почти смог обновить представления. Я вставлю класс ниже.

То, что не обновляется, однако, это 2-й вид, «B» остается, он должен отображать «Y» после нажатия кнопки обновления.

public class ViewPagerBugActivity extends Activity {

    private ViewPager myViewPager;
    private List<String> data;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        data = new ArrayList<String>();
        data.add("A");
        data.add("B");
        data.add("C");

        myViewPager = (ViewPager) findViewById(R.id.my_view_pager);
        myViewPager.setAdapter(new MyViewPagerAdapter(this, data));

        Button updateButton = (Button) findViewById(R.id.update_button);
        updateButton.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                updateViewPager();
            }
        });
    }

    private void updateViewPager() {
        data.clear();
        data.add("X");
        data.add("Y");
        data.add("Z");
        myViewPager.getAdapter().notifyDataSetChanged();
    }

    private class MyViewPagerAdapter extends PagerAdapter {

        private List<String> data;
        private Context ctx;

        public MyViewPagerAdapter(Context ctx, List<String> data) {
            this.ctx = ctx;
            this.data = data;
        }

        @Override
        public int getCount() {
            return data.size();
        }

        @Override
        public Object instantiateItem(View collection, int position) {
            TextView view = new TextView(ctx);
            view.setText(data.get(position));
            ((ViewPager)collection).addView(view);
            return view;
        }

        @Override
        public void destroyItem(View collection, int position, Object view) {
             ((ViewPager) collection).removeView((View) view);
        }

        @Override
        public boolean isViewFromObject(View view, Object object) {
            return view == object;
        }

        @Override
        public Parcelable saveState() {
            return null;
        }

        @Override
        public void restoreState(Parcelable arg0, ClassLoader arg1) {
        }

        @Override
        public void startUpdate(View arg0) {
        }

        @Override
        public void finishUpdate(View arg0) {
        }
    }
}

Ответы [ 37 ]

802 голосов
/ 02 сентября 2011

Есть несколько способов достичь этого.

Первый вариант проще, но немного менее эффективен.

Переопределить getItemPosition в вашем PagerAdapter так:

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

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

Второй вариант, , предложенный Альваро Луисом Бустаманте (ранее alvarolb) , - это метод setTag() в instantiateItem() при создании нового представления. Затем вместо notifyDataSetChanged() вы можете использовать findViewWithTag(), чтобы найти представление, которое вы хотите обновить.

Второй подход очень гибкий и высокопроизводительный. Слава Альваролбу за оригинальное исследование.

466 голосов
/ 06 ноября 2011

Не думаю, что в PagerAdapter есть какая-либо ошибка.Проблема в том, что понять, как это работает, немного сложно.Глядя на решения, объясненные здесь, есть недопонимание и, следовательно, плохое использование экземпляров представлений с моей точки зрения.

Последние несколько дней я работал с PagerAdapter и ViewPager, и яобнаружил следующее:

Метод notifyDataSetChanged() на PagerAdapter только уведомит ViewPager, что основные страницы изменились.Например, если вы создали / удалили страницы динамически (добавляя или удаляя элементы из вашего списка), ViewPager должен позаботиться об этом.В этом случае я думаю, что ViewPager определяет, следует ли удалять или создавать новое представление, используя методы getItemPosition() и getCount().

Я думаю, что ViewPager после вызова notifyDataSetChanged()принимает его дочерние виды и проверяет их позицию с помощью getItemPosition().Если для дочернего представления этот метод возвращает POSITION_NONE, ViewPager понимает, что представление было удалено, вызывает destroyItem() и удаляет это представление.

Таким образом, переопределяя getItemPosition() длявсегда возвращать POSITION_NONE совершенно неверно, если вы хотите обновить только содержимое страниц, потому что ранее созданные представления будут уничтожены, а новые будут создаваться при каждом вызове notifyDatasetChanged().Может показаться, что это не так неправильно только для нескольких TextView с, но когда у вас есть сложные представления, такие как ListViews, заполненные из базы данных, это может быть реальной проблемой и пустой тратой ресурсов.

ТакСуществует несколько подходов для эффективного изменения содержимого представления без необходимости повторного удаления и создания экземпляра представления.Это зависит от проблемы, которую вы хотите решить. Мой подход заключается в использовании метода setTag() для любого экземпляра представления в методе instantiateItem().Поэтому, когда вы хотите изменить данные или сделать недействительным нужное представление, вы можете вызвать метод findViewWithTag() для ViewPager, чтобы получить ранее созданное представление и изменить / использовать его так, как вам нужно, без необходимости удалять / создаватьновый вид каждый раз, когда вы хотите обновить какое-либо значение.

Представьте, например, что у вас есть 100 страниц со 100 TextView с, и вы хотите периодически обновлять только одно значение.С подходами, объясненными ранее, это означает, что вы удаляете и создаете 100 TextView s при каждом обновлении.Это не имеет смысла ...

79 голосов
/ 05 сентября 2014

Измените FragmentPagerAdapter на FragmentStatePagerAdapter.

Переопределить getItemPosition() метод и вернуть POSITION_NONE.

В конце концов, он будет прослушивать пейджер notifyDataSetChanged() on.

43 голосов
/ 06 июня 2012

Ответ , данный alvarolb, безусловно, лучший способ сделать это. Основываясь на его ответе, простой способ реализовать это состоит в том, чтобы просто сохранить активные представления по позиции:

SparseArray<View> views = new SparseArray<View>();

@Override
public Object instantiateItem(View container, int position) {
    View root = <build your view here>;
    ((ViewPager) container).addView(root);
    views.put(position, root);
    return root;
}

@Override
public void destroyItem(View collection, int position, Object o) {
    View view = (View)o;
    ((ViewPager) collection).removeView(view);
    views.remove(position);
    view = null;
}

Затем, переопределив метод notifyDataSetChanged, вы можете обновить представления ...

@Override
public void notifyDataSetChanged() {
    int key = 0;
    for(int i = 0; i < views.size(); i++) {
       key = views.keyAt(i);
       View view = views.get(key);
       <refresh view with new data>
    }
    super.notifyDataSetChanged();
}

Вы можете фактически использовать подобный код в instantiateItem и notifyDataSetChanged, чтобы обновить ваш вид. В моем коде я использую точно такой же метод.

25 голосов
/ 17 июня 2015

Была такая же проблема. Для меня это работало, чтобы расширить FragmentStatePagerAdapter и переопределить следующие методы:

@Override
public Parcelable saveState() {
    return null;
}

@Override
public void restoreState(Parcelable state, ClassLoader loader) {

}
20 голосов
/ 08 марта 2014

После нескольких часов разочарования, пытаясь решить все вышеперечисленные решения, чтобы решить эту проблему, а также пытаясь найти решение многих других похожих вопросов, таких как это , это и это которые все не удалось со мной решить эту проблему и заставить ViewPager уничтожить старый Fragment и заполнить pager новыми Fragment с. Я решил проблему следующим образом:

1) Сделать класс ViewPager для расширений FragmentPagerAdapter следующим образом:

 public class myPagerAdapter extends FragmentPagerAdapter {

2) Создать элемент для ViewPager, в котором хранятся title и fragment, следующим образом:

public class PagerItem {
private String mTitle;
private Fragment mFragment;


public PagerItem(String mTitle, Fragment mFragment) {
    this.mTitle = mTitle;
    this.mFragment = mFragment;
}
public String getTitle() {
    return mTitle;
}
public Fragment getFragment() {
    return mFragment;
}
public void setTitle(String mTitle) {
    this.mTitle = mTitle;
}

public void setFragment(Fragment mFragment) {
    this.mFragment = mFragment;
}

}

3) Заставить конструктор ViewPager взять мой экземпляр FragmentManager, чтобы сохранить его в моем class следующим образом:

private FragmentManager mFragmentManager;
private ArrayList<PagerItem> mPagerItems;

public MyPagerAdapter(FragmentManager fragmentManager, ArrayList<PagerItem> pagerItems) {
    super(fragmentManager);
    mFragmentManager = fragmentManager;
    mPagerItems = pagerItems;
}

4) Создать метод для переустановки данных adapter с новыми данными, удалив все предыдущие fragment из самого fragmentManager напрямую, чтобы сделать adapter для установки новый fragment из нового списка снова следующим образом:

public void setPagerItems(ArrayList<PagerItem> pagerItems) {
    if (mPagerItems != null)
        for (int i = 0; i < mPagerItems.size(); i++) {
            mFragmentManager.beginTransaction().remove(mPagerItems.get(i).getFragment()).commit();
        }
    mPagerItems = pagerItems;
}

5) Из контейнера Activity или Fragment не переинициализируйте адаптер с новыми данными. Установите новые данные с помощью метода setPagerItems с новыми данными следующим образом:

ArrayList<PagerItem> pagerItems = new ArrayList<PagerItem>();
pagerItems.add(new PagerItem("Fragment1", new MyFragment1()));
pagerItems.add(new PagerItem("Fragment2", new MyFragment2()));

mPagerAdapter.setPagerItems(pagerItems);
mPagerAdapter.notifyDataSetChanged();

Надеюсь, это поможет.

9 голосов
/ 25 мая 2017

У меня была такая же проблема, и мое решение переопределяет ViewPagerAdapter#getItemId(int position):

@Override
public long getItemId(int position) {
    return mPages.get(position).getId();
}

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

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

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

5 голосов
/ 07 февраля 2014

Через два с половиной года после того, как ОП задал свой вопрос, эта проблема все еще остается проблемой.Очевидно, что приоритет Google в этом не очень высок, поэтому вместо того, чтобы найти решение, я нашел обходной путь.Большим прорывом для меня стало выяснение истинной причины проблемы (см. Принятый ответ в этом посте ).Как только стало очевидно, что проблема заключалась в том, что все активные страницы не обновлялись должным образом, мой обходной путь был очевиден:

В моем фрагменте (страницы):

  • Я взял весь кодкоторый заполняет форму из onCreateView и помещает ее в функцию с именем PopulateForm, которая может быть вызвана из любого места, а не из фреймворка.Эта функция пытается получить текущий View, используя getView, и если он имеет значение null, он просто возвращает.Важно, чтобы PopulateForm содержал только отображаемый код - весь другой код, который создает прослушиватели FocusChange и т.п., все еще находится в OnCreate
  • Создайте логическое значение, которое можно использовать в качестве флага, указывающего, что форма должна быть перезагружена.У меня есть mbReloadForm
  • Переопределить OnResume () для вызова PopulateForm (), если установлена ​​функция mbReloadForm.

В моей деятельности, где я выполняю загрузку страниц:

  • Перейдите на страницу 0, прежде чем что-либо менять.Я использую FragmentStatePagerAdapter, поэтому я знаю, что затронуты максимум две или три страницы.Переход на страницу 0 гарантирует, что у меня возникнут проблемы только на страницах 0, 1 и 2.
  • Перед очисткой старого списка возьмите его размер ().Таким образом, вы узнаете, сколько страниц затронуто этой ошибкой.Если> 3, уменьшите его до 3 - если вы используете другой PagerAdapter, вам нужно будет увидеть, сколько страниц вам приходится иметь дело (может быть, все?)
  • Перезагрузите данные и вызовите pageAdapter.notifyDataSetChanged ()
  • Теперь для каждой из затронутых страниц посмотрите, активна ли эта страница, с помощью pager.getChildAt (i) - она ​​сообщает, есть ли у вас представление.Если это так, вызовите pager.PopulateView ().Если нет, установите флаг ReloadForm.

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

Надеюсь, это кому-нибудь поможет!

5 голосов
/ 04 февраля 2014

Все эти решения мне не помогли.таким образом я нашел рабочее решение: вы можете setAdapter каждый раз, но этого недостаточно.Вы должны сделать это до замены адаптера:

FragmentManager fragmentManager = slideShowPagerAdapter.getFragmentManager();
FragmentTransaction transaction = fragmentManager.beginTransaction();
List<Fragment> fragments = fragmentManager.getFragments();
for (Fragment f : fragments) {
    transaction.remove(f);
}
transaction.commit();

и после этого:

viewPager.setAdapter(adapter);
5 голосов
/ 28 сентября 2016

Спасибо rui.araujo и Альваро Луису Бустаманте.Сначала я пытаюсь использовать путь rui.araujo, потому что это легко.Это работает, но когда данные меняются, страница будет перерисовываться, очевидно.Это плохо, поэтому я стараюсь использовать способ Альваро Луиса Бустаманте.Это идеально.Вот код:

@Override
protected void onStart() {
    super.onStart();
}

private class TabPagerAdapter extends PagerAdapter {
    @Override
    public int getCount() {
        return 4;
    }

    @Override
    public boolean isViewFromObject(final View view, final Object object) {
        return view.equals(object);
    }

    @Override
    public void destroyItem(final View container, final int position, final Object object) {
        ((ViewPager) container).removeView((View) object);
    }

    @Override
    public Object instantiateItem(final ViewGroup container, final int position) {
        final View view = LayoutInflater.from(
                getBaseContext()).inflate(R.layout.activity_approval, null, false);
        container.addView(view);
        ListView listView = (ListView) view.findViewById(R.id.list_view);
        view.setTag(position);
        new ShowContentListTask(listView, position).execute();
        return view;
    }
}

А при изменении данных:

for (int i = 0; i < 4; i++) {
    View view = contentViewPager.findViewWithTag(i);
    if (view != null) {
        ListView listView = (ListView) view.findViewById(R.id.list_view);
        new ShowContentListTask(listView, i).execute();
    }
}
...