ViewPager和fragments——存储fragment状态的正确方法是什么?

511

Fragment似乎非常适合将UI逻辑分离成一些模块。但是与 ViewPager 一起使用它的生命周期仍然对我来说很模糊。所以需要大师们的建议!

编辑

见下面的愚蠢解决方案;-)

范围

Main activity有一个具有片段的 ViewPager 。这些片段可以针对其他(子主)活动实现略微不同的逻辑,因此通过回调接口在活动内部填充片段数据。第一次启动时一切正常,但是!...

问题

当活动重新创建时(例如在方向更改时), ViewPager 的片段也会重新创建。代码(您将在下面找到)表示每次创建活动时,我都会尝试创建与片段相同的新的 ViewPager 片段适配器(也许这是问题),但FragmentManager已将所有这些片段存储在某个地方(在哪里?)并开始为这些片段启动重建机制。因此,重建机制使用我的回调接口调用 “旧” 片段的 onAttach,onCreateView等方法来通过Activity的实现方法启动数据。但是该方法指向了通过Activity的onCreate方法创建的新片段。

问题

也许我使用了错误的模式,但是即使是Android 3 Pro书中也没有太多关于它的内容。因此,请给我一个或两个建议,并指出如何正确地执行它。非常感谢!

代码

Main Activity

public class DashboardActivity extends BasePagerActivity implements OnMessageListActionListener {

private MessagesFragment mMessagesFragment;

@Override
protected void onCreate(Bundle savedInstanceState) {
    Logger.d("Dash onCreate");
    super.onCreate(savedInstanceState);

    setContentView(R.layout.viewpager_container);
    new DefaultToolbar(this);

    // create fragments to use
    mMessagesFragment = new MessagesFragment();
    mStreamsFragment = new StreamsFragment();

    // set titles and fragments for view pager
    Map<String, Fragment> screens = new LinkedHashMap<String, Fragment>();
    screens.put(getApplicationContext().getString(R.string.dashboard_title_dumb), new DumbFragment());
    screens.put(getApplicationContext().getString(R.string.dashboard_title_messages), mMessagesFragment);

    // instantiate view pager via adapter
    mPager = (ViewPager) findViewById(R.id.viewpager_pager);
    mPagerAdapter = new BasePagerAdapter(screens, getSupportFragmentManager());
    mPager.setAdapter(mPagerAdapter);

    // set title indicator
    TitlePageIndicator indicator = (TitlePageIndicator) findViewById(R.id.viewpager_titles);
    indicator.setViewPager(mPager, 1);

}

/* set of fragments callback interface implementations */

@Override
public void onMessageInitialisation() {

    Logger.d("Dash onMessageInitialisation");
    if (mMessagesFragment != null)
        mMessagesFragment.loadLastMessages();
}

@Override
public void onMessageSelected(Message selectedMessage) {

    Intent intent = new Intent(this, StreamActivity.class);
    intent.putExtra(Message.class.getName(), selectedMessage);
    startActivity(intent);
}

BasePagerActivity也被称为helper

public class BasePagerActivity extends FragmentActivity {

BasePagerAdapter mPagerAdapter;
ViewPager mPager;
}

适配器

public class BasePagerAdapter extends FragmentPagerAdapter implements TitleProvider {

private Map<String, Fragment> mScreens;

public BasePagerAdapter(Map<String, Fragment> screenMap, FragmentManager fm) {

    super(fm);
    this.mScreens = screenMap;
}

@Override
public Fragment getItem(int position) {

    return mScreens.values().toArray(new Fragment[mScreens.size()])[position];
}

@Override
public int getCount() {

    return mScreens.size();
}

@Override
public String getTitle(int position) {

    return mScreens.keySet().toArray(new String[mScreens.size()])[position];
}

// hack. we don't want to destroy our fragments and re-initiate them after
@Override
public void destroyItem(View container, int position, Object object) {

    // TODO Auto-generated method stub
}

}

碎片

public class MessagesFragment extends ListFragment {

private boolean mIsLastMessages;

private List<Message> mMessagesList;
private MessageArrayAdapter mAdapter;

private LoadMessagesTask mLoadMessagesTask;
private OnMessageListActionListener mListener;

// define callback interface
public interface OnMessageListActionListener {
    public void onMessageInitialisation();
    public void onMessageSelected(Message selectedMessage);
}

@Override
public void onAttach(Activity activity) {
    super.onAttach(activity);
    // setting callback
    mListener = (OnMessageListActionListener) activity;
    mIsLastMessages = activity instanceof DashboardActivity;

}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    inflater.inflate(R.layout.fragment_listview, container);
    mProgressView = inflater.inflate(R.layout.listrow_progress, null);
    mEmptyView = inflater.inflate(R.layout.fragment_nodata, null);
    return super.onCreateView(inflater, container, savedInstanceState);
}

@Override
public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);

    // instantiate loading task
    mLoadMessagesTask = new LoadMessagesTask();

    // instantiate list of messages
    mMessagesList = new ArrayList<Message>();
    mAdapter = new MessageArrayAdapter(getActivity(), mMessagesList);
    setListAdapter(mAdapter);
}

