androidfragment-当另一个片段被推到其顶部时如何保存视图状态


137

在android中,一个片段(例如FragA)被添加到了backstack中,另一个片段(例如FragB)进入了顶部。现在回击FragA到顶部并被onCreateView()调用。现在,我FragA处于一种特殊的状态,然后FragB才被推上去。

我的问题是如何恢复FragA到以前的状态?有没有一种方法可以保存状态(例如在捆绑中说),如果可以,那么我应该重写哪种方法?

Answers:


98

片段指南 FragmentList示例中,您可以找到:

@Override
public void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putInt("curChoice", mCurCheckPosition);
}

您以后可以使用它,如下所示:

@Override
public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    if (savedInstanceState != null) {
        // Restore last state for checked position.
        mCurCheckPosition = savedInstanceState.getInt("curChoice", 0);
    }
}

我是Fragments的初学者,但似乎是您的问题的解决方案;)在片段从后堆栈返回后,将调用OnActivityCreated。


18
我无法使它正常工作saveInstanceState始终为null。我通过xml布局添加片段。不得不将mCurCheckPosition更改为静态,然后它可以工作,但是感觉很笨拙。
scottyab

57
它不调用onSaveInstanceState-为什么呢?因此,这种方法行不通。
2012年

10
万一我们想在同一Activity中从另一个片段返回时保留片段状态,这种方法真的可行吗?onSaveInstanceState()仅在Activity onPause / onStop事件上被调用。根据文档:“与活动一样,您可以使用捆绑包保留片段的状态,以防活动的进程被杀死,并且您需要在重新创建活动时还原片段的状态。片段的onSaveInstanceState()回调并在onCreate(),onCreateView()或onActivityCreated()期间将其还原。”
Paramvir Singh

26
从记录来看,这种方法是错误的,应该没有什么地方可以进行投票。onSaveInstanceState仅在其相应活动也关闭时调用。
Martin Konecny 2014年

16
当发生配置更改且活动被销毁时,onSaveInstanceState()被称为onle,此答案是错误的
Tadas Valaitis 2015年

83

片段的onSaveInstanceState(Bundle outState)永远不会被调用,除非片段的活动调用它本身和连接片段。因此,只有在某种情况下(通常是旋转)迫使活动进入SaveInstanceState并在以后恢复它,该方法才会被调用。但是,如果您只有一个活动,并且其中包含大量片段(大量使用replace),并且应用程序仅在一个方向上运行onSaveInstanceState(Bundle outState),那么很长一段时间可能不会调用活动。

我知道三种可能的解决方法。

首先:

使用片段的参数保存重要数据:

public class FragmentA extends Fragment {
    private static final String PERSISTENT_VARIABLE_BUNDLE_KEY = "persistentVariable";

    private EditText persistentVariableEdit;

    public FragmentA() {
        setArguments(new Bundle());
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_a, null);

        persistentVariableEdit = (EditText) view.findViewById(R.id.editText);

        TextView proofTextView = (TextView) view.findViewById(R.id.textView);

        Bundle mySavedInstanceState = getArguments();
        String persistentVariable = mySavedInstanceState.getString(PERSISTENT_VARIABLE_BUNDLE_KEY);

        proofTextView.setText(persistentVariable);


        view.findViewById(R.id.btnPushFragmentB).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                getFragmentManager()
                        .beginTransaction()
                        .replace(R.id.frameLayout, new FragmentB())
                        .addToBackStack(null)
                        .commit();
            }
        });

        return view;
    }

    @Override
    public void onPause() {
        super.onPause();
        String persistentVariable = persistentVariableEdit.getText().toString();

        getArguments().putString(PERSISTENT_VARIABLE_BUNDLE_KEY, persistentVariable);
    }
}

第二种但不太古怪的方式-以单例形式持有变量

第三个-不要replace()分段,而是add()/ show()/ hide()代替。


3
最好的解决方法Fragment.onSaveInstanceState()是永远不会被调用。只需将您自己的数据保存到参数中,包括列表视图中的项目或其ID(如果您还有其他集中式数据管理器)。无需保存列表视图的位置-已保存并自动恢复。

我尝试在我的应用中使用您的示例,但这String persistentVariable = mySavedInstanceState.getString(PERSISTENT_VARIABLE_BUNDLE_KEY);始终是null。有什么问题?
fragon 2015年

确定要使用片段的getArguments()方法,包括在ViewPager中嵌套片段。我使用1个活动并交换许多片段进/出,这非常有效。这是一个简单的测试,供您审核所有建议的解决方案:1)从Fragment A转到Fragment B;2)改变设备方向两次;3)按设备上的后退按钮。
Andy H.

我尝试了第一种方法,但对我没有用。getArguments()始终返回null。这也很有意义,因为替换了片段,并在onCreate()中设置了新的Bundle,因此旧的Bundle丢失了。我想念什么,我错了吗?
Zvi

