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

标签 android design-patterns android-fragments android-viewpager

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

编辑

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

范围

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

问题

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

问题

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

代码

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

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
}

}

fragment

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 将 fragment 保存在 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();
    ...
}

最佳答案

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

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

处理 fragment 时的常用方法是:

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

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

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

要获得更完整的图片,请查看 FragmentPagerAdapter 的一些来源(短)和ViewPager (长)。

关于android - ViewPager 和 Fragment — 存储 Fragment 状态的正确方法是什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/7951730/

相关文章:

Android:多 Pane 布局上操作栏/选项菜单的设计模式

安卓;切换选项卡时 fragment 重叠

java - 如何在android中使用java制作ImageView?

android - GalleryView 项目更改监听器

java - 在 Activity 之间共享 ArrayList 的良好编程实践

Android- 增强现实图像链接

android - 我在 android 中的 MVP 模式方法

android - 在 fragment 中使用 YouTube API

api - API(如 Web API)基于什么设计模式(如果有)?

java - java对象中更新字段的正确方法(设计模式)