@Override
public void onResume() {
    mListener.onMessageInitialisation();
    super.onResume();
}

public void onListItemClick(ListView l, View v, int position, long id) {
    Message selectedMessage = (Message) getListAdapter().getItem(position);
    mListener.onMessageSelected(selectedMessage);
    super.onListItemClick(l, v, position, id);
}

/* public methods to load messages from host acitivity, etc... */
}

解决方案

一个简单的解决方案是在宿主 Activity 的 onSaveInstanceState 方法中使用 putFragment 将碎片保存起来,在 onCreate 方法中使用 getFragment 获取它们。但我仍然有一种奇怪的感觉,似乎事情不应该这样做... 请参考下面的代码:

    @Override
protected void onSaveInstanceState(Bundle outState) {

    super.onSaveInstanceState(outState);
    getSupportFragmentManager()
            .putFragment(outState, MessagesFragment.class.getName(), mMessagesFragment);
}

protected void onCreate(Bundle savedInstanceState) {
    Logger.d("Dash onCreate");
    super.onCreate(savedInstanceState);

    ...
    // create fragments to use
    if (savedInstanceState != null) {
        mMessagesFragment = (MessagesFragment) getSupportFragmentManager().getFragment(
                savedInstanceState, MessagesFragment.class.getName());
                StreamsFragment.class.getName());
    }
    if (mMessagesFragment == null)
        mMessagesFragment = new MessagesFragment();
    ...
}

