小编典典

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

all

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

编辑

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

范围

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

问题

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

问题

也许我使用了错误的模式,但即使是 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();
    ...
}

阅读 94

收藏
2022-03-13

共1个答案

小编典典

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

即使我们没有使用 a 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(); 
    }

    ...

}

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

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

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

如需更全面的图片,请查看FragmentPagerAdapter(短)和ViewPager(长)的一些来源。

2022-03-13