ViewPager和片段 - 什么是正确的方式来存储片段的状态呢? [英] ViewPager and fragments — what's the right way to store fragment's state?

查看:153
本文介绍了ViewPager和片段 - 什么是正确的方式来存储片段的状态呢?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

片段似乎是很不错的分离UI逻辑到一些模块。但随着 ViewPager 它的生命周期仍然飘渺给我。因此,大师的思想都是很重要的!

修改

请参阅下面的哑巴解决方案; - )

范围

主要活动有一个 ViewPager 与片段。这些片段可以实现一点点不同的逻辑,其他(submain)活动,使碎片的数据是通过活动的内部回调接口填补。一切工作正常在第一次发射,但!...

问题

在该活动被重新创建(例如,在方向变化),这样做的 ViewPager 的片段。在code(你会发现下图)说,每次创建活动的时候,我尝试创建一个新的 ViewPager 片段适配器相同的片段(也许这就是这个问题),但FragmentManager已经有了所有这些片段存储在某个地方(在哪里?),并开始在娱乐机制的。所以,娱乐机构所说的老片段的onAttach,onCreateView,等我的回调接口呼吁通过活动的实施方法,启动数据。但这种方法指向其经由活动的onCreate方法创建的新创建的片段。

问题

也许我用错了方式,但即使是Android 3.0的临书没有多少了。因此,,给我一两冲,并指出如何做到这一点的正确方法。非常感谢!

code