1
我现在在想:是应该采用非常不同的方法,还是尝试通过onSavedInstancestate保存主活动(Dashboard)的片段以在onCreate()中使用它们。有没有适当的方法来保存这些片段并从bundle中获取它们在onCreate()中使用?它们似乎不可被序列化... - Oleksii Malovanyi
2
第二种方法可行——请看“解决方案”。但是它似乎是一段丑陋的代码,不是吗? - Oleksii Malovanyi
1
为了清理Android标签(详见此处:http://meta.stackexchange.com/questions/100529/help-us-clean-up-the-android-tag),您是否介意将您的解决方案发布为答案并将其标记为所选项?这样它就不会显示为未回答的问题 :) - Alexander Lucas
1
是的,我认为还可以。希望能有比我更好的东西... - Oleksii Malovanyi
1
这个愚蠢的解决方案能行吗?它给了我一个空指针异常。 - Zhen Liu
愚蠢的解决方案(使用saveOnInstance的那个)真的很有效,而且我不得不说,我花了数小时来调试我在某些非常特定的用例中遇到的片段问题。结果证明它归结于这一个问题,并使用这个解决方案解决了它。挽救了我的一天。 - miroslavign
11个回答

457
FragmentPagerAdapter向FragmentManager添加一个fragment时,它会使用一个特殊的标签,该标签基于fragment将被放置的特定位置。只有当该位置的fragment不存在时,才会调用FragmentPagerAdapter.getItem(int position)。在旋转后,Android会注意到它已经为该特定位置创建/保存了一个fragment,因此它会尝试使用FragmentManager.findFragmentByTag()重新连接它,而不是创建一个新的fragment。当使用FragmentPagerAdapter时,这一切都是免费的,这也是通常将你的fragment初始化代码放在getItem(int)方法中的原因。
即使我们没有使用FragmentPagerAdapter,在Activity.onCreate(Bundle)中每次创建一个新的fragment也不是一个好主意。正如您所注意到的那样,当一个fragment被添加到FragmentManager中后,在旋转后它将被自动重建,因此没有必要再次添加它。这样做是处理fragments时常见的错误之一。
处理fragments的一种通常方法是:
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    ...

    CustomFragment fragment;
    if (savedInstanceState != null) {
        fragment = (CustomFragment) getSupportFragmentManager().findFragmentByTag("customtag");
    } else {
        fragment = new CustomFragment();
        getSupportFragmentManager().beginTransaction().add(R.id.container, fragment, "customtag").commit(); 
    }

    ...

}
使用FragmentPagerAdapter时,我们将片段管理委托给了适配器,不必执行上述步骤。默认情况下,它仅预加载当前位置前后一个片段(尽管除非您使用FragmentStatePagerAdapter,否则它不会销毁它们)。这由ViewPager.setOffscreenPageLimit(int)控制。因此,在适配器之外直接调用片段的方法不能保证有效性,因为它们可能已经不存在。
简而言之,使用putFragment解决方案以后能够获得引用并不是那么疯狂,也不那么不像通常使用片段的方式(如上所述)。因为片段是由适配器添加的,而不是由你本人添加的,所以很难获得对它的引用。只需确保offscreenPageLimit足够高,始终加载所需的片段,因为您依赖它的存在。虽然这会绕过ViewPager的惰性加载功能,但似乎正是您的应用程序所需要的。
另一种方法是覆盖FragmentPageAdapter.instantiateItem(View, int),在返回片段之前从超级调用中保存对返回的片段的引用(如果已经存在,则具有查找片段的逻辑)。
为了全面了解情况,请查看FragmentPagerAdapter(短)和ViewPager(长)的一些源代码。

7
喜欢最后一部分。对碎片有一个缓存,并将把缓存逻辑放在 FragmentPageAdapter.instantiateItem(View, int) 中。最终解决了一个持续已久的错误,在旋转或配置更改时才会出现,让我感到疯狂... - Carlos Sobrinho
2
这解决了我在旋转更改时遇到的问题。也许是因为我看不见,但这是否有文档记录?即使用标签从先前状态中恢复?这可能是一个显而易见的答案,但我对Android开发还比较新。 - user484261
1
@工业抗抑郁症 这是要添加Fragment的容器(例如FrameLayout)的id。 - antonyt
1
顺便说一下,对我来说应该是FragmentPageAdapter.instantiateItem(ViewGroup, int)而不是FragmentPageAdapter.instantiateItem(View, int) - Display Name
1
关于重写FragmentPageAdapter.instantiateItem(ViewGroup, int)的部分值得单独设置一个标题或其他什么东西,因为它非常有帮助。如果您需要保存返回的Fragments的引用,则这是一个完美的位置。此外,在此方法中,您还可以获取Fragmenttag(由FragmentPagerAdapter自动创建的标签)。 - Tony Chan
显示剩余8条评论

37

我希望提供一个解决方案,扩展了antonyt精彩答案和覆盖FragmentPageAdapter.instantiateItem(View,int)以保存对已创建的Fragments的引用,以便稍后可以对它们进行操作。 这也适用于FragmentStatePagerAdapter;有关详细信息,请参见注释。


