ViewPager PagerAdapter не обновляет представление

597

Я использую 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) {
        }
    }
}
C0deAttack
источник
1
См. Stackoverflow.com/questions/12510404/… для потенциальной ошибки с FragmentStatePagerAdapter.
Самис
2
Извините, что углубил это. Ваш вопрос действительно помог мне, так что спасибо! Единственное, чего я не понимаю, - как меняется ссылка на данные в PagerAdapter, когда вы обновляете их в Activity
Ewanw
Заставьте свой адаптер расширить BaseAdapter. Перейдите по ссылке: stackoverflow.com/questions/13664155/…
1
Очевидно, что если вы снова установите адаптер на свой пейджер, он будет сброшен.
EpicPandaForce
У меня проблема в пейджере stackoverflow.com/questions/41217237/…
Крис

Ответы:

856

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

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

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

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

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

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

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

rui.araujo
источник
4
Лучше, чем моя работа, кажется, не имеет побочных эффектов, спасибо.
C0deAttack
3
У меня аналогичная проблема. Мой viewPager покажет новые результаты поиска, но по умолчанию не вернется к первой записи в курсоре. Кто-нибудь знает, как сбросить позицию при обновлении набора данных?
Android 2007
1
@mAndroid вы должны подать отдельный вопрос с более подробной информацией.
rui.araujo
1
возвращение POSITION_NONE, очевидно, добавляет много неэффективности всему приложению, так как это заставляет Android удалять представление, связанное с запрашиваемой позицией. Затем представление необходимо заново создать перед отображением. Решает проблему не обновляемых видов, но, вероятно, не лучшее решение для сложных макетов.
wufoo
11
Лучший ответ (без удаления всех элементов) -> stackoverflow.com/a/10852046/898056
yhpark
484

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

Альваро Луис Бустаманте
источник
11
+1 - Ваше решение работает не только как брелок. Я столкнулся именно с той проблемой, которую вы описали здесь, когда мне приходилось обновлять страницы, которые содержали вкладки со списками, текстовыми видами и изображениями. (OutOfErrorExceptions и тому подобное ...) Спасибо!
Шлингель
3
@alvarolb: Здравствуйте, я действительно не знаю, что вы имеете setTagв виду в методе. onInstantiateItemХотели бы вы обновить этот ответ кодированием? Спасибо.
Башня Хай
35
Было бы идеально, если бы вы написали пример.
Сами Элтамави
13
Как кто-то говорит: пример woulb нужно ценить!
Jey10
15
6 лет никто не может привести пример.
Уссаки
80

Изменение FragmentPagerAdapterTo FragmentStatePagerAdapter.

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

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

Комплект
источник
Я думаю, что вы должны правильно реализовать весь getItemPosition и вернуть POSITION_NONE, если фрагмент не найден.
18:43
46

Ответ, данный 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для обновления вашего представления. В моем коде я использую точно такой же метод.

Grimmace
источник
8
Можете ли вы предоставить полный пример адаптера? это делается с использованием фрагментов или нет?
9
<обновить представление новыми данными> ?? что это значит, мы должны добавить, что он видит здесь, или ничего?
Акарш М
Этот метод обновляет все представления. Если вы хотите обновить только одно представление, то вы можете написать свой собственный метод notifyDataItemChanged следующим образом: public void notifyDataItemChanged (int pos) {TextView tv = (TextView) views.get (pos) .findViewById (R.id.tv); tv.setText (list.get (Pos)); super.notifyDataSetChanged (); }
Кай Ван
Этот код вылетает, если размер нового пейджера меньше старого
Антон Дузенко,
@AntonDuzenko правильно, удаление просмотров довольно сложно.
MLProgrammer-CiM
28

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

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

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

}
Марио моска
источник
Это единственное решение, сработавшее и для меня. Мой случай не обновляет фрагмент, но удаляет или добавляет фрагменты динамически. Без расширения FragmentStatePagerAdapter ViewPager возвращает кэшированные фрагменты, что является неправильной позицией. Спасибо!
Сира Лам
Хорошо работает с библиотекой поддержки 28.0.0
Алексей К.
Переопределить что делать? Как создать объект состояния, связанный с адаптером?
Фани Ритвий
Спасибо, приятель. Это решение работает только
Партхи
20

