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个回答

-32

添加:

   @SuppressLint("ValidFragment")

在你的类之前。

如果它不起作用,就像这样做:

@SuppressLint({ "ValidFragment", "HandlerLeak" })

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