这是一个简单的示例,演示了如何获取对由FragmentPagerAdapter返回的Fragments的引用,而不依赖于在Fragments上设置的内部tags。关键是重写instantiateItem()并在其中保存引用,而不是在getItem()中保存。
public class SomeActivity extends Activity {
    private FragmentA m1stFragment;
    private FragmentB m2ndFragment;

    // other code in your Activity...

    private class CustomPagerAdapter extends FragmentPagerAdapter {
        // other code in your custom FragmentPagerAdapter...

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

        @Override
        public Fragment getItem(int position) {
            // Do NOT try to save references to the Fragments in getItem(),
            // because getItem() is not always called. If the Fragment
            // was already created then it will be retrieved from the FragmentManger
            // and not here (i.e. getItem() won't be called again).
            switch (position) {
                case 0:
                    return new FragmentA();
                case 1:
                    return new FragmentB();
                default:
                    // This should never happen. Always account for each position above
                    return null;
            }
        }

        // Here we can finally safely save a reference to the created
        // Fragment, no matter where it came from (either getItem() or
        // FragmentManger). Simply save the returned Fragment from
        // super.instantiateItem() into an appropriate reference depending
        // on the ViewPager position.
        @Override
        public Object instantiateItem(ViewGroup container, int position) {
            Fragment createdFragment = (Fragment) super.instantiateItem(container, position);
            // save the appropriate reference depending on position
            switch (position) {
                case 0:
                    m1stFragment = (FragmentA) createdFragment;
                    break;
                case 1:
                    m2ndFragment = (FragmentB) createdFragment;
                    break;
            }
            return createdFragment;
        }
    }

    public void someMethod() {
        // do work on the referenced Fragments, but first check if they
        // even exist yet, otherwise you'll get an NPE.

        if (m1stFragment != null) {
            // m1stFragment.doWork();
        }

        if (m2ndFragment != null) {
            // m2ndFragment.doSomeWorkToo();
        }
    }
}

或者,如果您更喜欢使用标签而不是类成员变量/对Fragments的引用进行工作,您也可以以同样的方式获取由FragmentPagerAdapter设置的标签集:

注意:这不适用于FragmentStatePagerAdapter,因为它在创建其Fragments时不设置标签

@Override
public Object instantiateItem(ViewGroup container, int position) {
    Fragment createdFragment = (Fragment) super.instantiateItem(container, position);
    // get the tags set by FragmentPagerAdapter
    switch (position) {
        case 0:
            String firstTag = createdFragment.getTag();
            break;
        case 1:
            String secondTag = createdFragment.getTag();
            break;
    }
    // ... save the tags somewhere so you can reference them later
    return createdFragment;
}

请注意,此方法不依赖于模仿由FragmentPagerAdapter设置的内部tag,而是使用适当的API来检索它们。这样,即使在未来版本的SupportLibrary中更改了tag,您仍将安全。
请不要忘记,根据您的Activity的设计,您尝试处理的Fragments可能已经存在或不存在,因此在使用引用之前,必须通过进行null检查来解决这个问题。
此外,如果您正在使用FragmentStatePagerAdapter,则不应保留对Fragments的硬引用,因为您可能有很多Fragments,而硬引用会不必要地使它们保留在内存中。相反,请将Fragment引用保存在WeakReference变量中,而不是标准变量。像这样:
WeakReference<Fragment> m1stFragment = new WeakReference<Fragment>(createdFragment);
// ...and access them like so
Fragment firstFragment = m1stFragment.get();
if (firstFragment != null) {
    // reference hasn't been cleared yet; do work...
}