После нескольких часов разочарования при попытке всех вышеуказанных решений , чтобы преодолеть эту проблему , а также пытается множество решений на других подобные вопросы , как это , это и это , который все FAILED со мной , чтобы решить эту проблему и сделать , 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) Создайте конструктор объекта ViewPagertake my FragmentManagerдля сохранения его в my 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

У меня была та же проблема, и мое решение использует FragmentPagerAdapterс переопределением FragmentPagerAdapter#getItemId(int position):

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

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

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

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

atlascoder
источник
1
Это. Я проверил это: просто POSITION_NONE недостаточно. Спасибо за ваш чудесный ответ! :)
Слава
Эта функция даже не присутствует в PagerAdapter. Что это за класс?
Сакет
@Saket, вы правы, классFragmentPagerAdapter
atlascoder
6

Я нашел очень интересное решение этой проблемы. Вместо использования FragmentPagerAdapter , который хранит в памяти все фрагменты, мы можем использовать FragmentStatePagerAdapter ( android.support.v4.app.FragmentStatePagerAdapter ), который каждый раз перезагружает фрагмент, когда мы его выбираем.

Реализации обоих адаптеров идентичны. Таким образом, нам нужно просто изменить « extension FragmentPagerAdapter » на « extends FragmentStatePagerAdapter »

Рома Богдан
источник
хороший момент, это поможет другим решить проблемы с памятью viewPager, связанные с фрагментом
Ашу Кумар
Отличный ответ, это мне очень помогло.
Серхио Марани
5

После долгих поисков этой проблемы я нашел действительно хорошее решение, которое я считаю правильным. По сути, instantiateItem вызывается только тогда, когда создается экземпляр представления, и никогда больше, если только представление не разрушено (это то, что происходит, когда вы переопределяете функцию getItemPosition для возврата POSITION_NONE). Вместо этого вы хотите сохранить созданные представления и либо обновить их в адаптере, сгенерировать функцию get, чтобы кто-то другой мог ее обновить, либо функцию set, которая обновляет адаптер (мой любимый).

Итак, в вашем MyViewPagerAdapter добавьте переменную вроде:

private View updatableView;

в вашем instantiateItem:

 public Object instantiateItem(View collection, int position) {
        updatableView = new TextView(ctx); //My change is here
        view.setText(data.get(position));
        ((ViewPager)collection).addView(view);
        return view;
    }

таким образом, вы можете создать функцию, которая будет обновлять ваш вид:

public updateText(String txt)
{
    ((TextView)updatableView).setText(txt);
}

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

Немецкий
источник
Я понял, что не устанавливал данные instantiateItem, поэтому мои представления не обновлялись. По твоему ответу я это понял. +1
Фани Ритвий
5

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

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

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

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

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

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

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

RichardP
источник
5

Все эти решения не помогли мне. таким образом я нашел рабочее решение: можно 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);
Махди Перфект
источник
Спасибо! В моем случае я использовал ViewPagerвнутри фрагмент, поэтому заменить slideShowPagerAdapter.getFragmentManager()на getChildFragmentManager(). Может быть getFragmentManager(), поможет в вашем случае. Я использовал FragmentPagerAdapter, нет FragmentStatePagerAdapter. Также см. Stackoverflow.com/a/25994654/2914140 для хакерского способа.
CoolMind
5

Гораздо более простой способ: используйте FragmentPagerAdapterи оберните свои постраничные представления фрагментами. Они обновляются

alfongj
источник
5

Спасибо 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();
    }
}
evan.z
источник
4

На всякий случай, если кто-то использует FragmentStatePagerAdapter адаптер на основе (который позволит ViewPager создавать минимум страниц, необходимых для отображения, максимум 2 для моего случая), ответ @ rui.araujo о перезаписи getItemPosition в вашем адаптере не вызовет значительных потерь, но все же может быть улучшена.

В псевдокоде:

public int getItemPosition(Object object) {
    YourFragment f = (YourFragment) object;
    YourData d = f.data;
    logger.info("validate item position on page index: " + d.pageNo);

    int dataObjIdx = this.dataPages.indexOf(d);

    if (dataObjIdx < 0 || dataObjIdx != d.pageNo) {
        logger.info("data changed, discard this fragment.");
        return POSITION_NONE;
    }

    return POSITION_UNCHANGED;
}
Джерри Тиан
источник
внутри ViewPager было довольно легко узнать позицию элемента после, самая совершенная реализация - просто вернуть новую позицию, getItemPosition()когда изменился набор данных, и ViewPager будет знать, изменились они или нет. к сожалению, ViewPager не сделал этого.
VinceStyling
3

