在ViewPager中保存Fragment状态

49
我尝试了从API获取的示例代码(点击此处查看),但它并没有真正地起作用,所以我实现了自己的代码:

FragmentPagerSupport

public class FragmentPagerSupport extends FragmentActivity {

static final int NUM_ITEMS = 10;
MyAdapter mAdapter;
ViewPager mPager;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    mAdapter = new MyAdapter(getSupportFragmentManager());
    Log.i("Pager", "mAdapter = " + mAdapter.toString());

    mPager = (ViewPager)findViewById(R.id.pager);
    if (mPager == null)
        Log.i("Pager", "mPager = null");
    else 
        Log.i("Pager", "mPager = " + mPager.toString());

    Log.i("Pager", "Setting Pager Adapter");
    mPager.setAdapter(mAdapter);
}

public static class MyAdapter extends FragmentPagerAdapter {
    public MyAdapter(FragmentManager fm) {
        super(fm);
        Log.i("Pager", "MyAdapter constructor");
    }

    @Override
    public int getCount() {
        Log.i("Pager", "MyAdapter.getCount()");
        return NUM_ITEMS;            
    }

    @Override
    public Fragment getItem(int position) {
        Log.i("Pager", "MyAdapter.getItem()");

        return TestFragment.newInstance(position);
    }
}

public static class TestFragment extends Fragment {

    public static TestFragment newInstance(int position) {
        Log.i("Pager", "TestFragment.newInstance()");

        TestFragment fragment = new TestFragment();

        Bundle args = new Bundle();
        args.putInt("position", position);
        fragment.setArguments(args);

        return fragment;
    }
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        // TODO Auto-generated method stub
        Log.i("Pager", "TestFragment.onCreateView()");

        LinearLayout layout = (LinearLayout)inflater.inflate(R.layout.fragment_item, null);
        int position = getArguments().getInt("position");

        TextView tv = (TextView)layout.findViewById(R.id.text);
        tv.setText("Fragment # " + position);
        tv.setTextColor(Color.WHITE);
        tv.setTextSize(30);

        return layout;
    }

}

}

main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
    <android.support.v4.view.ViewPager
    android:id="@+id/pager"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    />
</LinearLayout>

fragment_item.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
    <TextView  
    android:id="@+id/text"
    android:layout_width="match_parent" 
    android:layout_height="match_parent"
    android:gravity="center" 
    android:text="@string/hello"
    />
</LinearLayout>
我的问题是,如何在用户左右滑动时不每次创建一个新的Fragment实例,而是如何保存片段的状态(到某个数据结构)然后恢复它? 从API演示中似乎没有任何状态信息保存代码。

请查看我的回答:https://dev59.com/mFnUa4cB1Zd3GeqPeNXf#7030271 - rui.araujo
1
你需要将Fragment放置在数据结构中,并编写getItem方法以便访问正确的片段。例如,如果你的片段在一个ArrayList<YourFragment> fragList中,你只需要编写getItem方法返回fragList.get(position)即可。这样做简单而且不需要通过破坏destroyItem来打破ViewPager - dcow
6个回答

127

7
非动态视图翻页的简单有效解决方案,因为您将保留片段的状态而不需要进行其他操作。如果您需要不太多的内存资源,则建议使用此方法。 - Pelanes
@Pelanes,你说的“非动态视图翻页器”是什么意思?谢谢! - marson
ViewPagers具有固定数量的页面,用户(或应用程序自动)可以添加/删除页面。 - Pelanes
10
当然,这不是一个解决方案,因为它不能有效地保存状态。它只是防止Pager销毁未使用的片段,实际上将问题推迟到以后的某个时候,比如旋转设备或暂停/恢复应用程序时。 - Stephan Henningsen
4
这并没有回答问题,你只是提供了一个变通方法。 - Michael Fulton
显示剩余3条评论

24

只需在FragmentpagerAdapter中覆盖此方法即可。

@Override
        public void destroyItem(ViewGroup container, int position, Object object) {
            // TODO Auto-generated method stub
            super.destroyItem(ViewGroup container, int position, Object object);
        }

从你的代码中删除super.destroyItem(ViewGroup container, int position, Object object);


2
谢谢 @Jinu ,它工作了。 - Harshad07
3
你救了我的命 :) - Ko Vartthan
3
不错,我给你点赞。 - Andrei T
哇,这太棒了! - Dyno Cris

15