在内存不足的情况下,Android 可能会要求 FragmentManager 销毁未使用的片段,对吗?如果我没错的话,你的第二个版本将会起作用,而第一个版本可能会失败。(不知道当前的 Android 版本是否会这样做,但似乎我们必须预料到这种情况。) - Moritz Both
1
在每次屏幕旋转时都在Activity的onCreate中创建一个FragmetPagerAdapter是怎么样的?这样做是否有问题,因为它可能会绕过在FragmentPagerAdapter中已经添加的片段的重用。 - ygngy
重写instantiateItem()方法是解决问题的方法;这使我能够处理屏幕旋转,并在Activity和Adapter恢复时检索现有的Fragment实例;我在代码中留下了注释以作提醒:旋转后,getItem()方法不会被调用;只有这个instantiateItem()方法被调用。instantiateItem()方法的超级实现实际上在旋转后重新连接片段(如有必要),而不是实例化新实例! - HelloImKevo
我在第一个解决方案中的 Fragment createdFragment = (Fragment) super.instantiateItem.. 上遇到了空指针异常。 - AlexS
在搜索了所有不同的重复/变体迭代版本的问题之后,这是最好的解决方案。(交叉引用自另一个标题更相关的问题,因此感谢您的帮助!) - MandisaW

18
我找到了另一个相对简单的解决方案来回答你的问题。
FragmentPagerAdapter源代码中可以看出,由FragmentPagerAdapter管理的片段存储在使用以下标签生成的FragmentManager中:
String tag="android:switcher:" + viewId + ":" + index;

viewIdcontainer.getId()container 是你的 ViewPager 实例。 index 是碎片的位置。因此,你可以将对象 ID 保存到 outState 中:

@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putInt("viewpagerid" , mViewPager.getId() );
}

@Override
    protected void onCreate(Bundle savedInstanceState) {
    setContentView(R.layout.activity_main);
    if (savedInstanceState != null)
        viewpagerid=savedInstanceState.getInt("viewpagerid", -1 );  

    MyFragmentPagerAdapter titleAdapter = new MyFragmentPagerAdapter (getSupportFragmentManager() , this);        
    mViewPager = (ViewPager) findViewById(R.id.pager);
    if (viewpagerid != -1 ){
        mViewPager.setId(viewpagerid);
    }else{
        viewpagerid=mViewPager.getId();
    }
    mViewPager.setAdapter(titleAdapter);

如果你想与这个片段进行通信,可以通过FragmentManager获取它,例如:

getSupportFragmentManager().findFragmentByTag("android:switcher:" + viewpagerid + ":0")

22
我认为这不是一个好的做法,因为你正在依赖于一种内部标签命名约定,而这种约定不可能永远保持不变。 - Dori
1
对于那些寻找不依赖于内部“tag”的解决方案的人,可以尝试我的答案 - Tony Chan

16
我想为可能稍有不同的情况提供另一种解决方案,因为我搜索答案时很多结果都指向了这个主题。 我的情况 - 我正在动态创建/添加页面并将它们滑入ViewPager中,但当旋转(onConfigurationChange)时,由于OnCreate被再次调用,我会得到一个新页面。但是,我想保留对旋转前创建的所有页面的引用。 问题 - 我没有为我创建的每个片段设置唯一标识符,所以唯一的引用方式就是在数组中存储引用,并在旋转/配置更改后进行恢复。 解决方法 - 关键概念是让Activity(显示Fragments的Activity)也管理对现有Fragments的引用数组,因为该Activity可以利用onSaveInstanceState中的Bundles。
public class MainActivity extends FragmentActivity

在这个 Activity 中,我声明了一个私有成员来跟踪打开的页面。

private List<Fragment> retainedPages = new ArrayList<Fragment>();

每次调用 onSaveInstanceState 时都会更新此内容,并在 onCreate 中恢复

@Override
protected void onSaveInstanceState(Bundle outState) {
    retainedPages = _adapter.exportList();
    outState.putSerializable("retainedPages", (Serializable) retainedPages);
    super.onSaveInstanceState(outState);
}

...所以一旦存储,就可以检索...

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    if (savedInstanceState != null) {
        retainedPages = (List<Fragment>) savedInstanceState.getSerializable("retainedPages");
    }
    _mViewPager = (CustomViewPager) findViewById(R.id.viewPager);
    _adapter = new ViewPagerAdapter(getApplicationContext(), getSupportFragmentManager());
    if (retainedPages.size() > 0) {
        _adapter.importList(retainedPages);
    }
    _mViewPager.setAdapter(_adapter);
    _mViewPager.setCurrentItem(_adapter.getCount()-1);
}