У меня была похожая проблема, в которой у меня было четыре страницы, и одна из страниц обновляла представления на трех других. Мне удалось обновить виджеты (SeekBars, TextViews и т. Д.) На странице рядом с текущей страницей. Последние две страницы будут иметь неинициализированные виджеты при вызовеmTabsAdapter.getItem(position) .

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

После всех обновлений я бы использовал с setSelectedPage(position)последующимnotifyDataSetChanged() .

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

enKoder
источник
Привет, меня интересует ваш код, я столкнулся с похожей проблемой, я могу обновить соседние фрагменты, но текущий видимый фрагмент не обновляется. Я очень смущен всеми ответами здесь. как я могу обновить представления в текущем фрагменте, который показан адаптером VIewPager
Pankaj Nimgade
3

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

Затем в CustomViewPager создайте метод с именем updateViewAt (int position). Само представление может быть получено из элементов ArrayList, определенных в классе ViewPager (необходимо установить Id для представлений в экземпляре элемента и сравнить этот идентификатор с позицией в методе updateViewAt ()). Затем вы можете обновить представление по мере необходимости.

Навин Л.П.
источник
2

Я думаю, у меня есть логика ViewPager.

Если мне нужно обновить набор страниц и отобразить их на основе нового набора данных, я вызываю notifyDataSetChanged () . Затем ViewPager делает несколько вызовов getItemPosition () , передавая ему Fragment как объект. Этот фрагмент может быть либо из старого набора данных (который я хочу удалить), либо из нового (который я хочу отобразить). Итак, я переопределяю getItemPosition (), и там я должен как-то определить, является ли мой фрагмент из старого набора данных или из нового.

В моем случае у меня есть двухпанельный макет со списком верхних элементов на левой панели и представлением пролистывания (ViewPager) справа. Таким образом, я храню ссылку на мой текущий верхний элемент внутри моего PagerAdapter, а также внутри каждого фрагмента страницы, для которого была создана конкретная страница. Когда выбранный верхний элемент в списке изменяется, я сохраняю новый верхний элемент в PagerAdapter и вызываю notifyDataSetChanged () . И в переопределенном getItemPosition () я сравниваю верхний элемент из моего адаптера с верхним элементом из моего фрагмента. И только если они не равны, я возвращаю POSITION_NONE. Затем PagerAdapter восстанавливает все фрагменты, которые вернули POSITION_NONE.

НОТА.Лучше было бы сохранить идентификатор верхнего элемента вместо ссылки.

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

public class SomeFragment extends Fragment {
  private TopItem topItem;
}

public class SomePagerAdapter extends FragmentStatePagerAdapter {
  private TopItem topItem;

  public void changeTopItem(TopItem newTopItem) {
    topItem = newTopItem;
    notifyDataSetChanged();
  }

  @Override
  public int getItemPosition(Object object) {
    if (((SomeFragment) object).getTopItemId() != topItem.getId()) {
      return POSITION_NONE;
    }
    return super.getItemPosition(object);
  }
}

Спасибо всем предыдущим исследователям!

Станислав Караханов
источник
2

Код ниже работал для меня.

Создайте класс, который расширяет класс FragmentPagerAdapter, как показано ниже.

