Viewpager и FragmentPagerAdapter: удаление вида страницы

Я использую Viewpager с FragmentPagerAdapter, чтобы добавлять и удалять страницы. На каждой странице отображаются данные, полученные из Интернета.

По мере добавления новой страницы с этой страницей связан новый фрагмент. Данные получены через AsyncTask и отображаются во Фрагменте. Когда пользователь выбирает удаление страницы, идея состоит в том, чтобы уничтожить страницу и связанный с ней фрагмент.

В общем, все это хорошо работает. Проблема, которую я вижу, следующая:

  • У вас есть три страницы с данными:

    [Страница 1] [Страница 2] [Страница 3]

  • Вы удаляете любую страницу, отличную от последней, например, стр. 2; Исчезает по желанию:

    [Страница 1] [Страница 3]

  • Вы добавляете новую страницу; но вместо пустой новой страницы на новой странице отображаются данные (вид) со страницы 3.

    [Страница 1] [Страница 3] [Страница 4, но показ представления/данных Page 3, должен быть пустым]


Код удаления страницы в моей деятельности выглядит следующим образом:

   // Destroy fragment for this page
   DataListFragment curFrag = getFragmentByName(currentPage);
   FragmentManager fm = getSupportFragmentManager();
   fm.beginTransaction().remove(curFrag).commit();
   fm.executePendingTransactions();
   curFrag = null;

   // Remove page and update adapter
   mPageTitles.remove(position);        
   mAdapter.notifyDataSetChanged();

Используя отладчик, он показывает, что фрагмент удаляется из FragmentManager после вызова executePendingTransactions(). Но в вызове FrampePagerAdapters mAdapter.notifyDataSetChanged() фрагмент добавляется обратно, а затем отображается при создании новой страницы.

Я попытался использовать FrameStatePagerAdapter, так как это должно позволить уничтожить фрагменты, но это не сработало. В моем методе FragmentPagerAdapter getItemPosition() я использую return FragmentAdapter.POSITION_NONE;, как указано в другой статье SO, с которой я столкнулся.

Кажется, что представление для этой страницы не уничтожено, а затем добавлено обратно на новую страницу. Я попытался использовать метод removeViewAt() в представлении новой страницы, но это не сработало.


Будучи новичком в этом, я уверен, что мне не хватает чего-то очевидного...

Ответ 1

Вам лучше расширить FragmentStatePagerAdapter и удалить соответствующий элемент из списка элементов этого адаптера вместо того, чтобы пытаться удалить фрагмент из действия. Не забудьте вызвать adapter.notifyDataSetChanged() после удаления элемента в адаптере. После этого ViewPager и FragmentStatePagerAdapter позаботятся об остальном.

Ответ 2

См. метод getCount() возвращает точное количество элементов в viewPager. И да, FragmentStatePagerAdapter тоже подсчитывается.

Ответ 3

Я закончил с решением, которое смешивает следующие знания, основанные на опыте:

  • Вы можете добавить новый Fragment в хвост без проблем.
  • Вы не можете прочитать Fragment, который ранее был удален, поскольку иногда это приводит к java.lang.IllegalStateException: Can't change tag of fragment, поэтому вам нужно клонировать его.
  • Для удаления Fragment вам нужно вернуть PagerAdapter.POSITION_NONE в методе getItemPosition(Object object) и удалить Fragment из FragmentManager.
  • Если вы добавляете/удаляете/заменяете в другом месте, отличном от хвоста, вам нужно удалить все с позиции, которую вы меняете до конца, делать вещи, а затем читать (клонированные) Fragment, которые вы удалены.

Вот полный код FragmentActivity с FragmentPagerAdapter, который имеет 3 метода для добавления, удаления и замены вкладок:

public class TabTestActivity extends FragmentActivity implements
        ActionBar.TabListener {
    private SectionsPagerAdapter mSectionsPagerAdapter;
    private ViewPager mViewPager;
    private static int tabCount = 0;
    private static String labelString = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        labelString = getString(R.string.title_section);
        setContentView(R.layout.activity_tab_test);
        final ActionBar actionBar = getActionBar();
        actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
        mSectionsPagerAdapter = new SectionsPagerAdapter(
                getSupportFragmentManager());
        mViewPager = (ViewPager) findViewById(R.id.pager);
        mViewPager.setAdapter(mSectionsPagerAdapter);
        mViewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
                    @Override
                    public void onPageSelected(final int position) {
                        (new Handler()).postDelayed(new Runnable() {

                            @Override
                            public void run() {
                                actionBar.setSelectedNavigationItem(position);
                            }

                        }, 1);
                    }
                });

        for (int i = 0; i < mSectionsPagerAdapter.getCount(); i++) {
            actionBar.addTab(actionBar.newTab()
                    .setText(mSectionsPagerAdapter.getPageTitle(i))
                    .setTabListener(this));
        }
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.tab_test, menu);
        return true;
    }

    public void addNewTab() {
        int position = (mSectionsPagerAdapter.getCount() > 0 ? mViewPager.getCurrentItem() : 0);
        mSectionsPagerAdapter.insertFragment(position);
        mViewPager.setCurrentItem(position, true);
    }

    public void removeTab() {
        if (mSectionsPagerAdapter.getCount() > 0) {
            int position = mViewPager.getCurrentItem();
            mSectionsPagerAdapter.removeFragment(position);
        }
    }

    public void replaceTab() {
        if (mSectionsPagerAdapter.getCount() > 0) {
            int position = mViewPager.getCurrentItem();
            mSectionsPagerAdapter.replaceFragment(position);            
            mViewPager.setCurrentItem(position, false);
        }
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
        case R.id.action_add_tab:
            addNewTab();
            return true;
        case R.id.action_remove_tab:
            removeTab();
            return true;
        case R.id.action_replace_tab:
            replaceTab();
            return true;
        }
        return super.onOptionsItemSelected(item);
    }

    @Override
    public void onTabSelected(ActionBar.Tab tab,
            FragmentTransaction fragmentTransaction) {
        mViewPager.setCurrentItem(tab.getPosition());
    }

    @Override
    public void onTabUnselected(ActionBar.Tab tab,
            FragmentTransaction fragmentTransaction) {
    }

    @Override
    public void onTabReselected(ActionBar.Tab tab,
            FragmentTransaction fragmentTransaction) {
    }

    public class SectionsPagerAdapter extends FragmentPagerAdapter {

        private List<Fragment> currentFragments;
        private FragmentManager fragmentManager;

        public SectionsPagerAdapter(FragmentManager fm) {
            super(fm);
            fragmentManager = fm;
            currentFragments = new ArrayList<Fragment>();
        }

        public void insertFragment(int position) {
            // Remove fragments from position
            List<Fragment> fragmentsToRemove = new ArrayList<Fragment>(currentFragments.subList(position, currentFragments.size()));
            int i = currentFragments.size() - 1;
            int j = -1;
            int k = i;
            while (i >= position) {
                currentFragments.remove(i);
                i--;
                j++;
            }
            notifyDataSetChanged();
            final ActionBar actionBar = getActionBar();
            while (k >= position) {
                actionBar.removeTabAt(k);
                k--;
            }
            android.support.v4.app.FragmentTransaction transaction = fragmentManager.beginTransaction();
            while (j >= 0) {
                Fragment fragmentToRemove = fragmentsToRemove.get(j);
                transaction.detach(fragmentToRemove);
                transaction.remove(fragmentToRemove);
                j--;
            }
            transaction.commit();
            fragmentManager.executePendingTransactions();
            notifyDataSetChanged();
            // Add new fragment
            Fragment fragment = new DummySectionFragment();
            currentFragments.add(position, fragment);
            notifyDataSetChanged();
            actionBar.addTab(actionBar.newTab()
                    .setText(mSectionsPagerAdapter.getPageTitle(position))
                    .setTabListener(TabTestActivity.this), position);
            // Readd fragments
            if (fragmentsToRemove.size() > 0) {
                i = 1;
                for (Fragment fragmentToRemove : fragmentsToRemove) {
                    currentFragments.add(DummySectionFragment.cloneExistingFragment((DummySectionFragment)fragmentToRemove));
                    notifyDataSetChanged();
                    actionBar.addTab(actionBar.newTab()
                            .setText(mSectionsPagerAdapter.getPageTitle(position + i))
                            .setTabListener(TabTestActivity.this), position + i);
                    i++;
                }
            }
        }

        public void removeFragment(int position) {
            // Remove fragments from position
            List<Fragment> fragmentsToRemove = new ArrayList<Fragment>(currentFragments.subList(position, currentFragments.size()));
            int i = currentFragments.size() - 1;
            int j = -1;
            int k = i;
            while (i >= position) {
                currentFragments.remove(i);
                i--;
                j++;
            }
            notifyDataSetChanged();
            final ActionBar actionBar = getActionBar();
            while (k >= position) {
                actionBar.removeTabAt(k);
                k--;
            }
            android.support.v4.app.FragmentTransaction transaction = fragmentManager.beginTransaction();
            while (j >= 0) {
                Fragment fragmentToRemove = fragmentsToRemove.get(j);
                transaction.detach(fragmentToRemove);
                transaction.remove(fragmentToRemove);
                j--;
            }
            transaction.commitAllowingStateLoss();
            fragmentManager.executePendingTransactions();
            notifyDataSetChanged();
            // Readd fragments (except one)
            if (fragmentsToRemove.size() > 1) {
                i = 0;
                for (Fragment fragment : fragmentsToRemove.subList(1, fragmentsToRemove.size())) {
                    currentFragments.add(DummySectionFragment.cloneExistingFragment((DummySectionFragment)fragment));
                    notifyDataSetChanged();
                    actionBar.addTab(actionBar.newTab()
                            .setText(mSectionsPagerAdapter.getPageTitle(position + i))
                            .setTabListener(TabTestActivity.this), position + i);
                    i++;
                }
            }
        }

        public void replaceFragment(int position) {
            // Remove fragments from position
            List<Fragment> fragmentsToRemove = new ArrayList<Fragment>(currentFragments.subList(position, currentFragments.size()));
            int i = currentFragments.size() - 1;
            int j = -1;
            int k = i;
            while (i >= position) {
                currentFragments.remove(i);
                i--;
                j++;
            }
            notifyDataSetChanged();
            final ActionBar actionBar = getActionBar();
            while (k >= position) {
                actionBar.removeTabAt(k);
                k--;
            }
            android.support.v4.app.FragmentTransaction transaction = fragmentManager.beginTransaction();
            while (j >= 0) {
                Fragment fragmentToRemove = fragmentsToRemove.get(j);
                transaction.detach(fragmentToRemove);
                transaction.remove(fragmentToRemove);
                j--;
            }
            transaction.commit();
            fragmentManager.executePendingTransactions();
            notifyDataSetChanged();
            // Add new fragment
            Fragment fragment = new DummySectionFragment();
            currentFragments.add(position, fragment);
            notifyDataSetChanged();
            actionBar.addTab(actionBar.newTab()
                    .setText(mSectionsPagerAdapter.getPageTitle(position))
                    .setTabListener(TabTestActivity.this), position);
            // Readd fragments (except one)
            if (fragmentsToRemove.size() > 0) {
                i = 1;
                for (Fragment fragmentToRemove : fragmentsToRemove.subList(1, fragmentsToRemove.size())) {
                    currentFragments.add(DummySectionFragment.cloneExistingFragment((DummySectionFragment)fragmentToRemove));
                    notifyDataSetChanged();
                    actionBar.addTab(actionBar.newTab()
                            .setText(mSectionsPagerAdapter.getPageTitle(position + i))
                            .setTabListener(TabTestActivity.this), position + i);
                    i++;
                }
            }
        }

        @Override
        public Fragment getItem(int position) {
            if (currentFragments == null) {
                currentFragments = new ArrayList<Fragment>();
            }
            while (currentFragments.size() <= position) {
                currentFragments.add(null);
            }
            if (currentFragments.get(position) != null) {
                return currentFragments.get(position);
            }
            Fragment fragment = new DummySectionFragment();
            currentFragments.set(position, fragment);
            return fragment;
        }

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

        @Override
        public int getItemPosition(Object object) {
            int position = currentFragments.indexOf(object);
            if (position == -1) {
                return PagerAdapter.POSITION_NONE;
            }
            return position;
        }

        @Override
        public CharSequence getPageTitle(int position) {
            return ((DummySectionFragment)getItem(position)).getTitle();
        }
    }

    public static class DummySectionFragment extends Fragment {
        private int sectionNumber;

        public DummySectionFragment() {
            super();
            sectionNumber = ++tabCount;
        }

        public static DummySectionFragment cloneExistingFragment(DummySectionFragment fragment) {
            DummySectionFragment cloned = new DummySectionFragment();
            // Hack for avoiding autoincrement
            --tabCount;
            cloned.sectionNumber = fragment.getSectionNumber();
            return cloned;
        }

        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                Bundle savedInstanceState) {
            View rootView = inflater.inflate(R.layout.fragment_tab_test_dummy,
                    container, false);
            TextView dummyTextView = (TextView) rootView
                    .findViewById(R.id.section_label);
            dummyTextView.setText(String.format(labelString, sectionNumber));
            return rootView;
        }

        public int getSectionNumber() {
            return sectionNumber;
        }

        public String getTitle() {
            return String.format(labelString, sectionNumber);
        }
    }

}