这些是主活动所需的更改,因此我需要我的FragmentPagerAdapter中的成员和方法才能使其正常工作,因此在

public class ViewPagerAdapter extends FragmentPagerAdapter

一个相同的结构体(如上所示在MainActivity中)。
private List<Fragment> _pages = new ArrayList<Fragment>();

在 onSaveInstanceState 中使用的同步功能是由特定方法支持的。

public List<Fragment> exportList() {
    return _pages;
}

public void importList(List<Fragment> savedPages) {
    _pages = savedPages;
}

最后,在片段类中进行以下操作:

public class CustomFragment extends Fragment

为了使所有这些工作正常运行,需要进行两个更改,第一:
public class CustomFragment extends Fragment implements Serializable

然后将此添加到onCreate中,以便Fragment不被销毁。

setRetainInstance(true);

我仍在努力理解Fragments和Android生命周期,所以可能存在冗余或低效的方法。但它适用于我,并希望对其他类似情况的人有所帮助。


2
+1 for setRetainInstance(true) - 这就是我一直在寻找的黑魔法! - declension
使用FragmentStatePagerAdapter(v13)甚至更容易。它可以处理状态恢复和释放的问题。 - The Butcher
清晰的描述和非常好的解决方案。谢谢! - Bruno Bieri

8

我的解决方案很粗糙,但是有效:由于我的碎片是动态创建的,并且从保留的数据中恢复,所以在调用 super.onSaveInstanceState()之前,我只需从 PageAdapter 中删除所有碎片,然后在活动创建时重新创建它们:

@Override
protected void onSaveInstanceState(Bundle outState) {
    outState.putInt("viewpagerpos", mViewPager.getCurrentItem() );
    mSectionsPagerAdapter.removeAllfragments();
    super.onSaveInstanceState(outState);
}

你不能在onDestroy()中移除它们,否则会出现以下异常:java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
这里是页面适配器中的代码:
public void removeAllfragments()
{
    if ( mFragmentList != null ) {
        for ( Fragment fragment : mFragmentList ) {
            mFm.beginTransaction().remove(fragment).commit();
        }
        mFragmentList.clear();
        notifyDataSetChanged();
    }
}

我只保存当前页面并在 onCreate() 中恢复它,在片段已创建之后。

if (savedInstanceState != null)
    mViewPager.setCurrentItem( savedInstanceState.getInt("viewpagerpos", 0 ) );  

我不知道为什么,但是 notifyDataSetChanged(); 导致应用程序崩溃。 - Malachiasz

4
那个BasePagerAdapter是什么?您应该使用标准的 pager adapter 之一 - FragmentPagerAdapterFragmentStatePagerAdapter,取决于您是否希望由于 ViewPager 不再需要的 Fragments 被保留(前者)或其状态被保存(后者),并在需要时重新创建。
可以在此处找到使用ViewPager的示例代码:这里 虽然在视图页上跨活动实例管理片段有点复杂,因为框架中的FragmentManager负责保存状态并恢复页面器生成的任何活动片段。但所有这些都意味着初始化适配器时需要确保它重新连接到任何已恢复的片段。您可以查看FragmentPagerAdapterFragmentStatePagerAdapter的代码来了解如何完成此操作。