@Zvi,我在1.5年前编写了这段代码,不记得所有细节,但是正如我所记得的那样,replace不会重新创建片段,仅当您从代码中创建了新实例时才重新创建片段。在这种情况下,显然,构造函数调用并setArguments(new Bundle());覆盖了旧的Bundle。因此,请确保仅创建一次片段,然后使用该实例,而不是每次都创建一个。
Fyodor Volchyok '16

20

请注意,如果您使用ViewPager处理Fragments,则非常简单。您只需要调用此方法:setOffscreenPageLimit()

根据文档:

设置在空闲状态下视图层次结构中应保留到当前页面任一侧的页面数。超出此限制的页面将在需要时从适配器重新创建。

这里类似的问题


5
这是不同的。setOffScreenPageLimit的行为就像一个缓存(这意味着ViewPager在给定的时刻必须处理多少页),但不用于保存Fragment的状态。
Renaud Mathieu 2013年

就我而言,它与setOffscreenPageLimit()一起使用-尽管碎片已被破坏,但视图状态已保存并恢复。
达文乔2014年

谢谢,也帮了我。
以利亚2015年

几年后,这仍然如此重要。尽管它并不能真正回答问题,但可以解决问题
Supreme Dolphin

19

只需一次为您的视图充气。

示例如下:

public class AFragment extends Fragment {

private View mRootView;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    if(mRootView==null){
        mRootView = inflater.inflate(R.id.fragment_a, container, false);
        //......
    }
    return mRootView;
}

}


还应将现有碎片保留在阵列中或其他东西上
Amir

我听说对片段的rootView进行引用是一种不好的做法-可能导致泄漏[需要引用]?
giraffe.guru

1
@ giraffe.guru Fragment指的是它的根视图不会这样做。尽管某些GC根元素将引用非ui线程变量,例如全局静态属性。一个Fragment实例是不是GC-根,因此它可以被垃圾收集。因此,其根源也是如此。
Lym Zoy

你我今天一样。
Vijendra patidar

甜美而简单。
Rupam Das

8

我处理的问题与此非常相似。因为我知道我会经常来恢复回以前的片段,我检查了一下片段是否.isAdded()是真实的,如果是这样,而不是做一个transaction.replace()我只是做了transaction.show()。如果片段已经在堆栈中,则可以防止片段被重新创建-无需保存状态。

Fragment target = <my fragment>;
FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
if(target.isAdded()) {
    transaction.show(target);
} else {
    transaction.addToBackStack(button_id + "stack_item");
    transaction.replace(R.id.page_fragment, target);
}
transaction.commit();

要记住的另一件事是,尽管这保留了片段本身的自然顺序,但您可能仍需要处理在方向(配置)更改时销毁并重新创建的活动本身。要在您的节点的AndroidManifest.xml中解决此问题:

android:configChanges="orientation|screenSize"

在Android 3.0及更高版本中,screenSize显然是必需的。

祝好运


transaction.addToBackStack(button_id +“ stack_item”); //这行是做什么的。这里的button_id是什么?
raghu_3 2014年

button_id只是一个虚构的变量。传递给addToBackStack的字符串参数只是Backstack状态的可选名称-如果您仅管理单个Backstack,则可以将其设置为null。
rmir​​abelle 2014年

,我有一个问题,类似这样与片段,你能请看着它- stackoverflow.com/questions/22468977/...
raghu_3

1
永远不要添加android:configChanges=everythinYouCanThinkOf|moreThingsYouFoundOnTheInternets清单。相反,学习如何从这里保存和恢复状态,例如:speakerdeck.com/cyrilmottier/...
马辛科津斯基

然后,一旦您了解了疯狂的储蓄状态,并且提出的解决方案在您的特定情况下可以最干净地解决问题,请继续使用它,而不必担心那些不同意的人;-)
rmir​​abelle

5

我发现的最佳解决方案如下:

onSavedInstanceState():总是在活动要关闭时在片段内部调用(将活动从一个移动到另一个或更改配置)。因此,如果我们在同一活动上调用多个片段,那么我们必须使用以下方法:

使用片段的OnDestroyView()并将整个对象保存在该方法内。然后OnActivityCreated():检查object是否为null(因为每次都会调用此方法)。现在,在此处还原对象的状态。

它的作品总是!


4

如果您正在处理像这样在android清单中指定的片段活动中的配置更改

<activity
    android:name=".courses.posts.EditPostActivity"
    android:configChanges="keyboardHidden|orientation"
    android:screenOrientation="unspecified" />

onSaveInstanceState片段的不会被调用,并且该savedInstanceState对象始终为null。


1

我不这样认为 onSaveInstanceState不是一个好的解决方案。它仅用于已被破坏的活动。