public class Adapter extends FragmentPagerAdapter {

private int tabCount;
private Activity mActivity;
private Map<Integer, String> mFragmentTags;
private FragmentManager mFragmentManager;
private int container_id;
private ViewGroup container;
private List<Object> object;

public Adapter(FragmentManager fm) {
    super(fm);
}

public Adapter(FragmentManager fm, int numberOfTabs , Activity mA) {
    super(fm);
    mActivity = mA;
    mFragmentManager = fm;
    object = new ArrayList<>();
    mFragmentTags = new HashMap<Integer, String>();
    this.tabCount = numberOfTabs;
}

@Override
public Fragment getItem(int position) {
    switch (position) {
        case 0:
            return Fragment0.newInstance(mActivity);
        case 1:
            return Fragment1.newInstance(mActivity);
        case 2:
            return Fragment2.newInstance(mActivity);
        default:
            return null;
    }}


@Override
public Object instantiateItem(ViewGroup container, int position) {
    Object object = super.instantiateItem(container, position);
    if (object instanceof Fragment) {
        Log.e("Already defined","Yes");
        Fragment fragment = (Fragment) object;
        String tag = fragment.getTag();
        Log.e("Fragment Tag","" + position + ", " + tag);
        mFragmentTags.put(position, tag);
    }else{
        Log.e("Already defined","No");
    }
    container_id = container.getId();
    this.container = container;
    if(position == 0){
        this.object.add(0,object);
    }else if(position == 1){
        this.object.add(1,object);
    }else if(position == 2){
        this.object.add(2,object);
    }
    return object;
}

@Override
public void destroyItem(ViewGroup container, int position, Object object) {
    super.destroyItem(container, position, object);
    if (object instanceof Fragment) {
        Log.e("Removed" , String.valueOf(position));
    }
}

@Override
public int getItemPosition (Object object)
{   int index = 0;
    if(this.object.get(0) == object){
        index = 0;
    }else if(this.object.get(1) == object){
        index = 1;
    }else if(this.object.get(2) == object){
        index = 2;
    }else{
        index = -1;
    }
    Log.e("Index" , "..................." + String.valueOf(index));
    if (index == -1)
        return POSITION_NONE;
    else
        return index;
}

public String getFragmentTag(int pos){
    return "android:switcher:"+R.id.pager+":"+pos;
}

public void NotifyDataChange(){
    this.notifyDataSetChanged();
}

public int getcontainerId(){
    return container_id;
}

public ViewGroup getContainer(){
    return this.container;
}

public List<Object> getObject(){
    return this.object;
}

@Override
public int getCount() {
    return tabCount;
}}

Затем внутри каждого созданного вами фрагмента создайте метод updateFragment. В этом методе вы изменяете вещи, которые нужно изменить во фрагменте. Например, в моем случае Fragment0 содержал GLSurfaceView, который отображает трехмерный объект на основе пути к файлу .ply, поэтому в моем методе updateFragment я меняю путь к этому файлу ply.

затем создайте экземпляр ViewPager,

viewPager = (ViewPager) findViewById(R.id.pager);

и экземпляр Adpater,

adapter = new Adapter(getSupportFragmentManager(), 3, this);

тогда сделай это,

viewPager.setAdapter(adapter);
viewPager.setOffscreenPageLimit(1);

Затем внутри класса вы инициализировали класс Adapter выше и создали viewPager, каждый раз, когда вы хотите обновить один из ваших фрагментов (в нашем случае Fragment0), используйте следующее:

adapter.NotifyDataChange();

adapter.destroyItem(adapter.getContainer(), 0, adapter.getObject().get(0)); // destroys page 0 in the viewPager.

fragment0 = (Fragment0) getSupportFragmentManager().findFragmentByTag(adapter.getFragmentTag(0)); // Gets fragment instance used on page 0.

fragment0.updateFragment() method which include the updates on this fragment

adapter.instantiateItem(adapter.getContainer(), 0); // re-initialize page 0.

Это решение было основано на методике, предложенной Альваро Луисом Бустаманте.

Brainnovo
источник
1

1. Прежде всего, вам нужно установить метод getItemposition в вашем классе Pageradapter. 2. Вы должны прочитать Точную позицию вашего View Pager. 3. Затем отправить эту позицию в качестве местоположения данных вашего нового 4.. слушатель

этот программный код немного изменен, чтобы установить только конкретный элемент позиции

public class MyActivity extends Activity {

private ViewPager myViewPager;
private List<String> data;
public int location=0;
public Button updateButton;
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

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

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

      updateButton = (Button) findViewById(R.id.update);

    myViewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
        @Override
        public void onPageScrolled(int i, float v, int i2) {
             //Toast.makeText(MyActivity.this, i+"  Is Selected  "+data.size(), Toast.LENGTH_SHORT).show();
        }

        @Override
        public void onPageSelected( int i) {
          // here you will get the position of selected page
            final int k = i;
             updateViewPager(k);

        }

