ViewPager 和 Fragments — 存储 Fragment 状态的正确方法是啥?

Posted

技术标签:

【中文标题】ViewPager 和 Fragments — 存储 Fragment 状态的正确方法是啥?【英文标题】:ViewPager and fragments — what's the right way to store fragment's state?ViewPager 和 Fragments — 存储 Fragment 状态的正确方法是什么? 【发布时间】:2011-12-18 14:23:51 【问题描述】:

片段似乎非常适合将 UI 逻辑分离到某些模块中。但是与ViewPager 一起,它的生命周期对我来说仍然是模糊的。所以非常需要上师的思想!

编辑

请参阅下面的愚蠢解决方案;-)

范围

主要活动有一个带有片段的ViewPager。这些片段可以为其他(子主)活动实现一些不同的逻辑,因此片段的数据通过活动内部的回调接口填充。首次启动时一切正常,但是!...

问题

当重新创建活动时(例如在方向更改时),ViewPager 的片段也会这样做。代码(你会在下面找到)说,每次创建活动时,我都会尝试创建一个新的 ViewPager 片段适配器,与片段相同(也许这是问题所在)但 FragmentManager 已经将所有这些片段存储在某个地方(在?) 并为它们启动娱乐机制。所以重新创建机制调用“旧”片段的 onAttach、onCreateView 等,我的回调接口调用通过 Activity 的实现方法来初始化数据。但是这个方法指向的是通过Activity的onCreate方法新建的fragment。

问题

也许我使用了错误的模式,但即使是 android 3 Pro 书籍也没有太多关于它的内容。所以,,给我一两拳,并指出如何以正确的方式做到这一点。非常感谢!

代码

主要活动

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 又名助手

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... */

解决方案

愚蠢的解决方案是使用 putFragment 将片段保存在 onSaveInstanceState(宿主 Activity)中,并通过 getFragment 将它们放入 onCreate 中。但是我仍然有一种奇怪的感觉,事情不应该那样工作......见下面的代码:

    @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();
    ...

【问题讨论】:

我现在想知道:我应该使用一种非常不同的方法还是尝试通过 onSavedInstancestate 保存主要活动(仪表板)的片段以在 onCreate() 中使用它们。是否有适当的方法来保存这些片段并从 onCreate 中的捆绑包中获取它们?它们似乎不可包裹...... 第二种方法有效——参见“解决方案”。但这似乎是一段丑陋的代码,不是吗? 为了清理 Android 标签(详情请点击此处:meta.stackexchange.com/questions/100529/…),您介意将您的解决方案发布为答案并将其标记为选定的吗?这样它就不会显示为未回答的问题:) 是的,认为没关系。希望比我的更好...... 愚蠢的解决方案是否有效?它给了我一个空指针异常.. 【参考方案1】:

FragmentPagerAdapter 向FragmentManager 添加一个片段时,它会根据片段将被放置的特定位置使用一个特殊标签。 FragmentPagerAdapter.getItem(int position) 仅在该位置的片段不存在时调用。旋转后,Android 会注意到它已经为这个特定位置创建/保存了一个片段,因此它只是尝试使用FragmentManager.findFragmentByTag() 重新连接它,而不是创建一个新片段。使用FragmentPagerAdapter 时,所有这些都是免费的,这就是为什么通常将片段初始化代码放在getItem(int) 方法中的原因。

即使我们没有使用FragmentPagerAdapter,在Activity.onCreate(Bundle) 中每次都创建一个新片段也不是一个好主意。正如您所注意到的,当将片段添加到 FragmentManager 时,它将在旋转后为您重新创建,无需再次添加。这样做是处理片段时出错的常见原因。

处理片段时的常用方法是:

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 时,我们将片段管理交给适配器,而不必执行上述步骤。默认情况下,它只会在当前位置的前后预加载一个 Fragment(尽管它不会破坏它们,除非你使用FragmentStatePagerAdapter)。这由ViewPager.setOffscreenPageLimit(int) 控制。正因为如此,直接在适配器外部的片段上调用方法并不能保证是有效的,因为它们甚至可能不是活着的。

长话短说,您使用putFragment 之后能够获得参考的解决方案并没有那么疯狂,并且与使用片段的正常方式(上图)没有太大不同。否则很难获得参考,因为片段是由适配器添加的,而不是您个人添加的。只要确保offscreenPageLimit 足够高,可以随时加载您想要的片段,因为您依赖它的存在。这绕过了 ViewPager 的延迟加载功能,但似乎是您对应用程序的期望。