从Android 3.0开始,Fragmen由FragmentManager进行管理,条件是:一个活动映射曼尼片段,当在backStack中添加该片段(而不是替换:它将重新创建)时,该视图将被存储。回到最后一个时,它会像以前一样显示。

所以我认为fragmentManger和事务足以应付它。


0

我对包含列表视图的片段使用了混合方法。因为我不替换当前片段而是添加新片段并隐藏当前片段,所以它似乎表现出色。在承载我的片段的活动中,我有以下方法:

public void addFragment(Fragment currentFragment, Fragment targetFragment, String tag) {
    FragmentManager fragmentManager = getSupportFragmentManager();
    FragmentTransaction transaction = fragmentManager.beginTransaction();
    transaction.setCustomAnimations(0,0,0,0);
    transaction.hide(currentFragment);
    // use a fragment tag, so that later on we can find the currently displayed fragment
    transaction.add(R.id.frame_layout, targetFragment, tag)
            .addToBackStack(tag)
            .commit();
}

每当单击/点击列表项时,我都会在片段(包含列表视图)中使用此方法(因此,我需要启动/显示详细信息片段):

FragmentManager fragmentManager = getActivity().getSupportFragmentManager();
SearchFragment currentFragment = (SearchFragment) fragmentManager.findFragmentByTag(getFragmentTags()[0]);
DetailsFragment detailsFragment = DetailsFragment.newInstance("some object containing some details");
((MainActivity) getActivity()).addFragment(currentFragment, detailsFragment, "Details");

getFragmentTags()当我添加一个新片段时,返回一个字符串数组,用作不同片段的标签(请参见上面transaction.add方法中的addFragment方法)。

在包含列表视图的片段中,我通过其onPause()方法执行此操作:

@Override
public void onPause() {
    // keep the list view's state in memory ("save" it) 
    // before adding a new fragment or replacing current fragment with a new one
    ListView lv =  (ListView) getActivity().findViewById(R.id.listView);
    mListViewState = lv.onSaveInstanceState();
    super.onPause();
}

然后在片段的onCreateView中(实际上是在onCreateView中调用的方法中),恢复状态:

// Restore previous state (including selected item index and scroll position)
if(mListViewState != null) {
    Log.d(TAG, "Restoring the listview's state.");
    lv.onRestoreInstanceState(mListViewState);
}

0

最后,在尝试了许多复杂的解决方案之后,我只需要在Fragment(EditText的内容)中保存/恢复单个值,尽管这可能不是最优雅的解决方案,但创建SharedPreference并存储我的状态在那里为我工作


0

在活动中保持不同片段中字段值的简单方法

创建片段实例并添加而不是替换和删除

    FragA  fa= new FragA();
    FragB  fb= new FragB();
    FragC  fc= new FragB();
    fragmentManager = getSupportFragmentManager();
    fragmentTransaction = fragmentManager.beginTransaction();
    fragmentTransaction.add(R.id.fragmnt_container, fa);
    fragmentTransaction.add(R.id.fragmnt_container, fb);
    fragmentTransaction.add(R.id.fragmnt_container, fc);
    fragmentTransaction.show(fa);
    fragmentTransaction.hide(fb);
    fragmentTransaction.hide(fc);
    fragmentTransaction.commit();

然后只显示和隐藏片段,而不是再次添加和删除它们

    fragmentTransaction = fragmentManager.beginTransaction();
    fragmentTransaction.hide(fa);
    fragmentTransaction.show(fb);
    fragmentTransaction.hide(fc);
    fragmentTransaction.commit()

;


-1
private ViewPager viewPager;
viewPager = (ViewPager) findViewById(R.id.pager);
mAdapter = new TabsPagerAdapter(getSupportFragmentManager());
viewPager.setAdapter(mAdapter);
viewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {

        @Override
        public void onPageSelected(int position) {
            // on changing the page
            // make respected tab selected
            actionBar.setSelectedNavigationItem(position);
        }

        @Override
        public void onPageScrolled(int arg0, float arg1, int arg2) {
        }

        @Override
        public void onPageScrollStateChanged(int arg0) {
        }
    });
}

@Override
public void onTabReselected(Tab tab, FragmentTransaction ft) {
}

@Override
public void onTabSelected(Tab tab, FragmentTransaction ft) {
    // on tab selected
    // show respected fragment view
    viewPager.setCurrentItem(tab.getPosition());
}

@Override
public void onTabUnselected(Tab tab, FragmentTransaction ft) {
}

5
请考虑包括有关您的答案的一些信息,而不是简单地发布代码。我们不仅尝试提供“修复”,还帮助人们学习。您应该解释原始代码中的错误,所做的不同操作以及所做更改的原因。
安德鲁·巴伯
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.