        @Override
        public void onPageScrollStateChanged(int i) {

        }
    });
}

private void updateViewPager(final int i) {  
    updateButton.setOnClickListener(new OnClickListener() {

        @Override
        public void onClick(View v) {

            Toast.makeText(MyActivity.this, i+"  Is Selected  "+data.size(), Toast.LENGTH_SHORT).show();
            data.set(i, "Replaced "+i);         
            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 int getItemPosition(Object object) {
        return POSITION_NONE;
    }

    @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) {
    }
}
}
Навин Кумар
источник
1

что сработало для меня viewPager.getAdapter().notifyDataSetChanged();

а в адаптере выкладываю свой код для обновления вида внутри getItemPositionвот так

@Override
public int getItemPosition(Object object) {

    if (object instanceof YourViewInViewPagerClass) { 
        YourViewInViewPagerClass view = (YourViewInViewPagerClass)object;
        view.setData(data);
    }

    return super.getItemPosition(object);
}

может быть, не самый правильный способ сделать это, но это сработало ( return POSITION_NONEуловка вызвала сбой для меня, поэтому не вариант)

Fonix
источник
1

Вы можете динамически обновлять все фрагменты, вы можете увидеть в три этапа.

В вашем адаптере:

public class MyPagerAdapter extends FragmentPagerAdapter {
private static int NUM_ITEMS = 3;
private Map<Integer, String> mFragmentTags;
private FragmentManager mFragmentManager;

public MyPagerAdapter(FragmentManager fragmentManager) {
    super(fragmentManager);
    mFragmentManager = fragmentManager;
    mFragmentTags = new HashMap<Integer, String>();
}

// Returns total number of pages
@Override
public int getCount() {
    return NUM_ITEMS;
}

// Returns the fragment to display for that page
@Override
public Fragment getItem(int position) {
    switch (position) {
        case 0:
            return FirstFragment.newInstance();
        case 1:
            return SecondFragment.newInstance();
        case 2:
            return ThirdFragment.newInstance();
        default:
            return null;
    }
}

// Returns the page title for the top indicator
@Override
public CharSequence getPageTitle(int position) {
    return "Page " + position;
}

@Override
public Object instantiateItem(ViewGroup container, int position) {
    Object object = super.instantiateItem(container, position);
    if (object instanceof Fragment) {
        Fragment fragment = (Fragment) object;
        String tag = fragment.getTag();
        mFragmentTags.put(position, tag);
    }
    return object;
}

public Fragment getFragment(int position) {
    Fragment fragment = null;
    String tag = mFragmentTags.get(position);
    if (tag != null) {
        fragment = mFragmentManager.findFragmentByTag(tag);
    }
    return fragment;
}}

Сейчас в вашей деятельности:

public class MainActivity extends AppCompatActivity implements ViewPager.OnPageChangeListener{

MyPagerAdapter mAdapterViewPager;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    ViewPager viewPager = (ViewPager) findViewById(R.id.vpPager);
    mAdapterViewPager = new MyPagerAdapter(getSupportFragmentManager());
    viewPager.setAdapter(mAdapterViewPager);
    viewPager.addOnPageChangeListener(this);
}

@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {

}

@Override
public void onPageSelected(int position) {

    Fragment fragment = mAdapterViewPager.getFragment(position);
    if (fragment != null) {
        fragment.onResume();
    }
}

@Override
public void onPageScrollStateChanged(int state) {

}}

Наконец в вашем фрагменте, что-то вроде этого:

public class YourFragment extends Fragment {

// newInstance constructor for creating fragment with arguments
public static YourFragment newInstance() {

    return new YourFragment();
}

// Store instance variables based on arguments passed
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
}

// Inflate the view for the fragment based on layout XML
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {
    return inflater.inflate(R.layout.fragment, container, false);
}


@Override
public void onResume() {
    super.onResume();

    //to refresh your view
    refresh();

}}

Вы можете увидеть полный код здесь .

Спасибо Альваро Луису Бустаманте.

Кабезас
источник
1

Всегда возвращать POSITION_NONEэто просто, но немного неэффективно, потому что это вызывает создание экземпляров всех страниц, которые уже были созданы.

Я создал библиотеку ArrayPagerAdapter для динамического изменения элементов в PagerAdapter.