主要活动

 公共类DashboardActivity扩展BasePagerActivity实现OnMessageListActionListener {

私人MessagesFragment mMessagesFragment;

@覆盖
保护无效的onCreate(包savedInstanceState){
    Logger.d(短跑的onCreate);
    super.onCreate(savedInstanceState);

    的setContentView(R.layout.viewpager_container);
    新DefaultToolbar(本);

    //创建片段使用
    mMessagesFragment =新MessagesFragment();
    mStreamsFragment =新StreamsFragment();

    //设置标题和片段的视图寻呼机
    地图<字符串,片断>屏幕=新的LinkedHashMap<字符串,片断>();
    screens.put(getApplicationContext()的getString(R.string.dashboard_title_dumb),新DumbFragment());
    screens.put(getApplicationContext()的getString(R.string.dashboard_title_messages),mMessagesFragment。);

    //通过适配器实例化视图寻呼机
    mPager =(ViewPager)findViewById(R.id.viewpager_pager);
    mPagerAdapter =新BasePagerAdapter(屏幕,getSupportFragmentManager());
    mPager.setAdapter(mPagerAdapter);

    //设置标题指标
    TitlePageIndicator指标=(TitlePageIndicator)findViewById(R.id.viewpager_titles);
    indicator.setViewPager(mPager,1);

}

/ *组片段的回调接口实现* /

@覆盖
公共无效onMessageInitialisation(){

    Logger.d(短跑onMessageInitialisation);
    如果(mMessagesFragment!= NULL)
        mMessagesFragment.loadLastMessages();
}

@覆盖
公共无效onMessageSelected(消息selectedMessage){

    意向意图=新的意图(这一点,StreamActivity.class);
    intent.putExtra(Message.class.getName(),selectedMessage);
    startActivity(意向);
}
 

BasePagerActivity又名帮手

 公共类BasePagerActivity扩展FragmentActivity {

BasePagerAdapter mPagerAdapter;
ViewPager mPager;
}
 

适配器

 公共类BasePagerAdapter扩展FragmentPagerAdapter实现TitleProvider {

私人地图<字符串,片断> mScreens;

公共BasePagerAdapter(地图<字符串,片断>屏幕布局,FragmentManager FM){

    超(FM);
    this.mScreens =屏幕布局;
}

@覆盖
公共片段的getItem(INT位置){

    返回mScreens.values​​()的toArray(新片段[mScreens.size())[位置]。
}

@覆盖
公众诠释getCount将(){

    返回mScreens.size();
}

@覆盖
公共字符串的getTitle(INT位置){

    返回mScreens.keySet()的toArray(新的String [mScreens.size())[位置]。
}

//破解。我们不想破坏我们的片段后重新启动它们
@覆盖
公共无效destroyItem(View容器,INT位置,Object对象){

    // TODO自动生成方法存根
}

}
 

片段

 公共类MessagesFragment扩展ListFragment {

私人布尔mIsLastMessages;

私人列表<消息> mMessagesList;
私人MessageArrayAdapter mAdapter;

私人LoadMessagesTask mLoadMessagesTask;
私人OnMessageListActionListener mListener;

//定义回调接口
公共接口OnMessageListActionListener {
    公共无效onMessageInitialisation();
    公共无效onMessageSelected(消息selectedMessage);
}

@覆盖
公共无效onAttach(活动活动){
    super.onAttach(活动);
    //设置回调
    mListener =(OnMessageListActionListener)的活动;
    mIsLastMessages =活动的instanceof DashboardActivity;

}

@覆盖
公共查看onCreateView(LayoutInflater充气,容器的ViewGroup,捆绑savedInstanceState){
    inflater.inflate(R.layout.fragment_listview,集装箱);
    mProgressView = inflater.inflate(R.layout.listrow_progress,NULL);
    mEmptyView = inflater.inflate(R.layout.fragment_nodata,NULL);
    返回super.onCreateView(充气,容器,savedInstanceState);
}

@覆盖
公共无效onActivityCreated(包savedInstanceState){
    super.onActivityCreated(savedInstanceState);

    //实例化加载任务
    mLoadMessagesTask =新LoadMessagesTask();

    //实例化的邮件列表
    mMessagesList =新的ArrayList<消息>();
    mAdapter =新MessageArrayAdapter(getActivity(),mMessagesList);
    setListAdapter(mAdapter);
}

@覆盖
公共无效onResume(){
    mListener.onMessageInitialisation();
    super.onResume();
}

公共无效onListItemClick(ListView的L,视图V,INT位置,长的id){
    消息selectedMessage =(消息)getListAdapter()的getItem(位置)。
    mListener.onMessageSelected(selectedMessage);
    super.onListItemClick(L,V,位置ID);
}

/ *公共方法从宿主活性的研究,等消息加载... * /
}
 

解决方案

哑的解决方案是保存的片段里面的onSaveInstanceState(主机活动的)与putFragment,并通过getFragment让他们里面的onCreate。但我还是有一种奇怪的感觉,事情不应该这样的,参见下文code:

  @覆盖
保护无效的onSaveInstanceState(包outState){

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

保护无效的onCreate(包savedInstanceState){
    Logger.d(短跑的onCreate);
    super.onCreate(savedInstanceState);

    ...
    //创建片段使用
    如果(savedInstanceState!= NULL){
        mMessagesFragment =(MessagesFragment)getSupportFragmentManager()。getFragment(
                savedInstanceState,MessagesFragment.class.getName());
                StreamsFragment.class.getName());
    }
    如果(mMessagesFragment == NULL)
        mMessagesFragment =新MessagesFragment();
    ...
}
 

解决方案

FragmentPagerAdapter 添加一个片段的FragmentManager,它采用基于具体位置的特殊标记该片段将被放置。 FragmentPagerAdapter.getItem(INT位置)<$ C C $> 只有当该位置的片段不存在所谓的。转动后,机器人会发现,它已经创建/保存一个片段为这个特定的位置,所以它只是尝试重新连接()的,而不是建立它与 FragmentManager.findFragmentByTag一个新的。所有这一切都免费的,当使用 FragmentPagerAdapter ,这就是为什么它通常有的getItem(INT)内部的片段初始化code 方法。

即使我们没有使用 FragmentPagerAdapter ,这是不是一个好主意,以创建一个新片段 Activity.onCreate每一次(束)。正如您已经注意到,当一个片段被添加到FragmentManager,将旋转后重新创建为你,并且没有必要重新添加。这样做是错误的常见原因有碎片工作时。

与片段工作时,通常的做法是这样的:

 保护无效的onCreate(包savedInstanceState){
    super.onCreate(savedInstanceState);

    ...

    CustomFragment片段;
    如果(savedInstanceState!= NULL){
        片段=(CustomFragment)getSupportFragmentManager()findFragmentByTag(customtag);
    } 其他 {
        片段=新CustomFragment();
        。getSupportFragmentManager()的BeginTransaction()加(R.id.container,片段,customtag)提交()。
    }

    ...

}
 

在使用 FragmentPagerAdapter ,我们放弃片段管理适配器,并且没有执行上述步骤。默认情况下,它只会在前面和后面的当前位置preLOAD一个片段(虽然它不,除非你使用的是摧毁他们 FragmentStatePagerAdapter )。这是由<受控href="http://developer.android.com/reference/android/support/v4/view/ViewPager.html#setOffscreenPageLimit%28int%29">ViewPager.setOffscreenPageLimit(int).正因为如此,在适配器的外部的所述片段直接调用方法不能保证有效,因为它们可能甚至不活着。

要长话短说总之,你的解决方案中使用 putFragment 来能够得到一个参考事后没有那么疯狂,而不是一点都不像正常的方式来使用反正(上图)的片段。这是很难获得参考否则因为该片段由适配器,并将不你本人。只要确保在 offscreenPageLimit 高到足以载入您想要的片段在任何时候,因为你要靠它是present。这绕过ViewPager的延迟加载功能,但似乎是你想要为你的应用是什么。

另一种方法是重写 FragmentPageAdapter.instantiateItem(查看,INT)并保存参考返回之前从超级调用返回的片段(它有逻辑发现该片段中,如果已经present)。

对于一个更全面的了解,看看一些<一源href="http://grep$c$c.com/file/repository.grep$c$c.com/java/ext/com.google.android/android/4.2.2_r1/android/support/v4/view/PagerAdapter.java?av=f">FragmentPagerAdapter (短)和<一href="http://grep$c$c.com/file/repository.grep$c$c.com/java/ext/com.google.android/android/4.2.2_r1/android/support/v4/view/ViewPager.java?av=f">ViewPager (长)。

Fragments seem to be very nice for separation of UI logic into some modules. But along with ViewPager its lifecycle is still misty to me. So Guru thoughts are badly needed!

Edit

See dumb solution below ;-)

Scope

Main activity has a ViewPager with fragments. Those fragments could implement a little bit different logic for other (submain) activities, so the fragments' data is filled via a callback interface inside the activity. And everything works fine on first launch, but!...

Problem

When the activity gets recreated (e.g. on orientation change) so do the ViewPager's fragments. The code (you'll find below) says that every time the activity is created I try to create a new ViewPager fragments adapter the same as fragments (maybe this is the problem) but FragmentManager already has all these fragments stored somewhere (where?) and starts the recreation mechanism for those. So the recreation mechanism calls the "old" fragment's onAttach, onCreateView, etc. with my callback interface call for initiating data via the Activity's implemented method. But this method points to the newly created fragment which is created via the Activity's onCreate method.

Issue

Maybe I'm using wrong patterns but even Android 3 Pro book doesn't have much about it. So, please, give me one-two punch and point out how to do it the right way. Many thanks!

Code

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 aka helper

public class BasePagerActivity extends FragmentActivity {

BasePagerAdapter mPagerAdapter;
ViewPager mPager;
}

Adapter

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

Solution

The dumb solution is to save the fragments inside onSaveInstanceState (of host Activity) with putFragment and get them inside onCreate via getFragment. But I still have a strange feeling that things shouldn't work like that... See code below:

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

解决方案

When the FragmentPagerAdapter adds a fragment to the FragmentManager, it uses a special tag based on the particular position that the fragment will be placed. FragmentPagerAdapter.getItem(int position) is only called when a fragment for that position does not exist. After rotating, Android will notice that it already created/saved a fragment for this particular position and so it simply tries to reconnect with it with FragmentManager.findFragmentByTag(), instead of creating a new one. All of this comes free when using the FragmentPagerAdapter and is why it is usual to have your fragment initialisation code inside the getItem(int) method.

Even if we were not using a FragmentPagerAdapter, it is not a good idea to create a new fragment every single time in Activity.onCreate(Bundle). As you have noticed, when a fragment is added to the FragmentManager, it will be recreated for you after rotating and there is no need to add it again. Doing so is a common cause of errors when working with fragments.

A usual approach when working with fragments is this:

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

    ...

}

When using a FragmentPagerAdapter, we relinquish fragment management to the adapter, and do not have to perform the above steps. By default, it will only preload one Fragment in front and behind the current position (although it does not destroy them unless you are using FragmentStatePagerAdapter). This is controlled by ViewPager.setOffscreenPageLimit(int). Because of this, directly calling methods on the fragments outside of the adapter is not guaranteed to be valid, because they may not even be alive.

To cut a long story short, your solution to use putFragment to be able to get a reference afterwards is not so crazy, and not so unlike the normal way to use fragments anyway (above). It is difficult to obtain a reference otherwise because the fragment is added by the adapter, and not you personally. Just make sure that the offscreenPageLimit is high enough to load your desired fragments at all times, since you rely on it being present. This bypasses lazy loading capabilities of the ViewPager, but seems to be what you desire for your application.

Another approach is to override FragmentPageAdapter.instantiateItem(View, int) and save a reference to the fragment returned from the super call before returning it (it has the logic to find the fragment, if already present).

For a fuller picture, have a look at some of the source of FragmentPagerAdapter (short) and ViewPager (long).

这篇关于ViewPager和片段 - 什么是正确的方式来存储片段的状态呢?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