在Android中从ViewPager中移除碎片页面

我试图从ViewPagerdynamic添加和删除碎片,添加工作没有任何问题,但删除不能按预期工作。

每次我想删除当前项目,最后一个被删除。

我也尝试在适配器的getItemPosition方法中使用FragmentStatePagerAdapter或返回POSITION_NONE。

我究竟做错了什么?

这是一个基本的例子:

MainActivity.java

public class MainActivity extends FragmentActivity implements TextProvider { private Button mAdd; private Button mRemove; private ViewPager mPager; private MyPagerAdapter mAdapter; private ArrayList<String> mEntries = new ArrayList<String>(); @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); mEntries.add("pos 1"); mEntries.add("pos 2"); mEntries.add("pos 3"); mEntries.add("pos 4"); mEntries.add("pos 5"); mAdd = (Button) findViewById(R.id.add); mRemove = (Button) findViewById(R.id.remove); mPager = (ViewPager) findViewById(R.id.pager); mAdd.setOnClickListener(new OnClickListener() { @Override public void onClick(View view) { addNewItem(); } }); mRemove.setOnClickListener(new OnClickListener() { @Override public void onClick(View view) { removeCurrentItem(); } }); mAdapter = new MyPagerAdapter(this.getSupportFragmentManager(), this); mPager.setAdapter(mAdapter); } private void addNewItem() { mEntries.add("new item"); mAdapter.notifyDataSetChanged(); } private void removeCurrentItem() { int position = mPager.getCurrentItem(); mEntries.remove(position); mAdapter.notifyDataSetChanged(); } @Override public String getTextForPosition(int position) { return mEntries.get(position); } @Override public int getCount() { return mEntries.size(); } private class MyPagerAdapter extends FragmentPagerAdapter { private TextProvider mProvider; public MyPagerAdapter(FragmentManager fm, TextProvider provider) { super(fm); this.mProvider = provider; } @Override public Fragment getItem(int position) { return MyFragment.newInstance(mProvider.getTextForPosition(position)); } @Override public int getCount() { return mProvider.getCount(); } } } 

TextProvider.java

 public interface TextProvider { public String getTextForPosition(int position); public int getCount(); } 

MyFragment.java

 public class MyFragment extends Fragment { private String mText; public static MyFragment newInstance(String text) { MyFragment f = new MyFragment(text); return f; } public MyFragment() { } public MyFragment(String text) { this.mText = text; } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View root = inflater.inflate(R.layout.fragment, container, false); ((TextView) root.findViewById(R.id.position)).setText(mText); return root; } } 

activity_main.xml中

 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <Button android:id="@+id/add" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="add new item" /> <Button android:id="@+id/remove" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="remove current item" /> <android.support.v4.view.ViewPager android:id="@+id/pager" android:layout_width="match_parent" android:layout_height="0dip" android:layout_weight="1" /> </LinearLayout> 

fragment.xml之

 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <TextView android:id="@+id/position" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:textSize="35sp" /> </LinearLayout> 

劳斯的解决scheme不足以让我的工作,因为现有的碎片没有被破坏。 受此回答的启发,我发现解决scheme是重写FragmentPagerAdaptergetItemId(int position)方法,以便在FragmentPagerAdapter的预期位置发生变化时给出一个新的唯一ID。