1
BasePagerAdapter的代码可以在我的问题中找到。正如大家所看到的,它只是扩展了FragmentPagerAdapter,以实现TitleProvider的目的。因此,一切都已经按照Android开发建议的方法运作良好。 - Oleksii Malovanyi
我之前不知道有“FragmentStatePagerAdapter”这个类,你真的节省了我好几个小时的时间。非常感谢你。 - Knossos

2
如果有人遇到FragmentStatePagerAdapter不能正确恢复其片段状态的问题...即... FragmentStatePagerAdapter创建新片段而不是从状态中恢复它们...
请确保在调用ViewPager.setAdapter(fragmentStatePagerAdapter)之前调用ViewPager.setOffscreenPageLimit()。
在调用ViewPager.setOffscreenPageLimit()时,ViewPager将立即查找其适配器并尝试获取其片段。 这可能会在ViewPager有机会从savedInstanceState恢复Fragments之前发生(因此创建无法从SavedInstanceState重新初始化的新Fragments)。

0
我想出了这个简单而优雅的解决方案。它假设活动负责创建片段,适配器仅为它们提供服务。
这是适配器的代码(除了 mFragments 是由活动维护的碎片列表外,没有什么奇怪的东西)。
class MyFragmentPagerAdapter extends FragmentStatePagerAdapter {

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

    @Override
    public Fragment getItem(int position) {
        return mFragments.get(position);
    }

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

    @Override
    public int getItemPosition(Object object) {
        return POSITION_NONE;
    }

    @Override
    public CharSequence getPageTitle(int position) {
        TabFragment fragment = (TabFragment)mFragments.get(position);
        return fragment.getTitle();
    }
} 

整个线程的问题在于获得“旧”片段的引用,因此我在Activity的onCreate中使用了这段代码。
    if (savedInstanceState!=null) {
        if (getSupportFragmentManager().getFragments()!=null) {
            for (Fragment fragment : getSupportFragmentManager().getFragments()) {
                mFragments.add(fragment);
            }
        }
    }

当然,如果需要的话,您可以进一步微调此代码,例如确保片段是特定类的实例。

0
要在屏幕方向更改后获取片段,您必须使用 .getTag()。
    getSupportFragmentManager().findFragmentByTag("android:switcher:" + viewPagerId + ":" + positionOfItemInViewPager)

为了更好地处理,我编写了自己的ArrayList用于我的PageAdapter,以便在任何位置通过viewPagerId和FragmentClass获取片段:

public class MyPageAdapter extends FragmentPagerAdapter implements Serializable {
private final String logTAG = MyPageAdapter.class.getName() + ".";

private ArrayList<MyPageBuilder> fragmentPages;

public MyPageAdapter(FragmentManager fm, ArrayList<MyPageBuilder> fragments) {
    super(fm);
    fragmentPages = fragments;
}

@Override
public Fragment getItem(int position) {
    return this.fragmentPages.get(position).getFragment();
}

@Override
public CharSequence getPageTitle(int position) {
    return this.fragmentPages.get(position).getPageTitle();
}

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


public int getItemPosition(Object object) {
    //benötigt, damit bei notifyDataSetChanged alle Fragemnts refrehsed werden

    Log.d(logTAG, object.getClass().getName());
    return POSITION_NONE;
}

public Fragment getFragment(int position) {
    return getItem(position);
}

public String getTag(int position, int viewPagerId) {
    //getSupportFragmentManager().findFragmentByTag("android:switcher:" + R.id.shares_detail_activity_viewpager + ":" + myViewPager.getCurrentItem())

    return "android:switcher:" + viewPagerId + ":" + position;
}

public MyPageBuilder getPageBuilder(String pageTitle, int icon, int selectedIcon, Fragment frag) {
    return new MyPageBuilder(pageTitle, icon, selectedIcon, frag);
}


public static class MyPageBuilder {

    private Fragment fragment;

    public Fragment getFragment() {
        return fragment;
    }

    public void setFragment(Fragment fragment) {
        this.fragment = fragment;
    }

