关于android:FragmentPagerAdapter无法正确删除项目(片段)

FragmentPagerAdapter is not removing items (Fragments) correctly

我已经实现了FragmentPagerAdapter并使用List来保存所有片段,以供ViewPager显示。在addItem()上,我只需添加实例化的Fragment,然后调用notifyDataSetChanged()。我不确定这是否必要。

我的问题就是...
从片段1开始

1
[Fragment 1]

添加新的片段2

1
[Fragment 1] [Fragment 2]

删除片段2

1
[Fragment 1]

添加新的片段3

1
[Fragment 1] [Fragment 2]

添加新片段时,一切似乎都很棒。一旦删除片段,然后添加新实例化的片段,仍会显示旧片段。当我使用.getClass.getName()时,它给我片段3的名称,但是我仍然看到片段2。

我相信这可能是instantiateItem()之类的问题,但我认为适配器将为我们解决该问题。任何建议都很好。

适配器代码...

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
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);
}

}

我意识到该代码使用了一些"贫民区"来确定片段类型,但是我严格按照测试功能编写了此代码。任何帮助或想法都将是非常有用的,因为似乎没有多少人敢涉足FragmentPagerAdapter的世界。


我遇到了同样的问题,我的解决方案是按如下方法覆盖方法" destroyItem"。

1
2
3
4
5
6
7
@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();
}

对我来说这是工作,有人有其他解决方案吗?

更新:

我发现不需要删除Fragment的那些代码,因此添加了一个条件来避免它。

1
2
3
4
5
6
7
8
9
@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();
    }
}


更新了这篇文章,并包含了我的解决方案(如果有人可以改进,请告诉我)

好的,我现在已经以一种骇人听闻的方式解决了我的问题,但是是的,它正在起作用;)。如果有人可以改善我的解决方案,请告诉我。对于我的新解决方案,我现在使用CustomFragmentStatePagerAdapter,但它不会像应有的那样保存状态并将所有Fragments存储在列表中。如果用户有50个以上的片段,这可能会导致内存问题,就像普通的FragmentPagerAdapter一样。如果有人可以将State-thing添加回我的解决方案而无需删除我的修复程序,那就太好了。谢谢。

所以这是我的CustomFragmentStatePagerAdapter.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
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())。可以自由地改进我的解决方案,也可以在适配器的某个位置添加此代码,是的。由尝试实施此操作的用户决定。我在TabHelper.java(一个处理所有选项卡操作(如删除,添加,...)的类)中使用了此方法。

1
2
3
4
5
6
7
8
9
10
11
12
    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,...如果两次使用这些fragmenttype之一,则不会。

我现在就伪工作了。这是一个非常肮脏的解决方案,我仍在寻找更好的解决方案。请帮忙。

我更改了代码,在其中删除了一个标签页:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
   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分。我也可以为解决该问题的人捐款。

谢谢


也许这个答案可以帮助您。

使用FragmentStatePagerAdapter而不是FragmentPagerAdapter。

因为FragmentPagerAdapter不会破坏视图。有关更多信息,请阅读此答案。


采取"两全其美"(我的意思是@Tericky Shih和@mikepenz的回答),我们可以简单而轻松地做到这一点:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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以确保我为每个不同的Fragment返回唯一的数字-默认为返回" position"。我还必须再次在ViewPager中设置适配器-一个新实例可以工作,但是我将其设置回相同的实例。设置适配器会导致ViewPager删除所有视图并尝试再次添加它们。

但是,诀窍在于,适配器仅在要实例化Fragment时才调用getItem,而不是每次显示时都调用。这是因为它们被缓存并通过getItemId返回的"位置"查找它们。

假设您有三个片段(0、1和2),并且要删除" 1"。
如果您为getItemId返回" position",则删除片段1将不起作用,因为当您尝试在删除Fragment 1之后显示片段2时,寻呼机/适配器会认为它已经获得了该" position"的片段,并将继续显示片段1 。

仅供参考:我尝试使用notifyDataSetChanged而不是设置适配器,但是它对我不起作用。

首先,getItemId覆盖示例以及我对getItem所做的操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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;
    }
}

现在更改模式:

1
2
3
4
5
6
7
8
9
10
private void modeChanged(int newMode)
{
    if ( newMode == mode )
        return;

    mode = newMode;

    // Calling mSectionsPagerAdapter.notifyDataSetChanged() is not enough here
    mViewPager.setAdapter(mSectionsPagerAdapter);
}

我一直遇到同样的问题,直到我意识到,我是在另一个Fragment而不是main活动中创建PagerView的。

我的解决方案是将ChildFragment Manager传递给Fragment(State)PagerAdapter的构造函数,而不是父Fragment的Fragment Manager。

使用ChildFragmentManager销毁父Fragment时,将自动清除ViewPagerAdapter创建的所有Fragment。


真正的问题是FragmentPagerAdapter使用片段在列表中的位置作为ID。因此,如果您添加新列表或仅删除项目," instantiateItem"项目将在列表中为新项目找到不同的片段...

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@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;
}

1
2
3
  private static String makeFragmentName(int viewId, long id) {
    return"android:switcher:" + viewId +":" + id;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
     * 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;
}

经过大量的尝试,我使它能够正常工作,以便它可以在末端位置正确地去除或附着第三个碎片。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
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返回的对象存储在Array中。添加或删除片段时,必须使用startUpdate,finishUpdate和notifyDataSetChanged进行公告。必须手动更改项目数,要添加并增加它并实例化它,getItem就会创建它。若要删除,请调用destroyItem,在此代码中,位置> = mItems是必不可少的,因为如果片段超出缓存,则也会调用destroyItem。然后,您不想删除它。唯一无效的是滑动动画。删除最后一页后,"不能向左滑动"动画无法在新的最后一页上正确还原。如果您滑动它,将显示一个空白页,然后它会反弹回来。


没有为我工作。我的解决方案是将FragmentStatePagerAdapter.java放在我的项目中,重命名为FragmentStatePagerAdapter2.java。
在destroyItem()中,我根据错误日志进行了一些更改。从

1
// mFragments.set(position, null);

1
if (position < mFragments.size())mFragments.remove(position);

也许您没有相同的问题,只需查看日志即可。希望对您有所帮助!