另一种方法是覆盖 FragmentPageAdapter.instantiateItem(View, int) 并在返回之前保存对从 super 调用返回的片段的引用(如果已经存在,它具有查找片段的逻辑)。

要获得更全面的信息,请查看FragmentPagerAdapter(短)和ViewPager(长)的一些来源。

【讨论】:

喜欢最后一部分。有一个片段缓存,并在FragmentPageAdapter.instantiateItem(View, int) 内移动了缓存逻辑。终于修复了一个只出现在轮换/配置更改中并且让我发疯的长期存在的错误...... 这解决了我在轮换更改时遇到的问题。也许我只是看不到寻找,但这有记录吗?即使用标签从以前的状态恢复?这可能是一个显而易见的答案,但我对 Android 开发还很陌生。 @Industrial-antidepressant 要添加 Fragment 的容器(例如 FrameLayout)的 id。 顺便说一句,对我来说是FragmentPageAdapter.instantiateItem(ViewGroup, int)而不是FragmentPageAdapter.instantiateItem(View, int) 顺便说一句,当片段从屏幕上滑出时,您知道调用哪个(如果有的话)片段生命周期方法吗?是onDetach(),还是别的什么?【参考方案2】:

我想提供一个扩展 antonytwonderful answer 的解决方案,并提到覆盖 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();
        
    

如果您更喜欢使用tags 而不是类成员变量/对Fragments 的引用,您也可以以相同的方式获取FragmentPagerAdapter 设置的tags: 注意:这不适用于FragmentStatePagerAdapter,因为它在创建Fragments 时没有设置tags

@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 来检索它们。这样,即使 tagSupportLibrary 的未来版本中发生更改,您仍然是安全的。


不要忘记,根据您的 Activity 的设计,您尝试使用的 Fragments 可能存在也可能不存在,因此您必须考虑到这一点在使用您的参考资料之前进行null 检查。

另外,如果您正在使用FragmentStatePagerAdapter,那么您不想保留对您的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 版本是否这样做,但似乎我们必须期待。) 如何在每次屏幕旋转时在 Activity 的 onCreate 中创建一个 FragmetPagerAdapter。这是错误的吗,因为它可能会绕过在FragmentPagerAdapter中重复使用已添加的片段 覆盖instantiateItem() 是要走的路;这有助于我处理屏幕旋转,并在 Activity 和 Adapter 恢复后检索我现有的 Fragment 实例;我在代码中留下了 cmets 作为提醒:旋转后,getItem() 未被调用;只有这个方法instantiateItem() 被调用。 instantiateItem() 的超级实现实际上在旋转后重新附加片段(根据需要),而不是实例化新实例! 在第一个解决方案中,Fragment createdFragment = (Fragment) super.instantiateItem.. 上出现空指针。 在搜索了这个问题的所有各种重复/变体迭代之后,这是最好的解决方案。 (从另一个 Q 的更相关标题交叉引用,所以谢谢你!)【参考方案3】:

我为您的问题找到了另一个相对简单的解决方案。

从FragmentPagerAdapter source code可以看出,FragmentPagerAdapter管理的分片存储在FragmentManager的tag下使用:

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);

如果你想和这个fragment通信,可以从FragmentManager获取if,如:

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

【讨论】:

hmmm 我不认为这是一个好方法,因为您回复内部标签命名约定绝不会永远保持不变。 对于那些正在寻找不依赖于内部tag 的解决方案的人,请考虑尝试my answer【参考方案4】:

我想为可能略有不同的情况提供一个替代解决方案,因为我的许多答案搜索一直将我带到这个线程。

我的情况 - 我正在动态创建/添加页面并将它们滑动到 ViewPager 中,但是当旋转(onConfigurationChange)时,我最终会得到一个新页面,因为当然会再次调用 OnCreate。但我想保留对轮换之前创建的所有页面的引用。

问题 - 我创建的每个片段都没有唯一标识符,因此引用的唯一方法是以某种方式将引用存储在数组中,以便在轮换/配置更改后恢复。

解决方法 - 关键概念是让 Activity(显示 Fragments)也管理对现有 Fragments 的引用数组,因为此 Activity 可以利用 onSaveInstanceState 中的 Bundles

public class MainActivity extends FragmentActivity

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

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

每次在 onCreate 中调用和恢复 onSaveInstanceState 时都会更新