    private String pageTitle;

    public String getPageTitle() {
        return pageTitle;
    }

    public void setPageTitle(String pageTitle) {
        this.pageTitle = pageTitle;
    }

    private int icon;

    public int getIconUnselected() {
        return icon;
    }

    public void setIconUnselected(int iconUnselected) {
        this.icon = iconUnselected;
    }

    private int iconSelected;

    public int getIconSelected() {
        return iconSelected;
    }

    public void setIconSelected(int iconSelected) {
        this.iconSelected = iconSelected;
    }

    public MyPageBuilder(String pageTitle, int icon, int selectedIcon, Fragment frag) {
        this.pageTitle = pageTitle;
        this.icon = icon;
        this.iconSelected = selectedIcon;
        this.fragment = frag;
    }
}

public static class MyPageArrayList extends ArrayList<MyPageBuilder> {
    private final String logTAG = MyPageArrayList.class.getName() + ".";

    public MyPageBuilder get(Class cls) {
        // Fragment über FragmentClass holen
        for (MyPageBuilder item : this) {
            if (item.fragment.getClass().getName().equalsIgnoreCase(cls.getName())) {
                return super.get(indexOf(item));
            }
        }
        return null;
    }

    public String getTag(int viewPagerId, Class cls) {
        // Tag des Fragment unabhängig vom State z.B. nach bei Orientation change
        for (MyPageBuilder item : this) {
            if (item.fragment.getClass().getName().equalsIgnoreCase(cls.getName())) {
                return "android:switcher:" + viewPagerId + ":" + indexOf(item);
            }
        }
        return null;
    }
}

所以只需使用片段创建一个MyPageArrayList:

    myFragPages = new MyPageAdapter.MyPageArrayList();

    myFragPages.add(new MyPageAdapter.MyPageBuilder(
            getString(R.string.widget_config_data_frag),
            R.drawable.ic_sd_storage_24dp,
            R.drawable.ic_sd_storage_selected_24dp,
            new WidgetDataFrag()));

    myFragPages.add(new MyPageAdapter.MyPageBuilder(
            getString(R.string.widget_config_color_frag),
            R.drawable.ic_color_24dp,
            R.drawable.ic_color_selected_24dp,
            new WidgetColorFrag()));

    myFragPages.add(new MyPageAdapter.MyPageBuilder(
            getString(R.string.widget_config_textsize_frag),
            R.drawable.ic_settings_widget_24dp,
            R.drawable.ic_settings_selected_24dp,
            new WidgetTextSizeFrag()));

将它们添加到viewPager中。
    mAdapter = new MyPageAdapter(getSupportFragmentManager(), myFragPages);
    myViewPager.setAdapter(mAdapter);

在此之后,您可以通过使用其类来获取正确的片段,以便在方向更改后进行操作:

        WidgetDataFrag dataFragment = (WidgetDataFrag) getSupportFragmentManager()
            .findFragmentByTag(myFragPages.getTag(myViewPager.getId(), WidgetDataFrag.class));

0
一个有点不同的观点是,不要自己存储片段,而是让FragmentManager处理,当您需要对片段进行操作时,在FragmentManager中查找它们即可。
//make sure you have the right FragmentManager 
//getSupportFragmentManager or getChildFragmentManager depending on what you are using to manage this stack of fragments
List<Fragment> fragments = fragmentManager.getFragments();
if(fragments != null) {
   int count = fragments.size();
   for (int x = 0; x < count; x++) {
       Fragment fragment = fragments.get(x);
       //check if this is the fragment we want, 
       //it may be some other inspection, tag etc.
       if (fragment instanceof MyFragment) {
           //do whatever we need to do with it
       }
   }
}

如果你有很多碎片并且 instanceof 检查的成本可能不是你想要的,但是要记住 FragmentManager 已经记录了 Fragments,这是一个好的事情。

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