правильно вставлять и удалять фрагменты в viewpager

Я делаю приложение для Android с просмотрщиком и фрагментами. Я хочу сделать возможность динамически добавлять или удалять страницы фрагментов в пейджере. У меня есть собственный FragmentPagerAdapter:

public class MyAdapter extends FragmentPagerAdapter implements TitleProvider{

    protected final List<PageFragment> fragments;

    /**
    * @param fm
    * @param fragments
    */
    public MyAdapter(FragmentManager fm, List<PageFragment> fragments) {

        super(fm);
        this.fragments = fragments;
    }

    public void addItem(PageFragment f){
        fragments.add(f);
        notifyDataSetChanged();
    }

    public void addItem(int pos, PageFragment f){
        for(int i=0;i<fragments.size();i++){
            if(i>=pos){
                getSupportFragmentManager().beginTransaction().remove(getSupportFragmentManager().findFragmentByTag(getFragmentTag(i))).commit();
            }
        }
        fragments.add(pos,f);
        notifyDataSetChanged();
        pager.setAdapter(this);
        pager.setCurrentItem(pos);

    }

    public void removeItem(int pos){
        getSupportFragmentManager().beginTransaction().remove(getSupportFragmentManager().findFragmentByTag(getFragmentTag(pos))).commit();
        for(int i=0;i<fragments.size();i++){
            if(i>pos){
                getSupportFragmentManager().beginTransaction().remove(getSupportFragmentManager().findFragmentByTag(getFragmentTag(i))).commit();
            }
        }
        fragments.remove(pos);

        notifyDataSetChanged();
        pager.setAdapter(this);
        if(pos<fragments.size()){
            pager.setCurrentItem(pos);
        }else{
            pager.setCurrentItem(pos-1);
        }
    }



    @Override
    public Fragment getItem(int position) {
        return fragments.get(position).toFragment();
    }

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

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

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

    public String getTitle(int position) {
        String name =  fragments.get(position).getName();
        if(name.equals(""))
            return "- "+position+" -";
        return name;
    }
}

Я могу удалить любой фрагмент, кроме 0. когда я пытаюсь удалить его, я получаю NullPointerException на notifyDataSetChanged(); или на странице pager.setAdapter(this); если я закомментирую уведомление.

Я также получил исключение NullPointerException, когда пытаюсь вставить новую страницу. Когда я добавляю новую страницу в конец списка, она работает нормально. Я даже пытался читать фрагменты во вставке с этим после фрагментов.add(pos,f)

    for(int i=0;i<fragments.size();i++){
            if(i>=pos){
                getSupportFragmentManager().beginTransaction().add(fragments.get(i).toFragment(),getFragmentTag(i-1)).commit();
            }
        }

если я использую getFragmentTag(i-1), я снова получаю нулевой указатель. Используя только i, я получил недопустимое исключение состояния, потому что не могу изменить тег фрагмента. С beginTransaction().add(pager.getId,fragments.get(i).toFragment()) это все еще нулевой указатель...

У меня вопрос: что я делаю не так, и как это можно сделать правильно? (и, может быть: откуда notyfyDataSetChanged() брать данные, когда вызывает исключение nullpointerexception?)


person Blackguard    schedule 11.04.2012    source источник


Ответы (3)


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

java.lang.IllegalStateException: не удается изменить тег фрагмента MyFragment{4055f558 id=0x7f050034 android:switcher:2131034164:3}: был android:switcher:2131034164:3, теперь android:switcher:2131034164:4

Этот адаптер создается со списком всех страниц заранее, и затем вы можете использовать setEnabled(int position, boolean enable) для отключения или включения определенных страниц, которые скрывают или показывают эти страницы в ViewPager.

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

public class DynamicFragmentPagerAdapter extends FragmentPagerAdapter {

    public final ArrayList<Fragment> screens = new ArrayList<Fragment>();

    private Context context;
    private List<AtomicBoolean> flags = new ArrayList<AtomicBoolean>();

    public DynamicFragmentPagerAdapter(FragmentManager fm, Context context, List<Class<?>> screens) {
        super(fm);
        this.context = context;

        for(Class<?> screen : screens)
            addScreen(screen, null);

        notifyDataSetChanged();
    }

    public DynamicFragmentPagerAdapter(FragmentManager fm, Context context, Map<Class<?>, Bundle> screens) {
        super(fm);
        this.context = context;

        for(Class<?> screen : screens.keySet())
            addScreen(screen, screens.get(screen));

        notifyDataSetChanged();
    }

    private void addScreen(Class<?> clazz, Bundle args) {
        screens.add(Fragment.instantiate(context, clazz.getName(), args));
        flags.add(new AtomicBoolean(true));
    }

    public boolean isEnabled(int position) {
        return flags.get(position).get();
    }

    public void setEnabled(int position, boolean enabled) {
        AtomicBoolean flag = flags.get(position);
        if(flag.get() != enabled) {
            flag.set(enabled);
            notifyDataSetChanged();
        }
    }

    @Override
    public int getCount() {
        int n = 0;
        for(AtomicBoolean flag : flags) {
            if(flag.get())
                n++;
        }
        return n;
    }

    @Override
    public Fragment getItem(int position) {
        return screens.get(position);
    }

    @Override
    public int getItemPosition(Object object) {
        return POSITION_NONE; // To make notifyDataSetChanged() do something
    }

    @Override
    public void notifyDataSetChanged() {
        try {
            super.notifyDataSetChanged();
        } catch (Exception e) {
            Log.w("", e);
        }
    }

    private List<Fragment> getEnabledScreens() {
        List<Fragment> res = new ArrayList<Fragment>();
        for(int n = 0; n < screens.size(); n++) {
            if(flags.get(n).get())
                res.add(screens.get(n));
        }
        return res;
    }

    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        // We map the requested position to the position as per our screens array list
        Fragment fragment = getEnabledScreens().get(position);
        int internalPosition = screens.indexOf(fragment);
        return super.instantiateItem(container, internalPosition);
    }

    @Override
    public CharSequence getPageTitle(int position) {
        Fragment fragment = getEnabledScreens().get(position);
        if(fragment instanceof TitledFragment)
            return ((TitledFragment)fragment).getTitle(context);
        return super.getPageTitle(position);
    }

    public static interface TitledFragment {
        public String getTitle(Context context);
    }
}
person Oliver Jonas    schedule 21.02.2013
comment
Он работает нормально, но когда вызывается notifyDatasetChanged, он создает новые представления для каждого Fragment, поэтому я теряю состояние пользовательского интерфейса. Как я могу предотвратить это? - person WonderCsabo; 09.11.2013

У меня есть решение, которое работает с адаптером, не основанным на фрагментах. Взгляните на мой пост и посмотрите, поможет ли он.

person Peri Hartman    schedule 02.12.2012
comment
Только ссылки на ответы не рекомендуются. - person Mooing Duck; 08.07.2015

Я могу удалить любой фрагмент, кроме 0.

Для меня это работает, если просто использовать условие >= в цикле for removeItem:

     if (i >= pos){
          fm.beginTransaction().remove(fragments.get(i).getFragment()).commit();
      }
person Alex    schedule 08.02.2013