@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 这样片段就不会被破坏

setRetainInstance(true);

我仍在研究 Fragments 和 Android 生命周期,因此需要注意的是,这种方法可能存在冗余/效率低下的问题。但它对我有用,我希望可能对其他与我有类似情况的人有所帮助。

【讨论】:

+1 for setRetainInstance(true) - 我一直在寻找的黑魔法! 使用 FragmentStatePagerAdapter (v13) 更容易。哪个为您处理状态恢复和释放。 清晰的描述和非常好的解决方案。谢谢!【参考方案5】:

我的解决方案非常粗鲁但有效:作为我的片段从保留的数据动态创建,我只需在调用super.onSaveInstanceState() 之前从PageAdapter 中删除所有片段,然后在活动创建时重新创建它们:

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

你不能在onDestroy()中删除它们,否则你会得到这个异常:

java.lang.IllegalStateException: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();导致应用崩溃【参考方案6】:

BasePagerAdapter 是什么?您应该使用标准寻呼机适配器之一——FragmentPagerAdapterFragmentStatePagerAdapter,这取决于您是否希望将 ViewPager 不再需要的片段保留在周围(前者)或保存它们的状态(后者)并在需要时重新创建。

使用ViewPager的示例代码可以在here找到

确实,跨活动实例的视图分页器中的片段管理有点复杂,因为框架中的FragmentManager 负责保存状态并恢复分页器创建的任何活动片段。这实际上意味着适配器在初始化时需要确保它与任何已恢复的片段重新连接。您可以查看 FragmentPagerAdapterFragmentStatePagerAdapter 的代码以了解这是如何完成的。

【讨论】:

BasePagerAdapter 代码在我的问题中可用。正如人们所看到的,它只是为了实现TitleProvider 而扩展了 FragmentPagerAdapter。所以一切都已经在使用 android dev 建议的方法了。 我不知道“FragmentStatePagerAdapter”。你真的为我节省了几个小时。谢谢。【参考方案7】:

如果有人在他们的 FragmentStatePagerAdapter 无法正确恢复其 Fragment 的状态时遇到问题...即...FragmentStatePagerAdapter 正在创建新的 Fragment 而不是从状态中恢复它们...

请确保在致电ViewPager.setAdapter(fragmentStatePagerAdapter)之前致电ViewPager.setOffscreenPageLimit()

调用ViewPager.setOffscreenPageLimit()...ViewPager 将立即查看其适配器并尝试获取其片段。这可能发生在 ViewPager 有机会从 savedInstanceState 恢复 Fragments 之前(从而创建新的 Fragment,因为它们是新的,所以无法从 SavedInstanceState 重新初始化)。

【讨论】:

【参考方案8】:

我想出了这个简单而优雅的解决方案。它假定 Activity 负责创建 Fragments,而 Adapter 只是为它们服务。

这是适配器的代码(这里没什么奇怪的,除了mFragments是Activity维护的片段列表)

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);
            
        
    

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

【讨论】:

【参考方案9】:

要在方向更改后获取片段,您必须使用 .getTag()。

    getSupportFragmentManager().findFragmentByTag("android:switcher:" + viewPagerId + ":" + positionOfItemInViewPager)

为了进行更多处理,我为我的 PageAdapter 编写了自己的 ArrayList,以通过 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));

【讨论】:

【参考方案10】:

有点不同的意见,而不是自己存储 Fragment,只需将其留给 FragmentManager,当您需要对 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
       
   

如果您有很多 Fragment,并且 instanceof 检查的成本可能不是您想要的,但最好记住 FragmentManager 已经记录了 Fragment。

【讨论】:

【参考方案11】:

添加:

   @SuppressLint("ValidFragment")

上课前。

它不起作用做这样的事情:

@SuppressLint( "ValidFragment", "HandlerLeak" )

【讨论】:

以上是关于ViewPager 和 Fragments — 存储 Fragment 状态的正确方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章

Android 2.2 + ViewPager和Fragments实现TabHost使用教程

使用 Fragments 和 ViewPager 一段时间后,Android 应用程序崩溃

如何从 ViewPager 的 onPageSelected AsyncTask 到 Fragments?

响应Activity中的ViewPager2 Fragments事件

wemall app商城源码中基于JAVA的T绑定和处理fragments和viewpager之间的逻辑关系代码

将 ViewPager 与 Fragments 一起使用时,在滑动时会破坏 Fragment