Динамически обновлять ViewPager?
Я не могу обновить содержимое в ViewPager.
вопрос: какова связь и правильное использование методов instantiateItem() и getItem() в классе FragmentPagerAdapter?
я использовал только getItem () для создания экземпляра и возврата моих фрагментов:
@Override
public Fragment getItem(int position) {
return new MyFragment(context, paramters);
}
это сработало хорошо (но, как сказано, я не могу изменить содержимое).
Итак, я нашел это: ViewPager PagerAdapter не обновляет представление
в частности, один в котором говорится о методе instantiateItem ():
" мой подход заключается в использовании метода setTag () для любого экземпляра представления в методе instantiateItem ()"
поэтому теперь я хочу реализовать instantiateItem () для этого. Но я не знаю, что я должен туда вернуть (return is Object) и каково отношение с getItem(int position)?
В настоящее время я использую getItem для создания экземпляра фрагмента, это неправильно? Но тогда ... необходимо поместить фрагменты в переменную экземпляра или вообще не реализовывать getItem ()...? Я просто не понимаю.
Я пробовал читать ссылка:
публичный абстрактный фрагмент getItem (int position)
возвращает фрагмент, связанный с указанной позицией.
public Object instantiateItem (ViewGroup container, int position)
создать страницу для данной позиции. Адаптер отвечает за добавление представления в контейнер, указанный здесь, хотя он должен только убедиться, что это сделано к моменту его возвращения из finishUpdate(ViewGroup). Параметры
container содержащее представление, в котором будет показана страница. расположите позицию страницы для создания экземпляра.
возвращает
возвращает объект, представляющий новую страницу. Это не обязательно должен быть вид, но может быть какой-то другой контейнер страницы.
... но я все равно не понимаю, как они связаны и что я должен делать.
вот мой код. Я использую пакет поддержки v4.
ViewPagerTest
public class ViewPagerTest extends FragmentActivity {
private ViewPager pager;
private MyFragmentAdapter adapter;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.pager1);
pager = (ViewPager)findViewById(R.id.slider);
String[] data = {"page1", "page2", "page3", "page4", "page5", "page6"};
adapter = new MyFragmentAdapter(getSupportFragmentManager(), 6, this, data);
pager.setAdapter(adapter);
((Button)findViewById(R.id.button)).setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
reload();
}
});
}
private void reload() {
String[] data = {"changed1", "changed2", "changed3", "changed4", "changed5", "changed6"};
//adapter = new MyFragmentAdapter(getSupportFragmentManager(), 6, this, data);
adapter.setData(data);
adapter.notifyDataSetChanged();
pager.invalidate();
//pager.setCurrentItem(0);
}
}
MyFragmentAdapter
class MyFragmentAdapter extends FragmentPagerAdapter {
private int slideCount;
private Context context;
private String[] data;
public MyFragmentAdapter(FragmentManager fm, int slideCount, Context context, String[] data) {
super(fm);
this.slideCount = slideCount;
this.context = context;
this.data = data;
}
@Override
public Fragment getItem(int position) {
return new MyFragment(data[position], context);
}
@Override
public int getCount() {
return slideCount;
}
public void setData(String[] data) {
this.data = data;
}
@Override
public int getItemPosition(Object object) {
return POSITION_NONE;
}
}
MyFragment
public final class MyFragment extends Fragment {
private String text;
public MyFragment(String text, Context context) {
this.text = text;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.slide, null);
((TextView)view.findViewById(R.id.text)).setText(text);
return view;
}
}
здесь тоже кто-то с подобной проблемой, не ответы...
http://www.mail-archive.com/android-developers@googlegroups.com/msg200477.html
18 ответов
при использовании FragmentPagerAdapter или FragmentStatePagerAdapter лучше всего иметь дело исключительно с getItem()
Не трогать instantiateItem()
на всех. The instantiateItem()
-destroyItem()
-isViewFromObject()
интерфейс на PagerAdapter-это интерфейс нижнего уровня, который FragmentPagerAdapter использует для реализации гораздо более простого getItem()
интерфейс.
прежде чем вдаваться в это, я должен уточнить, что
если вы хотите переключить вне фактические части которые показываются, то вам чтобы избежать FragmentPagerAdapter и использовать FragmentStatePagerAdapter.
более ранняя версия этого ответа сделала ошибку, используя FragmentPagerAdapter для своего примера - это не сработает, потому что FragmentPagerAdapter никогда не уничтожает фрагмент после его отображения в первый раз.
я не рекомендую setTag()
и findViewWithTag()
обходной путь, указанный в сообщении, которое вы связали. Как вы обнаружили, используя setTag()
и findViewWithTag()
не работает фрагменты, так что это не очень хорошее совпадение.
правильное решение-переопределить getItemPosition()
. Когда notifyDataSetChanged()
вызывается, ViewPager вызывает getItemPosition()
на всех деталях в своем переходнике для того чтобы увидеть нужно ли их двинуть к другому положению или извлечь.
по умолчанию getItemPosition()
возвращает POSITION_UNCHANGED
, что означает: "этот объект хорош там, где он есть, не уничтожайте и не удаляйте его.- Возвращение!--16--> устраняет проблему, вместо этого говоря :" этот объект больше не является элементом, я отображение, удалить его."Таким образом, это имеет эффект удаления и воссоздания каждого элемента в вашем адаптере.
это полностью законное исправление! Это исправление делает notifyDataSetChanged вести себя как обычный адаптер без повторного просмотра. Если вы реализуете это исправление и производительность удовлетворительна, вы отправляетесь на гонки. Дело сделано.
Если вам нужна лучшая производительность, вы можете использовать любитель getItemPosition()
реализация. Вот пример для пейджера, создающего фрагменты из списка строк:
ViewPager pager = /* get my ViewPager */;
// assume this actually has stuff in it
final ArrayList<String> titles = new ArrayList<String>();
FragmentManager fm = getSupportFragmentManager();
pager.setAdapter(new FragmentStatePagerAdapter(fm) {
public int getCount() {
return titles.size();
}
public Fragment getItem(int position) {
MyFragment fragment = new MyFragment();
fragment.setTitle(titles.get(position));
return fragment;
}
public int getItemPosition(Object item) {
MyFragment fragment = (MyFragment)item;
String title = fragment.getTitle();
int position = titles.indexOf(title);
if (position >= 0) {
return position;
} else {
return POSITION_NONE;
}
}
});
С этой реализацией будут отображаться только фрагменты, отображающие новые названия. Любые фрагменты, отображающие заголовки, которые все еще находятся в списке, будут перемещены на новое место в списке, а фрагменты с заголовками, которых больше нет в списке, будут уничтожены.
что делать, если фрагмент не был воссоздан, но должен быть обновлен в любом случае? Обновления живого фрагмента лучше всего обрабатываются фрагмент себя. В этом преимущество наличия фрагмента, в конце концов-это его собственный контроллер. Фрагмент может добавить слушателя или наблюдателя к другому объекту в onCreate()
, а затем удалите его в onDestroy()
, таким образом управляя обновлениями. Вам не нужно помещать весь код обновления внутрь getItem()
как и в адаптере для ListView или других типов AdapterView.
последнее - только потому, что FragmentPagerAdapter не уничтожает фрагмент, не означает, что getItemPosition совершенно бесполезен в FragmentPagerAdapter. Вы все еще можете использовать этот обратный вызов для изменения порядка фрагментов в ViewPager. Однако он никогда не удалит их полностью из FragmentManager.
вместо возврата POSITION_NONE
С getItemPosition()
и вызывает полный вид отдыха, сделайте следующее:
//call this method to update fragments in ViewPager dynamically
public void update(UpdateData xyzData) {
this.updateData = xyzData;
notifyDataSetChanged();
}
@Override
public int getItemPosition(Object object) {
if (object instanceof UpdateableFragment) {
((UpdateableFragment) object).update(updateData);
}
//don't return POSITION_NONE, avoid fragment recreation.
return super.getItemPosition(object);
}
фрагменты должны реализовать UpdateableFragment
интерфейс:
public class SomeFragment extends Fragment implements
UpdateableFragment{
@Override
public void update(UpdateData xyzData) {
// this method will be called for every fragment in viewpager
// so check if update is for this fragment
if(forMe(xyzData)) {
// do whatever you want to update your UI
}
}
}
интерфейс:
public interface UpdateableFragment {
public void update(UpdateData xyzData);
}
свой класс данных:
public class UpdateData {
//whatever you want here
}
для тех, кто все еще сталкивается с той же проблемой, с которой я сталкивался раньше, когда у меня есть ViewPager с 7 фрагментами. по умолчанию эти фрагменты загружают английский контент из службы api, но проблема здесь в том, что я хочу изменить язык из действия настроек и после завершения действие настроек я хочу, чтобы ViewPager в основном действии обновлял фрагменты в соответствии с выбором языка от пользователя и загружал арабский контент, если пользователь выбрал арабский здесь, что я сделал для работы с первый раз
1 - Вы должны использовать FragmentStatePagerAdapter, как упоминалось выше.
2-on mainActivity я переопределяю onResume и сделал следующее
if (!(mPagerAdapter == null)) {
mPagerAdapter.notifyDataSetChanged();
}
3 - я переопределил getItemPosition () в mPagerAdapter и заставил его вернуть POSITION_NONE.
@Override
public int getItemPosition(Object object) {
return POSITION_NONE;
}
все работает.
я столкнулся с этой проблемой и, наконец, решил ее сегодня, поэтому я записываю то, что я узнал, и я надеюсь, что это полезно для тех, кто новичок в Android ViewPager
и обновление, как я делаю. Я использую FragmentStatePagerAdapter
на уровне API 17 и в настоящее время имеют только 2 фрагмента. Я думаю, что должно быть что-то не так, пожалуйста, поправьте меня, спасибо.
сериализованные данные должны быть загружены в память. Это можно сделать с помощью
CursorLoader
/AsyncTask
/Thread
. Будь то он автоматически загружается в зависимости от вашего кода. Если вы используетеCursorLoader
, это автоматически загружается, так как есть зарегистрированный наблюдатель данных.после вызова
viewpager.setAdapter(pageradapter)
, адаптераgetCount()
постоянно вызывается для построения фрагментов. Поэтому, если данные загружаются,getCount()
может возвращать 0, поэтому вам не нужно создавать фиктивные фрагменты для не показанных данных.после загрузки данных, то адаптер не будет автоматически создавать фрагменты с
getCount()
по-прежнему 0, поэтому мы можем установить фактически загруженный номер данных, который будет возвращенgetCount()
, затем вызовите адаптерnotifyDataSetChanged()
.ViewPager
начните создавать фрагменты (только первые 2 фрагмента) по данным в памяти. Это делается раньшеnotifyDataSetChanged()
возвращается. ТогдаViewPager
имеет правильные фрагменты, которые вам нужны.если данные в базе данных и памяти обновляются (запись через), или просто данные в памяти обновляются (запись обратно), или только данные в базе данных усовершенствованный. В последних двух случаях, если данные не загружаются автоматически из базы данных в память (как указано выше). The
ViewPager
и адаптер пейджера просто имеют дело с данными в памяти.поэтому, когда данные в памяти обновляются, нам просто нужно вызвать адаптер
notifyDataSetChanged()
. Поскольку фрагмент уже создан, адаптерonItemPosition()
передnotifyDataSetChanged()
возвращает. Ничего не нужно делать вgetItemPosition()
. Затем данные обновляются.
после нескольких часов разочарования, пробуя все вышеперечисленные решения для преодоления этой проблемы, а также пытаясь многие решения по другим подобным вопросам, как этой, этой и этой который все не удалось со мной решить эту проблему и сделать ViewPager
уничтожить старое Fragment
и заполнить pager
новая Fragment
s. Я решил проблему следующим образом:
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
С напрямую сделать 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();
надеюсь, это поможет.
private class MyAdapter extends FragmentStatePagerAdapter {
// [Rest of implementation]
@Override
public void restoreState(Parcelable state, ClassLoader loader) {}
}
Я немного изменил решение, предоставленное Биллом Филлипсом, чтобы удовлетворить мои потребности
private class PagerAdapter extends FragmentStatePagerAdapter{
Bundle oBundle;
FragmentManager oFragmentManager;
ArrayList<Fragment> oPooledFragments;
public PagerAdapter(FragmentManager fm) {
super(fm);
oFragmentManager=fm;
}
@Override
public int getItemPosition(Object object) {
Fragment oFragment=(Fragment)object;
oPooledFragments=new ArrayList<>(oFragmentManager.getFragments());
if(oPooledFragments.contains(oFragment))
return POSITION_NONE;
else
return POSITION_UNCHANGED;
}
}
так что getItemPosition()
возвращает POSITION_NONE
только для тех фрагментов, которые в настоящее время находятся в FragmentManager
, когда getItemPosition
называется.
(Обратите внимание, что это FragmentStatePager
и ViewPager
связанные с ним содержатся во фрагменте не в деятельности)
У меня была аналогичная проблема, но я не хочу доверять существующим решениям (имена жестко закодированных тегов и т. д.) и я не мог заставить решение м-Ваджи работать на меня. Вот мое решение:
Я сохраняю ссылки на фрагменты, созданные в getItem в массиве. Это прекрасно работает до тех пор, пока действие не уничтожается из-за configurationChange или отсутствия памяти или чего-либо еще (--> возвращаясь к действию, фрагменты возвращаются в свое последнее состояние без повторного вызова getItem и при этом без обновления массива).
чтобы избежать этой проблемы, я реализовал instantiateItem (ViewGroup, int) и обновил свой массив там, например:
@Override
public Object instantiateItem(ViewGroup container, int position) {
Object o = super.instantiateItem(container, position);
if(o instanceof FragmentX){
myFragments[0] = (FragmentX)o;
}else if(o instanceof FragmentY){
myFragments[1] = (FragmentY)o;
}else if(o instanceof FragmentZ){
myFragments[2] = (FragmentZ)o;
}
return o;
}
Если вы хотите использовать FragmentStatePagerAdapter, пожалуйста, взгляните на https://code.google.com/p/android/issues/detail?can=2&start=0&num=100&q=&colspec=ID%20Type%20Status%20Owner%20Summary%20Stars&groupby=&sort=&id=37990. Есть проблемы с FragmentStatePagerAdapter, которые могут или не могут беспокоить ваш прецедент.
кроме того, link также имеет несколько решений..немногие могут одеть к вашему требованию.
Я жил той же проблемой, и я искал слишком много раз. Любой ответ, данный в stackoverflow или через google, не был решением моей проблемы. Моя проблема была проста. У меня есть список, я показываю этот список с помощью viewpager. Когда я добавляю новый элемент в начало списка и обновляю viewpager, ничего не меняется. Мое окончательное решение было очень простым. Когда новый элемент добавлен в список и хотите обновить список. Сначала установите адаптер viewpager в null, затем воссоздайте адаптер и установите i к нему в viewpager.
myVPager.setAdapter(null);
myFragmentAdapter = new MyFragmentAdapter(getSupportFragmentManager(),newList);
myVPager.setAdapter(myFragmentAdapter);
убедитесь, что ваш адаптер должен расширить FragmentStatePagerAdapter
я использую EventBus библиотеки по обновлению Fragment
контент ViewPager
. Логика проста, как и документ EventBus, как это сделать. Это не нужно контролировать FragmentPagerAdapter
экземпляра. Код здесь:
1: Определение событий
определите, какое сообщение необходимо обновить.
public class UpdateCountEvent {
public final int count;
public UpdateCountEvent(int count) {
this.count = count;
}
}
2.Подготовьте подписчиков
напишите ниже код в фрагменте, который необходимо обновить.
@Override
public void onStart() {
super.onStart();
EventBus.getDefault().register(this);
}
@Override
public void onStop() {
EventBus.getDefault().unregister(this);
super.onStop();
}
public void onEvent(UpdateCountEvent event) {//get update message
Toast.makeText(getActivity(), event.count, Toast.LENGTH_SHORT).show();
}
3.Должность события
напишите ниже код в другом Activity
и Fragment
который должен обновить параметр
//input update message
EventBus.getDefault().post(new UpdateCountEvent(count));
Я пробовал так много разных подходов, ни один из них не решал мою проблему. Ниже приведены, как я решаю его с помощью сочетания решений, предоставляемых всеми вами. Поблагодарить каждого.
class PagerAdapter extends FragmentPagerAdapter {
public boolean flag_refresh=false;
public PagerAdapter(FragmentManager fm) {
super(fm);
}
@Override
public Fragment getItem(int page) {
FragmentsMain f;
f=new FragmentsMain();
f.page=page;
return f;
}
@Override
public int getCount() {
return 4;
}
@Override
public int getItemPosition(Object item) {
int page= ((FragmentsMain)item).page;
if (page == 0 && flag_refresh) {
flag_refresh=false;
return POSITION_NONE;
} else {
return super.getItemPosition(item);
}
}
@Override
public void destroyItem(View container, int position, Object object) {
((ViewPager) container).removeView((View) object);
}
}
Я хочу обновить страницу 0 только после onResume ().
adapter=new PagerAdapter(getSupportFragmentManager());
pager.setAdapter(adapter);
@Override
protected void onResume() {
super.onResume();
if (adapter!=null) {
adapter.flag_refresh=true;
adapter.notifyDataSetChanged();
}
}
В моем FragmentsMain есть публичное целое число "страница", которое может сказать мне, является ли это страница, которую я хочу обновить.
public class FragmentsMain extends Fragment {
private Cursor cursor;
private static Context context;
public int page=-1;
Я знаю, что опаздываю на вечеринку. Я исправил проблему, позвонив TabLayout#setupWithViewPager(myViewPager);
сразу после FragmentPagerAdapter#notifyDataSetChanged();
Это решение не будет работать для всех, но в моем случае каждый фрагмент в моем ViewPager является другим классом, и только один из них существует одновременно.
С этим ограничением это решение безопасно и должно быть безопасным для использования в производстве.
private void updateFragment(Item item) {
List<Fragment> fragments = getSupportFragmentManager().getFragments();
for (Fragment fragment : fragments) {
if (fragment instanceof MyItemFragment && fragment.isVisible()) {
((MyItemFragment) fragment).update(item);
}
}
}
если у вас есть несколько версий одного и того же фрагмента, вы можете использовать эту же стратегию для вызова методов на этих фрагментах, чтобы определить, является ли это фрагмент, который вы хотите обновить.
это может помочь кому - то-в моем случае при вставке новой страницы пейджер просмотра запрашивал позицию существующего фрагмента дважды, но не запрашивал позицию нового элемента, вызывая неправильное поведение и данные не отображаются.
скопируйте источник для FragmentStatePagerAdapter (кажется, не обновлялся в возрасте).
-
переопределить notifyDataSetChanged()
@Override public void notifyDataSetChanged() { mFragments.clear(); super.notifyDataSetChanged(); }
-
добавить a проверка вменяемости на destroyItem () для предотвращения сбоев:
if (position < mFragments.size()) { mFragments.set(position, null); }
Я прошел через все ответы выше и ряд других сообщений, но все еще не мог найти то, что сработало для меня (с различными типами фрагментов наряду с динамическим добавлением и удалением вкладок). Fwiw следующий подход-это то, что сработало для меня (в случае, если у кого-то еще есть такие же проблемы).
public class MyFragmentStatePageAdapter extends FragmentStatePagerAdapter {
private static final String TAB1_TITLE = "Tab 1";
private static final String TAB2_TITLE = "Tab 2";
private static final String TAB3_TITLE = "Tab 3";
private ArrayList<String> titles = new ArrayList<>();
private Map<Fragment, Integer> fragmentPositions = new HashMap<>();
public MyFragmentStatePageAdapter(FragmentManager fm) {
super(fm);
}
public void update(boolean showTab1, boolean showTab2, boolean showTab3) {
titles.clear();
if (showTab1) {
titles.add(TAB1_TITLE);
}
if (showTab2) {
titles.add(TAB2_TITLE);
}
if (showTab3) {
titles.add(TAB3_TITLE);
}
notifyDataSetChanged();
}
@Override
public int getCount() {
return titles.size();
}
@Override
public Fragment getItem(int position) {
Fragment fragment = null;
String tabName = titles.get(position);
if (tabName.equals(TAB1_TITLE)) {
fragment = Tab1Fragment.newInstance();
} else if (tabName.equals(TAB2_TITLE)) {
fragment = Tab2Fragment.newInstance();
} else if (tabName.equals(TAB3_TITLE)) {
fragment = Tab3Fragmen.newInstance();
}
((BaseFragment)fragment).setTitle(tabName);
fragmentPositions.put(fragment, position);
return fragment;
}
@Override
public CharSequence getPageTitle(int position) {
return titles.get(position);
}
@Override
public int getItemPosition(Object item) {
BaseFragment fragment = (BaseFragment)item;
String title = fragment.getTitle();
int position = titles.indexOf(title);
Integer fragmentPosition = fragmentPositions.get(item);
if (fragmentPosition != null && position == fragmentPosition) {
return POSITION_UNCHANGED;
} else {
return POSITION_NONE;
}
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
super.destroyItem(container, position, object);
fragmentPositions.remove(object);
}
}
используйте FragmentStatePagerAdapter вместо FragmentPagerAdapter если вы хотите воссоздать или перезагрузить фрагмент на основе индекса Например, если вы хотите перезагрузить фрагмент, отличный от FirstFragment, вы можете проверить экземпляр и вернуть позицию следующим образом
public int getItemPosition(Object item) {
if(item instanceof FirstFragment){
return 0;
}
return POSITION_NONE;
}