Внутренне, адаптеры этой библиотеки вернуться POSITION_NONEна getItemPosiition()только тогда , когда это необходимо.

С помощью этой библиотеки вы можете динамически изменять элементы, как описано ниже.

@Override
protected void onCreate(Bundle savedInstanceState) {
        /** ... **/
    adapter = new MyStatePagerAdapter(getSupportFragmentManager()
                            , new String[]{"1", "2", "3"});
    ((ViewPager)findViewById(R.id.view_pager)).setAdapter(adapter);
     adapter.add("4");
     adapter.remove(0);
}

class MyPagerAdapter extends ArrayViewPagerAdapter<String> {

    public MyPagerAdapter(String[] data) {
        super(data);
    }

    @Override
    public View getView(LayoutInflater inflater, ViewGroup container, String item, int position) {
        View v = inflater.inflate(R.layout.item_page, container, false);
        ((TextView) v.findViewById(R.id.item_txt)).setText(item);
        return v;
    }
}

Библиотека Thils также поддерживает страницы, созданные Fragments.

T.Nakama
источник
1

Это для всех таких, как я, которым необходимо обновить Viewpager из службы (или другого фонового потока), и ни одно из предложений не сработало: после небольшой проверки логов я понял, что метод notifyDataSetChanged () никогда не возвращается. getItemPosition (Object object) называется все концами там без дальнейшей обработки. Затем я нашел в документах родительского класса PagerAdapter (не в документах подклассов): «Изменения в наборе данных должны происходить в основном потоке и должны заканчиваться вызовом notifyDataSetChanged ()». Итак, рабочее решение в этом случае (с использованием FragmentStatePagerAdapter и getItemPosition (Object object) было установлено для возврата POSITION_NONE):

а затем вызов notifyDataSetChanged ():

runOnUiThread(new Runnable() {
         @Override
         public void run() {
             pager.getAdapter().notifyDataSetChanged();
         }
     });
Helmwag
источник
1

Вы можете добавить преобразование пейджера на Viewpager, как это

myPager.setPageTransformer(true, new MapPagerTransform());

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

public class MapPagerTransform implements ViewPager.PageTransformer {


    public void transformPage(View view, float position) {
     LinearLayout  showSelectionLL=(LinearLayout)view.findViewById(R.id.showSelectionLL);

     if (position < 0) {

                showSelectionLL.setBackgroundColor(Color.WHITE);
     }else if (position >0){

                showSelectionLL.setBackgroundColor(Color.WHITE);
     }else {
                showSelectionLL.setBackgroundColor(Color.RED);
     }
   }
}
Акшай Дашоре
источник
1

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

хорошо,

сам ответ говорит, что это неэффективно

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

private boolean refresh;

public void refreshAdapter(){
    refresh = true;
    notifyDataSetChanged();
}

@Override
public int getItemPosition(@NonNull Object object) {
    if(refresh){
        refresh = false;
        return POSITION_NONE;
    }else{
        return super.getItemPosition(object);
    }
}
пример
источник
1

ViewPager не был разработан для поддержки динамического изменения вида.

У меня было подтверждение этого при поиске другой ошибки, связанной с этой https://issuetracker.google.com/issues/36956111 и, в частности, https://issuetracker.google.com/issues/36956111#comment56.

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

Для примеров ViewPager2 вы можете проверить https://github.com/googlesamples/android-viewpager2

Если вы хотите использовать ViewPager2, вам нужно добавить следующую зависимость в ваш файл build.gradle:

  dependencies {
     implementation 'androidx.viewpager2:viewpager2:1.0.0-beta02'
  }

Затем вы можете заменить ваш ViewPager в вашем XML-файле на:

    <androidx.viewpager2.widget.ViewPager2
        android:id="@+id/pager"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />

После этого вам нужно будет заменить ViewPager на ViewPager2 в вашей активности

ViewPager2 требуется либо RecyclerView.Adapter, либо FragmentStateAdapter, в вашем случае это может быть RecyclerView.Adapter

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

import java.util.ArrayList;