源代码:

 private class MyPagerAdapter extends FragmentPagerAdapter { private TextProvider mProvider; private long baseId = 0; public MyPagerAdapter(FragmentManager fm, TextProvider provider) { super(fm); this.mProvider = provider; } @Override public Fragment getItem(int position) { return MyFragment.newInstance(mProvider.getTextForPosition(position)); } @Override public int getCount() { return mProvider.getCount(); } //this is called when notifyDataSetChanged() is called @Override public int getItemPosition(Object object) { // refresh all fragments when data set changed return PagerAdapter.POSITION_NONE; } @Override public long getItemId(int position) { // give an ID different from position when position has been changed return baseId + position; } /** * Notify that the position of a fragment has been changed. * Create a new ID for each position to force recreation of the fragment * @param n number of items which have been changed */ public void notifyChangeInPosition(int n) { // shift the ID returned by getItemId outside the range of all previous fragments baseId += getCount() + n; } } 

现在,例如,如果您删除单个选项卡或对订单进行一些更改,则应在调用notifyChangeInPosition(1)之前调用notifyDataSetChanged() ,这将确保重新创build所有片段。

这个解决scheme为什

重写getItemPosition():

notifyDataSetChanged() ,适配器将调用它所连接的ViewPagernotifyChanged()方法。 然后, ViewPager检查适配器getItemPosition()为每个项目返回的值,删除返回POSITION_NONE (请参阅源代码 )并重新填充的项目。

重写getItemId():

ViewPager重新填充时,这是防止适配器重新加载旧片段所ViewPager的。 您可以通过查看FragmentPagerAdapter instantiateItem() 的源代码轻松理解其原因。

  final long itemId = getItemId(position); // Do we already have this fragment? String name = makeFragmentName(container.getId(), itemId); Fragment fragment = mFragmentManager.findFragmentByTag(name); if (fragment != null) { if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment); mCurTransaction.attach(fragment); } else { fragment = getItem(position); if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment); mCurTransaction.add(container.getId(), fragment, makeFragmentName(container.getId(), itemId)); } 

正如你所看到的,只有在片段pipe理器找不到具有相同Id的现有片段时才调用getItem()方法。 对我来说,即使在notifyDataSetChanged()之后,旧的碎片依然附着,但ViewPager的文档明确指出:

注意这个类目前正在进行早期的devise和开发。 该API可能会在稍后的兼容性库更新中发生变化,在针对新版本编译时需要更改应用程序的源代码。

所以希望在这里给出的解决方法在支持库的未来版本中不是必需的。

ViewPager不会用上面的代码去除你的片段,因为它将几个视图(或者你的案例中的片段)加载到内存中。 除了可见视图之外,它还将视图加载到可见视图的任一侧。 这提供了从视图到视图的平滑滚动,使得ViewPager变得非常酷。

为了达到你想要的效果,你需要做一些事情。

  1. 将FragmentPagerAdapter更改为FragmentStatePagerAdapter。 这是因为FragmentPagerAdapter将永久保存所有加载到内存中的视图。 FragmentStatePagerAdapter处理的视图落在当前视图和遍历视图之外。

  2. 覆盖适配器方法getItemPosition(如下所示)。 当我们调用mAdapter.notifyDataSetChanged(); ViewPager询问适配器以确定在定位方面发生了什么变化。 我们使用这种方法来说,一切都改变了,所以重新处理你所有的视图定位。

这是代码

 private class MyPagerAdapter extends FragmentStatePagerAdapter { //... your existing code @Override public int getItemPosition(Object object){ return PagerAdapter.POSITION_NONE; } } 

我的工作解决scheme从视图寻呼机中删除片段页面

 public class MyFragmentAdapter extends FragmentStatePagerAdapter { private ArrayList<ItemFragment> pages; public MyFragmentAdapter(FragmentManager fragmentManager, ArrayList<ItemFragment> pages) { super(fragmentManager); this.pages = pages; } @Override public Fragment getItem(int index) { return pages.get(index); } @Override public int getCount() { return pages.size(); } @Override public int getItemPosition(Object object) { int index = pages.indexOf (object); if (index == -1) return POSITION_NONE; else return index; } } 

而当我需要通过索引删除一些页面我做到这一点

 pages.remove(position); // ArrayList<ItemFragment> adapter.notifyDataSetChanged(); // MyFragmentAdapter 

这里是我的适配器初始化

 MyFragmentAdapter adapter = new MyFragmentAdapter(getSupportFragmentManager(), pages); viewPager.setAdapter(adapter); 

我有一个简单的从android.support.v4.app.FragmentPagerAdpater复制源代码到一个名为CustumFragmentPagerAdapter的自定义类的CustumFragmentPagerAdapter 。 这让我有机会修改instantiateItem(...)以便每次调用它时,都会删除/销毁当前连接的片段,然后添加从getItem()方法接收到的新片段。

只需按以下方式修改instantiateItem(...)

 @Override public Object instantiateItem(ViewGroup container, int position) { if (mCurTransaction == null) { mCurTransaction = mFragmentManager.beginTransaction(); } final long itemId = getItemId(position); // Do we already have this fragment? String name = makeFragmentName(container.getId(), itemId); Fragment fragment = mFragmentManager.findFragmentByTag(name); // remove / destroy current fragment if (fragment != null) { mCurTransaction.remove(fragment); } // get new fragment and add it fragment = getItem(position); mCurTransaction.add(container.getId(), fragment, makeFragmentName(container.getId(), itemId)); if (fragment != mCurrentPrimaryItem) { fragment.setMenuVisibility(false); fragment.setUserVisibleHint(false); } return fragment; } 

你可以把两者结合起来,

 private class MyPagerAdapter extends FragmentStatePagerAdapter { //... your existing code @Override public int getItemPosition(Object object){ if(Any_Reason_You_WantTo_Update_Positions) //this includes deleting or adding pages return PagerAdapter.POSITION_NONE; } else return PagerAdapter.POSITION_UNCHANGED; //this ensures high performance in other operations such as editing list items. } 

我的最终版本代码,修复了所有的错误。 我花了3天

https://github.com/lin1987www/FragmentBuilder/blob/master/app/src/main/java/android/support/v4/app/FragmentStatePagerAdapterFix.java

 public class FragmentStatePagerAdapterFix extends PagerAdapter { private static final String TAG = FragmentStatePagerAdapterFix.class.getSimpleName(); private static final boolean DEBUG = false; private WeakReference<FragmentActivity> wrFragmentActivity; private WeakReference<Fragment> wrParentFragment; private final FragmentManager mFragmentManager; private FragmentTransaction mCurTransaction = null; protected ArrayList<Fragment> mFragments = new ArrayList<>(); protected ArrayList<FragmentState> mFragmentStates = new ArrayList<>(); protected ArrayList<String> mFragmentTags = new ArrayList<>(); protected ArrayList<String> mFragmentClassNames = new ArrayList<>(); protected ArrayList<Bundle> mFragmentArgs = new ArrayList<>(); private Fragment mCurrentPrimaryItem = null; private boolean[] mTempPositionChange; @Override public int getCount() { return mFragmentClassNames.size(); } public FragmentActivity getFragmentActivity() { return wrFragmentActivity.get(); } public Fragment getParentFragment() { return wrParentFragment.get(); } public FragmentStatePagerAdapterFix(FragmentActivity activity) { mFragmentManager = activity.getSupportFragmentManager(); wrFragmentActivity = new WeakReference<>(activity); wrParentFragment = new WeakReference<>(null); } public FragmentStatePagerAdapterFix(Fragment fragment) { mFragmentManager = fragment.getChildFragmentManager(); wrFragmentActivity = new WeakReference<>(fragment.getActivity()); wrParentFragment = new WeakReference<>(fragment); } public void add(Class<? extends android.support.v4.app.Fragment> fragClass) { add(fragClass, null, null); } public void add(Class<? extends android.support.v4.app.Fragment> fragClass, Bundle args) { add(fragClass, args, null); } public void add(Class<? extends android.support.v4.app.Fragment> fragClass, String tag) { add(fragClass, null, tag); } public void add(Class<? extends android.support.v4.app.Fragment> fragClass, Bundle args, String tag) { add(fragClass, args, tag, getCount()); } public void add(Class<? extends android.support.v4.app.Fragment> fragClass, Bundle args, String tag, int position) { mFragments.add(position, null); mFragmentStates.add(position, null); mFragmentTags.add(position, tag); mFragmentClassNames.add(position, fragClass.getName()); mFragmentArgs.add(position, args); mTempPositionChange = new boolean[getCount()]; } public void remove(int position) { if (position < getCount()) { mTempPositionChange = new boolean[getCount()]; for (int i = position; i < mTempPositionChange.length; i++) { mTempPositionChange[i] = true; } mFragments.remove(position); mFragmentStates.remove(position); mFragmentTags.remove(position); mFragmentClassNames.remove(position); mFragmentArgs.remove(position); } } public void clear(){ mFragments.clear(); mFragmentStates.clear(); mFragmentTags.clear(); mFragmentClassNames.clear(); mFragmentArgs.clear(); } @Override public void startUpdate(ViewGroup container) { } @Override public Object instantiateItem(ViewGroup container, int position) { Fragment fragment; // If we already have this item instantiated, there is nothing // to do. This can happen when we are restoring the entire pager // from its saved state, where the fragment manager has already // taken care of restoring the fragments we previously had instantiated. fragment = mFragments.get(position); if (fragment != null) { return fragment; } if (mCurTransaction == null) { mCurTransaction = mFragmentManager.beginTransaction(); } FragmentState fs = mFragmentStates.get(position); if (fs != null) { fragment = fs.instantiate(getFragmentActivity(), getParentFragment()); // Fix bug // http://stackoverflow.com/questions/11381470/classnotfoundexception-when-unmarshalling-android-support-v4-view-viewpagersav if (fragment.mSavedFragmentState != null) { fragment.mSavedFragmentState.setClassLoader(fragment.getClass().getClassLoader()); } } if (fragment == null) { fragment = Fragment.instantiate(getFragmentActivity(), mFragmentClassNames.get(position), mFragmentArgs.get(position)); } if (DEBUG) { Log.v(TAG, "Adding item #" + position + ": f=" + fragment); } fragment.setMenuVisibility(false); fragment.setUserVisibleHint(false); mFragments.set(position, fragment); mFragmentStates.set(position, null); mCurTransaction.add(container.getId(), fragment, mFragmentTags.get(position)); return fragment; } @Override public void destroyItem(ViewGroup container, int position, Object object) { Fragment fragment = (Fragment) object; if (mCurTransaction == null) { mCurTransaction = mFragmentManager.beginTransaction(); } if (DEBUG) { Log.v(TAG, "Removing item #" + position + ": f=" + object + " v=" + ((Fragment) object).getView()); } if (position < getCount()) { FragmentState fragmentState = new FragmentState(fragment); Fragment.SavedState savedState = mFragmentManager.saveFragmentInstanceState(fragment); if (savedState != null) { fragmentState.mSavedFragmentState = savedState.mState; } mFragmentStates.set(position, fragmentState); mFragments.set(position, null); } mCurTransaction.remove(fragment); } @Override public void setPrimaryItem(ViewGroup container, int position, Object object) { Fragment fragment = (Fragment) object; if (fragment != mCurrentPrimaryItem) { if (mCurrentPrimaryItem != null) { mCurrentPrimaryItem.setMenuVisibility(false); mCurrentPrimaryItem.setUserVisibleHint(false); } if (fragment != null) { fragment.setMenuVisibility(true); fragment.setUserVisibleHint(true); } mCurrentPrimaryItem = fragment; } } @Override public void finishUpdate(ViewGroup container) { if (mCurTransaction != null) { mCurTransaction.commitAllowingStateLoss(); mCurTransaction = null; mFragmentManager.executePendingTransactions(); // Fix: Fragment is added by transaction. BUT didn't add to FragmentManager's mActive. for (Fragment fragment : mFragments) { if (fragment != null) { fixActiveFragment(mFragmentManager, fragment); } } } } @Override public boolean isViewFromObject(View view, Object object) { return ((Fragment) object).getView() == view; } @Override public Parcelable saveState() { Bundle state = null; // 目前顯示的 Fragments for (int i = 0; i < mFragments.size(); i++) { Fragment f = mFragments.get(i); if (f != null && f.isAdded()) { if (state == null) { state = new Bundle(); } String key = "f" + i; mFragmentManager.putFragment(state, key, f); } } if (mFragmentStates.size() > 0) { if (state == null) { state = new Bundle(); } FragmentState[] fs = new FragmentState[mFragmentStates.size()]; mFragmentStates.toArray(fs); state.putParcelableArray("states_fragment", fs); } return state; } @Override public void restoreState(Parcelable state, ClassLoader loader) { if (state != null) { Bundle bundle = (Bundle) state; bundle.setClassLoader(loader); Parcelable[] fs = bundle.getParcelableArray("states_fragment"); mFragments.clear(); mFragmentStates.clear(); mFragmentTags.clear(); mFragmentClassNames.clear(); mFragmentArgs.clear(); if (fs != null) { for (int i = 0; i < fs.length; i++) { FragmentState fragmentState = (FragmentState) fs[i]; mFragmentStates.add(fragmentState); if (fragmentState != null) { mFragmentArgs.add(fragmentState.mArguments); mFragmentTags.add(fragmentState.mTag); mFragmentClassNames.add(fragmentState.mClassName); } else { mFragmentArgs.add(null); mFragmentTags.add(null); mFragmentClassNames.add(null); } mFragments.add(null); } } Iterable<String> keys = bundle.keySet(); for (String key : keys) { if (key.startsWith("f")) { int index = Integer.parseInt(key.substring(1)); Fragment f = mFragmentManager.getFragment(bundle, key); if (f != null) { f.setMenuVisibility(false); mFragments.set(index, f); mFragmentArgs.set(index, f.mArguments); mFragmentTags.set(index, f.mTag); mFragmentClassNames.set(index, f.getClass().getName()); } else { Log.w(TAG, "Bad fragment at key " + key); } } } // If restore will change notifyDataSetChanged(); } } public static void fixActiveFragment(FragmentManager fragmentManager, Fragment fragment) { FragmentManagerImpl fm = (FragmentManagerImpl) fragmentManager; if (fm.mActive != null) { int index = fragment.mIndex; Fragment origin = fm.mActive.get(index); if (origin != null) { if ((origin.mIndex != fragment.mIndex) || !(origin.equals(fragment))) { Log.e(TAG, String.format("fixActiveFragment: Not Equal! Origin: %s %s, Fragment: %s $s", origin.getClass().getName(), origin.mIndex, fragment.getClass().getName(), fragment.mIndex )); } } fm.mActive.set(index, fragment); } } // Fix // http://stackoverflow.com/questions/10396321/remove-fragment-page-from-viewpager-in-android @Override public int getItemPosition(Object object) { int index = mFragments.indexOf(object); if (index < 0) { return PagerAdapter.POSITION_NONE; } boolean isPositionChange = mTempPositionChange[index]; int result = PagerAdapter.POSITION_UNCHANGED; if (isPositionChange) { result = PagerAdapter.POSITION_NONE; } return result; } } 

劳斯的答案很好。 但我不认为总是返回POSITION_NONE是一个好主意。 因为POSITION_NONE意味着片段应该被销毁,并且会创build一个新的片段。 你可以在ViewPager的源代码中检查dataSetChanged函数。

  if (newPos == PagerAdapter.POSITION_NONE) { mItems.remove(i); i--; ... not related code mAdapter.destroyItem(this, ii.position, ii.object); 

所以我想你最好使用weakReference的数组列表来保存你创build的所有片段。 而当你添加或删除一些页面,你可以从你自己的数组列表中获得正确的位置。

  public int getItemPosition(Object object) { for (int i = 0; i < mFragmentsReferences.size(); i ++) { WeakReference<Fragment> reference = mFragmentsReferences.get(i); if (reference != null && reference.get() != null) { Fragment fragment = reference.get(); if (fragment == object) { return i; } } } return POSITION_NONE; } 

根据注释,当主视图试图确定一个项目的位置是否已经改变时,调用getItemPosition。 而回报价值意味着新的位置。

但是这还不够。 我们仍然有一个重要的步骤。 在FragmentStatePagerAdapter的源代码中,有一个名为“mFragments”的数组caching未被销毁的碎片。 并在instantiateItem函数中。

 if (mFragments.size() > position) { Fragment f = mFragments.get(position); if (f != null) { return f; } } 

它发现caching片段不为空时直接返回caching片段。 所以有一个问题。 从例子中,我们删除位置2的一个页面,首先,我们从我们自己的引用数组列表中删除这个片段。 所以在getItemPosition它将返回POSITION_NONE该片段,然后该片段将被销毁,并从“mFragments”中删除。

  mFragments.set(position, null); 

现在位置3的片段将位于位置2.将调用具有参数位置3的实例化的项目。 此时,“mFramgents”中的第三项不为空,因此将直接返回。 但实际上它返回的是位置2处的片段。所以当我们转到第3页时,我们会在那里find一个空白页面。

要解决这个问题。 我的build议是,您可以将FragmentStatePagerAdapter的源代码复制到您自己的项目中,当您添加或删除操作时,应该添加和删除“mFragments”数组列表中的元素。

事情会更简单,如果你只是使用PagerAdapter而不是FragmentStatePagerAdapter。 祝你好运。

dynamic添加或删除ViewPager中的片段。
在活动开始时调用setupViewPager(viewPager)。 加载不同的片段调用setupViewPagerCustom(viewPager)。 例如在button点击调用:setupViewPagerCustom(viewPager);

  private void setupViewPager(ViewPager viewPager) { ViewPagerAdapter adapter = new ViewPagerAdapter(getSupportFragmentManager()); adapter.addFrag(new fragmnet1(), "HOME"); adapter.addFrag(new fragmnet2(), "SERVICES"); viewPager.setAdapter(adapter); } private void setupViewPagerCustom(ViewPager viewPager) { ViewPagerAdapter adapter = new ViewPagerAdapter(getSupportFragmentManager()); adapter.addFrag(new fragmnet3(), "Contact us"); adapter.addFrag(new fragmnet4(), "ABOUT US"); viewPager.setAdapter(adapter); } //Viewpageradapter, handles the views static class ViewPagerAdapter extends FragmentStatePagerAdapter { private final List<Fragment> mFragmentList = new ArrayList<>(); private final List<String> mFragmentTitleList = new ArrayList<>(); public ViewPagerAdapter(FragmentManager manager){ super(manager); } @Override public Fragment getItem(int position) { return mFragmentList.get(position); } @Override public int getCount() { return mFragmentList.size(); } @Override public int getItemPosition(Object object){ return PagerAdapter.POSITION_NONE; } public void addFrag(Fragment fragment, String title){ mFragmentList.add(fragment); mFragmentTitleList.add(title); } @Override public CharSequence getPageTitle(int position){ return mFragmentTitleList.get(position); } } 

我有一些FragmentStatePagerAdapter问题。 删除项目后:

  • 还有一个用于某个职位的项目(一个不属于该职位但是紧挨着一个职位的项目)
  • 或者某个片段没有被加载(那个页面上只有空白的背景)

经过大量的实验,我想出了以下解决scheme。

 public class SomeAdapter extends FragmentStatePagerAdapter { private List<Item> items = new ArrayList<Item>(); private boolean removing; @Override public Fragment getItem(int position) { ItemFragment fragment = new ItemFragment(); Bundle args = new Bundle(); // use items.get(position) to configure fragment fragment.setArguments(args); return fragment; } @Override public int getCount() { return items.size(); } @Override public int getItemPosition(Object object) { if (removing) { return PagerAdapter.POSITION_NONE; } Item item = getItemOfFragment(object); int index = items.indexOf(item); if (index == -1) { return POSITION_NONE; } else { return index; } } public void addItem(Item item) { items.add(item); notifyDataSetChanged(); } public void removeItem(int position) { items.remove(position); removing = true; notifyDataSetChanged(); removing = false; } } 

此解决scheme仅在删除项目的情况下使用黑客。 否则(例如,添加项目时)保留原始代码的清洁度和性能。

当然,从适配器的外部,你只需要调用addItem / removeItem,不需要调用notifyDataSetChanged()。

该片段必须已被删除,但问题是viewpager保存状态

尝试

 myViewPager.setSaveFromParentEnabled(false); 

没有任何工作,但这解决了问题!

干杯!

我希望这可以帮助你想要的。

 private class MyPagerAdapter extends FragmentStatePagerAdapter { //... your existing code @Override public int getItemPosition(Object object){ return PagerAdapter.POSITION_UNCHANGED; } }