更新: 在MyAdapter的getItem方法中,您返回一个新的Fragment实例。Android不会在每次滑动时调用getItem。它只会在需要获取新实例时调用getItem,但只要可用,它将重用现有实例。

关于演示中的状态部分,我无法为您提供帮助。我认为在Fragments/Activities中恢复状态的常规技术也适用于在ViewPager中加载它(但我可能是错误的)。


1
这是正确的答案。不需要覆盖destroyItem - dcow

14

如果你重写

public void destroyItem(View container, int position, Object object)

不调用super方法,该片段将不会被销毁并且会被重用。


1
只是一条快速注释:此方法应该添加到适配器中(即扩展FragmentPagerAdapter的类)。 - Igor Popov
16
这不是解决问题的正确方式,设计非常糟糕。请不要这样做,花几分钟时间了解问题的真正原因和@jtietema回答中提供的非常简单的解决方案。 - dcow
1
就像 @DavidCowden 说的那样。这是一个非常糟糕的黑客行为。 - Adam
该方法已被弃用。您必须使用:@Override public void destroyItem(ViewGroup container, int position, Object object) { } - hector6872
再说一遍:不,你不能使用destroyItem()。 - Stephan Henningsen
完美的答案。在我的情况下,实际上没有必要杀死片段。 - TaRan LaYal

4
适配器应该从FragmentStatePagerAdapter继承,而不是从FragmentPageAdapter继承,并且适配器将保留从位置到片段项的引用。在活动或片段父级上,设置OnPageChangeListener监听器以检测片段项的位置已被激活并更新相关数据状态。
关于片段项的数据状态,我认为应该从Activity或Fragement父级保存/恢复状态。
适配器可以添加以下代码:
public static class MyAdapter extends FragmentStatePagerAdapter {
    private SparseArray<TestFragment> mPageReferenceMap 
                              = new SparseArray<TestFragment>();
    ...

    @Override 
    public Object instantiateItem(ViewGroup viewGroup, int position) {
        Object obj = super.instantiateItem(viewGroup, position);

        //Add the reference when fragment has been create or restore
        if (obj instanceof TestFragment) {
                TestFragment f= (TestFragment)obj;
                mPageReferenceMap.put(position, f);
        }

        return obj;
    }

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        //Remove the reference when destroy it
        mPageReferenceMap.remove(position);

        super.destroyItem(container, position, object);
    }

    public TestFragment getFragment(int key) {

        return mPageReferenceMap.get(key);
    }
    ...
}

希望这有所帮助。

0

我在类似的问题上遇到了一些困难......

我希望将我的片段保存在适配器中,以避免不必要的重新加载,因为我只有在FragmentPagerAdapter中有5个片段。

基本上我不得不创建一个数组并重写getItem(position)方法:

private StoryListFragment[] fragmentsArray
        = new StoryListFragment[getCount()];

public SectionsAdapter(FragmentManager fm) {
    super(fm);
}


@Override
public Fragment getItem(int position) {

    if(fragmentsArray[position]!=null)
        return fragmentsArray[position];
    StoryListFragment storyListFragment = null;
    switch(position){
        case(0):
            storyListFragment = StoryListFragment.newInstance(StoriesBank.NEWS);
            break;
        case(1):
            storyListFragment = StoryListFragment.newInstance(StoriesBank.FEATURES);
            break;
        case(2):
             storyListFragment=  StoryListFragment.newInstance(StoriesBank.ARTS);
            break;
    }

    fragmentsArray[position] =  storyListFragment;
    return storyListFragment;
}

@Override
public Object instantiateItem(ViewGroup container, int position) {
    StoryListFragment fragment = (StoryListFragment) super.instantiateItem(container, position);
    fragmentsArray[position] = fragment;
    return fragment;
}

但这意味着你要保存所有的片段,而且我也不确定在恢复活动和片段时这样做是否有效(这种情况发生在应用程序在后台运行且操作系统决定需要一些内存时,因此关闭一些活动)。 - android developer
1
是的。正如我所说,我使用了这个系统,因为我只有5个轻量级片段,并且需要随时跟踪它们。此外,如果开发人员担心片段被删除,那么他/她可以在活动的onCreate()中进行条件语句,以重新实例化当前访问的片段(或所有片段,具体情况而定)。同样,这对于重量级片段或包含多个片段的活动来说并不理想。 - Kamal
这也是我所做的,但我希望它不会导致问题,因为我陷入了一个更加复杂的情况:http://stackoverflow.com/q/27648565/878126 - android developer

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接