public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {

    private Context context;
    private ArrayList<String> arrayList = new ArrayList<>();

    public MyAdapter(Context context, ArrayList<String> arrayList) {
        this.context = context;
        this.arrayList = arrayList;
    }

    @NonNull
    @Override
    public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(context).inflate(R.layout.list_item, parent, false);
        return new MyViewHolder(view);
    }

    @Override
    public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
        holder.tvName.setText(arrayList.get(position));
    }

    @Override
    public int getItemCount() {
        return arrayList.size();
    }

    public class MyViewHolder extends RecyclerView.ViewHolder {
        TextView tvName;

        public MyViewHolder(@NonNull View itemView) {
            super(itemView);
            tvName = itemView.findViewById(R.id.tvName);
        }
    }
} 

В случае, если вы использовали TabLayout, вы можете использовать TabLayoutMediator:

        TabLayoutMediator tabLayoutMediator = new TabLayoutMediator(tabLayout, viewPager, true, new TabLayoutMediator.OnConfigureTabCallback() {
            @Override
            public void onConfigureTab(@NotNull TabLayout.Tab tab, int position) {
                // configure your tab here
                tab.setText(tabs.get(position).getTitle());
            }
        });

        tabLayoutMediator.attach();

После этого вы сможете обновить свои представления, изменив данные вашего адаптера и вызвав метод notifyDataSetChanged.

Ив Делерм
источник
0

Я думаю, что я сделал простой способ уведомить об изменениях набора данных:

Во-первых, немного изменим способ работы функции instantiateItem:

    @Override
    public Object instantiateItem(final ViewGroup container, final int position) {
        final View rootView = mInflater.inflate(...,container, false);
        rootView.setTag(position);
        updateView(rootView, position);
        container.addView(rootView, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
        mViewPager.setObjectForPosition(rootView, position);
        return rootView;
    }

для "updateView" заполните представление всеми данными, которые вы хотите заполнить (setText, setBitmapImage, ...).

убедитесь, что destroyView работает так:

    @Override
    public void destroyItem(final ViewGroup container, final int position, final Object obj) {
        final View viewToRemove = (View) obj;
        mViewPager.removeView(viewToRemove);
    }

Теперь предположим, что вам нужно изменить данные, сделать это, а затем вызвать следующую функцию в PagerAdapter:

    public void notifyDataSetChanged(final ViewPager viewPager, final NotifyLocation fromPos,
            final NotifyLocation toPos) {
        final int offscreenPageLimit = viewPager.getOffscreenPageLimit();
        final int fromPosInt = fromPos == NotifyLocation.CENTER ? mSelectedPhotoIndex
                : fromPos == NotifyLocation.MOST_LEFT ? mSelectedPhotoIndex - offscreenPageLimit
                        : mSelectedPhotoIndex + offscreenPageLimit;
        final int toPosInt = toPos == NotifyLocation.CENTER ? mSelectedPhotoIndex
                : toPos == NotifyLocation.MOST_LEFT ? mSelectedPhotoIndex - offscreenPageLimit
                        : mSelectedPhotoIndex + offscreenPageLimit;
        if (fromPosInt <= toPosInt) {
            notifyDataSetChanged();
            for (int i = fromPosInt; i <= toPosInt; ++i) {
                final View pageView = viewPager.findViewWithTag(i);
                mPagerAdapter.updateView(pageView, i);
            }
        }
    }

public enum NotifyLocation {
    MOST_LEFT, CENTER, MOST_RIGHT
}

Например, если вы хотите уведомить все представления, отображаемые viewPager, о том, что что-то изменилось, вы можете вызвать:

notifyDataSetChanged(mViewPager,NotifyLocation.MOST_LEFT,NotifyLocation.MOST_RIGHT);

Вот и все.

разработчик Android
источник
0

Что бы это ни стоило, на KitKat + кажется, что этого adapter.notifyDataSetChanged()достаточно для отображения новых представлений, при условии, что вы setOffscreenPageLimitдостаточно высоко. Я могу получить желаемое поведение, делая viewPager.setOffscreenPageLimit(2).

mszaro
источник
0

Я на самом деле использовать notifyDataSetChanged()на ViewPagerи CirclePageIndicatorи после этого я вызываю destroyDrawingCache()на ViewPagerи это работает .. Ни один из других решений не работает для меня.

czaku
источник
Первоначальному вопросу уже почти 2 года, я уверен, что в Android API есть много различий, поскольку ответы здесь могут быть не подходящими для всех версий Android API.
C0deAttack