Динамически обновлять 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 фрагмента. Я думаю, что должно быть что-то не так, пожалуйста, поправьте меня, спасибо. enter image description here

  1. сериализованные данные должны быть загружены в память. Это можно сделать с помощью CursorLoader/AsyncTask/Thread. Будь то он автоматически загружается в зависимости от вашего кода. Если вы используете CursorLoader, это автоматически загружается, так как есть зарегистрированный наблюдатель данных.

  2. после вызова viewpager.setAdapter(pageradapter), адаптера getCount() постоянно вызывается для построения фрагментов. Поэтому, если данные загружаются,getCount() может возвращать 0, поэтому вам не нужно создавать фиктивные фрагменты для не показанных данных.

  3. после загрузки данных, то адаптер не будет автоматически создавать фрагменты с getCount() по-прежнему 0, поэтому мы можем установить фактически загруженный номер данных, который будет возвращен getCount(), затем вызовите адаптер notifyDataSetChanged(). ViewPager начните создавать фрагменты (только первые 2 фрагмента) по данным в памяти. Это делается раньше notifyDataSetChanged() возвращается. Тогда ViewPager имеет правильные фрагменты, которые вам нужны.

  4. если данные в базе данных и памяти обновляются (запись через), или просто данные в памяти обновляются (запись обратно), или только данные в базе данных усовершенствованный. В последних двух случаях, если данные не загружаются автоматически из базы данных в память (как указано выше). The ViewPager и адаптер пейджера просто имеют дело с данными в памяти.

  5. поэтому, когда данные в памяти обновляются, нам просто нужно вызвать адаптер notifyDataSetChanged(). Поскольку фрагмент уже создан, адаптер onItemPosition() перед notifyDataSetChanged() возвращает. Ничего не нужно делать в getItemPosition(). Затем данные обновляются.


попробовать destroyDrawingCache() на ViewPager после notifyDataSetChanged() в коде.


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

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);
        }

    }
}

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


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

  1. скопируйте источник для FragmentStatePagerAdapter (кажется, не обновлялся в возрасте).

  2. переопределить notifyDataSetChanged()

    @Override
    public void notifyDataSetChanged() {
        mFragments.clear();
        super.notifyDataSetChanged();
    }
    
  3. добавить 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;
 }