FragmentPagerAdapter没有正确删除项目(碎片)
我已经实现了FragmentPagerAdapter
,并使用List<Fragment>
来保存ViewPager
所有片段。 在addItem()
我只需添加一个实例化的Fragment
,然后调用notifyDataSetChanged()
。 我不确定这是否有必要。
我的问题只是…从片段1开始
[Fragment 1]
添加新的片段2
[Fragment 1] [Fragment 2]
删除片段2
[Fragment 1]
添加新的片段3
[Fragment 1] [Fragment 2]
添加新的片段时,一切似乎都很棒。 一旦我删除一个片段,然后添加一个新实例化的片段,旧的片段仍然显示。 当我去.getClass.getName()
它给了我片段3的名字,但我仍然看到片段2。
我相信这可能是与instantiateItem()
或类似的问题,但我认为适配器是为我们处理。 任何build议将是伟大的。
适配器代码…
public class MyPagerAdapter extends FragmentPagerAdapter { public final ArrayList<Fragment> screens2 = new ArrayList<Fragment>(); private Context context; public MyPagerAdapter(FragmentManager fm, Context context) { super(fm); this.context = context; } public void removeType(String name){ for(Fragment f: screens2){ if(f.getClass().getName().contains(name)){ screens2.remove(f); return; } } this.notifyDataSetChanged(); } public boolean addSt(String tag, Class<?> clss, Bundle args){ if(clss==null) return false; if(!clss.getName().contains("St")) return false; if(!args.containsKey("cId")) return false; boolean has = false; boolean hasAlready = false; for(Fragment tab: screens2){ if(tab.getClass().getName().contains("St")){ has = true; if(tab.getArguments().containsKey("cId")) if(tab.getArguments().getLong("cId") == args.getLong("cId")){ hasAlready = true; } if(!hasAlready){ // exists but is different so replace screens2.remove(tab); this.addScreen(tag, clss, args, C.PAGE_ST); // if returned true then it notifies dataset return true; } } hasAlready = false; } if(!has){ // no st yet exist in adapter this.addScreen(tag, clss, args, C.PAGE_ST); return true; } return false; } public boolean removeCCreate(){ this.removeType("Create"); return false; } @Override public int getItemPosition(Object object) { return POSITION_NONE; //To make notifyDataSetChanged() do something } public void addCCreate(){ this.removeCCreate(); Log.w("addding c", " "); this.addScreen("Create C", CreateCFragment.class, null, C.PAGE_CREATE_C); } public void addScreen(String tag, Class<?> clss, Bundle args, int type){ if(clss!=null){ screens2.add(Fragment.instantiate(context, clss.getName(), args)); } } @Override public int getCount() { return screens2.size(); } @Override public Fragment getItem(int position) { return screens2.get(position); } }
我意识到代码使用一些“贫民窟”的手段来确定片段types,但我写这个代码严格的testingfunction。 任何帮助或想法都会很好,因为看起来没有多less人冒险进入FragmentPagerAdapter
的世界。
我得到了同样的问题,我的解决scheme是overring方法“destroyItem”如下。
@Override public void destroyItem(ViewGroup container, int position, Object object) { FragmentManager manager = ((Fragment)object).getFragmentManager(); FragmentTransaction trans = manager.beginTransaction(); trans.remove((Fragment)object); trans.commit(); }
这对我有用,有没有人有另一种解决scheme?
更新:
我发现这些代码使片段被删除不必要,所以我添加了一个条件来避免它。
@Override public void destroyItem(ViewGroup container, int position, Object object) { if (position >= getCount()) { FragmentManager manager = ((Fragment) object).getFragmentManager(); FragmentTransaction trans = manager.beginTransaction(); trans.remove((Fragment) object); trans.commit(); } }
更新这个post,并包括我的解决scheme(如果有人可以改善让我知道)
好吧,我现在用黑客的方式解决了我的问题,但是它的工作;)。 如果有人能改善我的解决scheme,请让我知道。 对于我的新解决scheme,我现在使用一个CustomFragmentStatePagerAdapter,但它并不像它应该保存状态,并将所有碎片存储在列表中。 这可能会导致内存问题,如果用户有超过50个片段,就像普通的FragmentPagerAdapter一样。 如果有人可以将状态信息添加回我的解决scheme而不删除我的修复,那将是非常好的。 谢谢。
所以这里是我的CustomFragmentStatePagerAdapter.java
package com.tundem.webLab.Adapter; import java.util.ArrayList; import android.os.Bundle; import android.os.Parcelable; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentTransaction; import android.support.v4.view.PagerAdapter; import android.util.Log; import android.view.View; import android.view.ViewGroup; public abstract class CustomFragmentStatePagerAdapter extends PagerAdapter { private static final String TAG = "FragmentStatePagerAdapter"; private static final boolean DEBUG = false; private final FragmentManager mFragmentManager; private FragmentTransaction mCurTransaction = null; public ArrayList<Fragment.SavedState> mSavedState = new ArrayList<Fragment.SavedState>(); public ArrayList<Fragment> mFragments = new ArrayList<Fragment>(); private Fragment mCurrentPrimaryItem = null; public CustomFragmentStatePagerAdapter(FragmentManager fm) { mFragmentManager = fm; } /** * Return the Fragment associated with a specified position. */ public abstract Fragment getItem(int position); @Override public void startUpdate(ViewGroup container) {} @Override public Object instantiateItem(ViewGroup container, int position) { // 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. // DONE Remove of the add process of the old stuff /* if (mFragments.size() > position) { Fragment f = mFragments.get(position); if (f != null) { return f; } } */ if (mCurTransaction == null) { mCurTransaction = mFragmentManager.beginTransaction(); } Fragment fragment = getItem(position); if (DEBUG) Log.v(TAG, "Adding item #" + position + ": f=" + fragment); if (mSavedState.size() > position) { Fragment.SavedState fss = mSavedState.get(position); if (fss != null) { try // DONE: Try Catch { fragment.setInitialSavedState(fss); } catch (Exception ex) { // Schon aktiv (kA was das heißt xD) } } } while (mFragments.size() <= position) { mFragments.add(null); } fragment.setMenuVisibility(false); mFragments.set(position, fragment); mCurTransaction.add(container.getId(), fragment); return fragment; } @Override public void destroyItem(ViewGroup container, int position, Object object) { Fragment fragment = (Fragment) object; if (mCurTransaction == null) { mCurTransaction = mFragmentManager.beginTransaction(); } mCurTransaction.remove(fragment); /*if (mCurTransaction == null) { mCurTransaction = mFragmentManager.beginTransaction(); } if (DEBUG) Log.v(TAG, "Removing item #" + position + ": f=" + object + " v=" + ((Fragment) * object).getView()); while (mSavedState.size() <= position) { mSavedState.add(null); } mSavedState.set(position, mFragmentManager.saveFragmentInstanceState(fragment)); * 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); } if (fragment != null) { fragment.setMenuVisibility(true); } mCurrentPrimaryItem = fragment; } } @Override public void finishUpdate(ViewGroup container) { if (mCurTransaction != null) { mCurTransaction.commitAllowingStateLoss(); mCurTransaction = null; mFragmentManager.executePendingTransactions(); } } @Override public boolean isViewFromObject(View view, Object object) { return ((Fragment) object).getView() == view; } @Override public Parcelable saveState() { Bundle state = null; if (mSavedState.size() > 0) { state = new Bundle(); Fragment.SavedState[] fss = new Fragment.SavedState[mSavedState.size()]; mSavedState.toArray(fss); state.putParcelableArray("states", fss); } for (int i = 0; i < mFragments.size(); i++) { Fragment f = mFragments.get(i); if (f != null) { if (state == null) { state = new Bundle(); } String key = "f" + i; mFragmentManager.putFragment(state, key, f); } } return state; } @Override public void restoreState(Parcelable state, ClassLoader loader) { if (state != null) { Bundle bundle = (Bundle) state; bundle.setClassLoader(loader); Parcelable[] fss = bundle.getParcelableArray("states"); mSavedState.clear(); mFragments.clear(); if (fss != null) { for (int i = 0; i < fss.length; i++) { mSavedState.add((Fragment.SavedState) fss[i]); } } 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) { while (mFragments.size() <= index) { mFragments.add(null); } f.setMenuVisibility(false); mFragments.set(index, f); } else { Log.w(TAG, "Bad fragment at key " + key); } } } } } }
这是我正常的FragmentAdapter.java
package com.tundem.webLab.Adapter; import java.util.LinkedList; import java.util.List; import android.support.v4.app.FragmentManager; import com.tundem.webLab.fragments.BaseFragment; import com.viewpagerindicator.TitleProvider; public class FragmentAdapter extends CustomFragmentStatePagerAdapter implements TitleProvider { public List<BaseFragment> fragments = new LinkedList<BaseFragment>(); private int actPage; public FragmentAdapter(FragmentManager fm) { super(fm); } public void setActPage(int actPage) { this.actPage = actPage; } public void addItem(BaseFragment fragment) { // TODO if exists don't open / change to that tab fragments.add(fragment); } public BaseFragment getActFragment() { return getItem(getActPage()); } public int getActPage() { return actPage; } @Override public BaseFragment getItem(int position) { if (position < getCount()) { return fragments.get(position); } else return null; } @Override public int getCount() { return fragments.size(); } @Override public String getTitle(int position) { return fragments.get(position).getTitle(); } @Override public int getItemPosition(Object object) { return POSITION_NONE; } }
这是我删除片段的方式。 (我知道这仅仅是.remove())。 可以自由地改进我的解决scheme,你也可以在适配器的某处添加这个代码,所以是的。 用户试图实现这一点。 我在我的TabHelper.java (处理所有选项卡操作(如删除,添加…)的类中使用此)
int act = Cfg.mPager.getCurrentItem(); Cfg.mPager.removeAllViews(); Cfg.mAdapter.mFragments.remove(act); try { Cfg.mAdapter.mSavedState.remove(act); } catch (Exception ex) {/* Already removed */} try { Cfg.mAdapter.fragments.remove(act); } catch (Exception ex) {/* Already removed */} Cfg.mAdapter.notifyDataSetChanged(); Cfg.mIndicator.notifyDataSetChanged();
Cfg的描述 事情。 我将这些对象的引用保存在一个cfg类中,这样我就可以使用它们而不需要特殊的Factory.java …
是啊。 我希望我能够帮助。 随意改善这一点,但让我知道,所以我也可以提高我的代码。
谢谢。
如果我错过任何代码让我知道。
我的旧的答案也适用,但只有你有不同的片段。 FileFragment,WebFragment,…如果您使用这些片段types两次之一,则不是。
我现在伪装了。 这是一个非常肮脏的解决scheme,我仍然在寻找更好的解决scheme。 请帮忙。
我改变了代码,在这里我删除了一个标签:
public static void deleteActTab() { //We set this on the indicator, NOT the pager int act = Cfg.mPager.getCurrentItem(); Cfg.mAdapter.removeItem(act); List<BaseFragment> frags = new LinkedList<BaseFragment>(); frags = Cfg.mAdapter.fragments; Cfg.mPager = (ViewPager)Cfg.act.findViewById(R.id.pager); Cfg.mPager.setAdapter(Cfg.mAdapter); Cfg.mIndicator.setViewPager(Cfg.mPager); Cfg.mAdapter.fragments = frags; if(act > 0) { Cfg.mPager.setCurrentItem(act-1); Cfg.mIndicator.setCurrentItem(act-1); } Cfg.mIndicator.notifyDataSetChanged(); }
如果有人能改善这个代码让我知道。 如果有人能告诉我们这个问题的真正答案。 请在这里添加。 有很多人遇到这个问题。 我为解决问题的人增加了50的声望。 我也可以为解决问题的人捐款。
谢谢
以“两全其美”(我的意思是@Tericky Shih和@mikepenz的答案),我们简单而简单:
public class MyPagerAdapter extends FragmentPagerAdapter { public ArrayList<Fragment> fragments = new ArrayList<Fragment>(); ... @Override public void destroyItem(ViewGroup container, int position, Object object) { super.destroyItem(container, position, object); if (position >= getCount()) fm.beginTransaction().remove((Fragment) object).commit(); } @Override public int getItemPosition(Object object) { if (fragments.contains(object)) return fragments.indexOf(object); else return POSITION_NONE; } }
主要区别在于,如果某个片段没有被更改,则不必摧毁其视图,也不必为此返回POSITION_NONE
。 同时,当ViewPager持有一个已被销毁的物品的引用时,我遇到了一种情况,因此检查if (fragments.contains(object))
有助于确定是否不再需要该物品。
我有一个类似于你的情况。 我最近需要添加和从ViewPager中删除片段。 在第一种模式下,我有片段0,1和2,在第二种模式下,我有片段0和3.我希望片段0对于两种模式都是相同的,并保留信息。
我所需要做的就是覆盖FragmentPagerAdapter.getItemId,以确保我为每个不同的片段返回一个唯一的编号 – 默认是返回“位置”。 我还必须再次在ViewPager中设置适配器 – 一个新的实例将工作,但我把它设置回同一个实例。 设置适配器将导致ViewPager删除所有视图并尝试再次添加它们。
然而,诀窍是适配器只在实例化Fragment时才调用getItem,而不是每次显示它。 这是因为它们被caching,并通过getItemId返回的“位置”查找它们。
想象一下,你有三个片段(0,1和2),你想删除“1”。 如果您返回getItemId的“position”,那么删除Fragment 1将不起作用,因为当您在删除Fragment 1后尝试显示Fragment 2时,寻呼机/适配器将认为它已经获得了该“position”的Fragment,并将继续显示Fragment 1 。
仅供参考:我尝试notifyDataSetChanged而不是设置适配器,但它不适合我。
首先,getItemId覆盖示例以及我为getItem所做的操作:
public class SectionsPagerAdapter extends FragmentPagerAdapter { ... @Override public long getItemId(int position) { // Mode 1 uses Fragments 0, 1 and 2. Mode 2 uses Fragments 0 and 3 if ( mode == 2 && position == 1 ) return 3; return position; } @Override public Fragment getItem(int position) { if ( mode == 1 ) { switch (position) { case 0: return <<fragment 0>>; case 1: return <<fragment 1>>; case 2: return <<fragment 2>>; } } else // Mode 2 { switch (position) { case 0: return <<fragment 0>>; case 1: return <<fragment 3>>; } } return null; } }
现在模式的改变:
private void modeChanged(int newMode) { if ( newMode == mode ) return; mode = newMode; // Calling mSectionsPagerAdapter.notifyDataSetChanged() is not enough here mViewPager.setAdapter(mSectionsPagerAdapter); }
也许这个答案可以帮助你。
使用FragmentStatePagerAdapter而不是FragmentPagerAdapter 。
因为FragmentPagerAdapter不会破坏视图。 欲了解更多信息阅读这个答案 。
没有为我工作。 我的解决scheme是把FragmentStatePagerAdapter.java放在我的项目中,重命名为FragmentStatePagerAdapter2.java。 在destroyItem()中,我根据错误日志进行了一些修改。 从
// mFragments.set(position, null);
至
if (position < mFragments.size())mFragments.remove(position);
也许你没有同样的问题,只是检查日志。希望这可以帮助别人!
经过很多尝试,我得到它的工作,以便它正确地删除或附加第三个片段在结束的位置。
Object fragments[] = new Object[3]; int mItems = 2; MyAdapter mAdapter; ViewPager mPager; public void addFragment(boolean bool) { mAdapter.startUpdate(mPager); if (!bool) { mAdapter.destroyItem(mPager, 2, fragments[2]); mItems = 2; fNach = false; } else if (bool && !fNach){ mItems = 3; mAdapter.instantiateItem(mPager,2); fNach = true; } mAdapter.finishUpdate(mPager); mAdapter.notifyDataSetChanged(); } public class MyAdapter extends FragmentPagerAdapter { MyAdapter(FragmentManager fm) { super(fm); } @Override public int getCount() { return mItems; } @Override public CharSequence getPageTitle(int position) { ... (code for the PagerTitleStrip) } @Override public Fragment getItem(int position) { Fragment f = null; switch (position) { case 0: f = new Fragment1(); break; case 1: f = new Fragment2(); break; case 2: f = new Fragment3(); break; } return f; } @Override public Object instantiateItem(ViewGroup container, int position) { Object o = super.instantiateItem(container,position); fragments[position] = o; return o; } @Override public void destroyItem(ViewGroup container, int position, Object object) { super.destroyItem(container, position, object); System.out.println("Destroy item " + position); if (position >= getCount()) { FragmentManager manager = ((Fragment) object).getFragmentManager(); FragmentTransaction ft = manager.beginTransaction(); ft.remove((Fragment) object); ft.commit(); } } }
一些澄清:获取对象引用调用destroyItem,我存储从一个数组中的instantiateItem返回的对象。 在添加或删除片段时,必须使用startUpdate,finishUpdate和notifyDataSetChanged进行通告。 项目的数量必须手动改变,为了添加你增加它并实例化,getItem然后创build它。 对于删除操作,您可以调用destroyItem,并且在这段代码中,位置> = mItems是非常重要的,因为如果一个碎片离开了caching,destroyItem也会被调用。 那么你不想删除它。 唯一不起作用的是滑动animation。 删除最后一页后,“无法向左滑动”animation在新的最后一页上不能正确恢复。 如果您滑过,则会显示一个空白页面并弹回。
真正的问题是FragmentPagerAdapter使用列表中片段的位置作为ID。 所以,如果你添加一个新的列表或只是删除项目“instantiateItem”项目将find列表中的新项目不同的片段…
@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); 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)); } if (fragment != mCurrentPrimaryItem) { fragment.setMenuVisibility(false); fragment.setUserVisibleHint(false); } return fragment; }
和
private static String makeFragmentName(int viewId, long id) { return "android:switcher:" + viewId + ":" + id; }
和
* Return a unique identifier for the item at the given position. * <p> * <p>The default implementation returns the given position. * Subclasses should override this method if the positions of items can change.</p> * * @param position Position within this adapter * @return Unique identifier for the item at position */ public long getItemId(int position) { return position; }