添加到后端堆栈时,如何维护碎片状态?
我写了一个虚拟活动,在两个片段之间切换。 当你从FragmentA到FragmentB时,FragmentA被添加到后端堆栈。 然而,当我回到FragmentA(通过按回),一个全新的FragmentA被创build,并且它所处的状态已经丢失。 我觉得我跟这个问题是一样的,但是我已经包含了一个完整的代码示例来帮助解决这个问题:
public class FooActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); final FragmentTransaction transaction = getFragmentManager().beginTransaction(); transaction.replace(android.R.id.content, new FragmentA()); transaction.commit(); } public void nextFragment() { final FragmentTransaction transaction = getFragmentManager().beginTransaction(); transaction.replace(android.R.id.content, new FragmentB()); transaction.addToBackStack(null); transaction.commit(); } public static class FragmentA extends Fragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { final View main = inflater.inflate(R.layout.main, container, false); main.findViewById(R.id.next_fragment_button).setOnClickListener(new View.OnClickListener() { public void onClick(View v) { ((FooActivity) getActivity()).nextFragment(); } }); return main; } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); // Save some state! } } public static class FragmentB extends Fragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { return inflater.inflate(R.layout.b, container, false); } } }
添加了一些日志消息:
07-05 14:28:59.722 D/OMG ( 1260): FooActivity.onCreate 07-05 14:28:59.742 D/OMG ( 1260): FragmentA.onCreateView 07-05 14:28:59.742 D/OMG ( 1260): FooActivity.onResume <Tap Button on FragmentA> 07-05 14:29:12.842 D/OMG ( 1260): FooActivity.nextFragment 07-05 14:29:12.852 D/OMG ( 1260): FragmentB.onCreateView <Tap 'Back'> 07-05 14:29:16.792 D/OMG ( 1260): FragmentA.onCreateView
它永远不会调用FragmentA.onSaveInstanceState,当你回击的时候它会创build一个新的FragmentA。 但是,如果我在FragmentA上,并locking屏幕,FragmentA.onSaveInstanceState确实被调用。 太奇怪了……我错在期待一个片段添加到后端堆栈,不需要重新创build? 以下是文档所说的内容:
而如果在删除片段时确实调用了addToBackStack(),则片段将停止,并在用户返回时恢复。
如果从后端堆栈返回片段,则不会重新创build片段,而是重新使用同一个实例,并在片段生命周期中以onCreateView()
开头,请参阅片段生命周期 。
所以,如果你想存储状态,你应该使用实例variables,而不是依靠onSaveInstanceState()
。
与苹果的UINavigationController
和UIViewController
,Google在Android软件架构上做得不好。 而关于Fragment
Android文件并没有多大帮助。
从FragmentAinputFragmentB时,现有的FragmentA实例不会被销毁。 当按FragmentB中的Back并返回到FragmentA时,我们不会创build一个新的FragmentA实例。 现有的FragmentA实例的onCreateView()
将被调用。
关键是我们不应该在FragmentA的onCreateView()
再次膨胀视图,因为我们正在使用现有的FragmentA的实例。 我们需要保存和重用rootView。
以下代码运行良好。 它不仅保持片段状态,而且还减less了RAM和CPU负载(因为我们只在必要时扩充布局)。 我不能相信谷歌的示例代码和文档从来没有提到它,但总是夸大布局 。
版本1(不要使用版本1使用版本2)
public class FragmentA extends Fragment { View _rootView; public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { if (_rootView == null) { // Inflate the layout for this fragment _rootView = inflater.inflate(R.layout.fragment_a, container, false); // Find and setup subviews _listView = (ListView)_rootView.findViewById(R.id.listView); ... } else { // Do not inflate the layout again. // The returned View of onCreateView will be added into the fragment. // However it is not allowed to be added twice even if the parent is same. // So we must remove _rootView from the existing parent view group // (it will be added back). ((ViewGroup)_rootView.getParent()).removeView(_rootView); } return _rootView; } }
—— 2005年5月3日更新:——-
正如所提到的注释,有时_rootView.getParent()
在onCreateView
,为空,导致崩溃。 版本2删除onDestroyView()中的_rootView,如dell116build议。 在Android 4.0.3,4.4.4,5.1.0上testing
版本2
public class FragmentA extends Fragment { View _rootView; public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { if (_rootView == null) { // Inflate the layout for this fragment _rootView = inflater.inflate(R.layout.fragment_a, container, false); // Find and setup subviews _listView = (ListView)_rootView.findViewById(R.id.listView); ... } else { // Do not inflate the layout again. // The returned View of onCreateView will be added into the fragment. // However it is not allowed to be added twice even if the parent is same. // So we must remove _rootView from the existing parent view group // in onDestroyView() (it will be added back). } return _rootView; } @Override public void onDestroyView() { if (_rootView.getParent() != null) { ((ViewGroup)_rootView.getParent()).removeView(_rootView); } super.onDestroyView(); } }
警告!!!
这是一个HACK! 虽然我在我的应用程序中使用它,但您需要仔细testing和阅读注释。
我想有一个替代方法来实现你正在寻找的东西。 我不是说它是一个完整的解决scheme,但它在我的情况下是为了达到目的。
我所做的是不是replace我刚添加目标片段的片段。 所以基本上你会使用add()
方法而不是replace()
。
我还做了什么。 我隐藏我的当前片段,并将其添加到背层。
因此它在当前片段上重叠新的片段而不会破坏它的视图(检查它的onDestroyView()
方法没有被调用.Plus将它添加到backstate
给了我恢复片段的优点。
这里是代码:
Fragment fragment=new DestinationFragment(); FragmentManager fragmentManager = getFragmentManager(); android.app.FragmentTransaction ft=fragmentManager.beginTransaction(); ft.add(R.id.content_frame, fragment); ft.hide(SourceFragment.this); ft.addToBackStack(SourceFragment.class.getName()); ft.commit();
如果视图被销毁或者没有被创build,AFAIK系统只调用onCreateView()
。 但是在这里,我们通过不把它从内存中移除来保存视图,所以它不会创build新的视图。
而当你从Destination Fragment回来时,它会popup最后一个FragmentTransaction
移除顶部的片段,这将使最上面的(SourceFragment)视图出现在屏幕上。
评论:正如我所说,它不是一个完整的解决scheme,因为它不会消除源片段的视图,因此比平常占用更多的内存。但仍然服务于此目的。此外,我们正在使用一个完全不同的机制隐藏视图,而不是replace这是非传统的。
所以它不是真的如何维持国家,而是你如何维持这个观点。
我在包含地图的Fragment中遇到了这个问题,该地图有太多的设置细节来保存/重新加载。 我的解决办法是基本保持这个片段活跃(类似于@kaushal所提到的)。
假设你有现在的片段A并且想要显示片段B.总结结果:
- replace() – 删除Fragment A并用Fragment Breplace它。Fragment A将会被重新创build一次
- 添加() – (创build和)添加片段B,它重叠片段A,它仍然在后台活动
- remove() – 可以用来删除Fragment B并返回A.当稍后调用时,将会重新创buildFragment B.
因此,如果你想保留这两个碎片“保存”,只需使用hide()/ show()切换它们。
优点 :保持多个碎片运行的简单方法
缺点 :你使用更多的内存来保持所有的内存运行。 可能会遇到问题,例如显示许多大的位图
onSaveInstanceState()
仅在configuration更改时才会调用。
由于从一个片段更改为另一个没有configuration更改,所以没有调用onSaveInstanceState()
在那里。 什么状态不被保存? 你能指定吗?
如果您在EditText中input一些文字,它将被自动保存。 任何没有ID的UI项目都是不能保存视图状态的项目。
在这里,因为onSaveInstanceState
中的片段不会在您将片段添加到backstack时调用。 在恢复后,堆栈中的片段生命周期开始onCreateView
并结束onDestroyView
onSaveInstanceState
在onDestroyView
和onDestroy
之间调用。 我的解决scheme是在onCreate
创build实例variables和init。 示例代码:
private boolean isDataLoading = true; private ArrayList<String> listData; public void onCreate(Bundle savedInstanceState){ super.onCreate(savedInstanceState); isDataLoading = false; // init list at once when create fragment listData = new ArrayList(); }
并检查onActivityCreated
:
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); if(isDataLoading){ fetchData(); }else{ //get saved instance variable listData() } } private void fetchData(){ // do fetch data into listData }
getSupportFragmentManager().addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() { @Override public void onBackStackChanged() { if (getSupportFragmentManager().getBackStackEntryCount() == 0) { //setToolbarTitle("Main Activity"); } else { Log.e("fragment_replace11111", "replace"); } } }); YourActivity.java @Override public void onBackPressed() { Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.Fragment_content); if (fragment instanceof YourFragmentName) { fragmentReplace(new HomeFragment(),"Home Fragment"); txt_toolbar_title.setText("Your Fragment"); } else{ super.onBackPressed(); } } public void fragmentReplace(Fragment fragment, String fragment_name) { try { fragmentTransaction = fragmentManager.beginTransaction(); fragmentTransaction.replace(R.id.Fragment_content, fragment, fragment_name); fragmentTransaction.setCustomAnimations(R.anim.enter_from_right, R.anim.exit_to_left, R.anim.enter_from_left, R.anim.exit_to_right); fragmentTransaction.addToBackStack(fragment_name); fragmentTransaction.commitAllowingStateLoss(); } catch (Exception e) { e.printStackTrace(); } }
我的问题是相似的,但我没有保持片段活着,我克服了我。 假设你有一个活动,有两个片段 – F1和F2。 F1是最初启动,并让说包含一些用户信息,然后在某些情况下F2popup要求用户填写额外的属性 – 他们的电话号码。 接下来,您需要将该电话号码回弹到F1并完成注册,但您意识到之前的所有用户信息都已丢失,并且您没有以前的数据。 该片段是从头开始重新创build的,即使您将此信息保存在onSaveInstanceState
该包在onActivityCreated
也会返回null。
解决scheme:将所需信息作为实例variables保存在调用活动中。 然后将该实例variables传递给你的片段。
@Override public void onActivityCreated(@Nullable Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); Bundle args = getArguments(); // this will be null the first time F1 is created. // it will be populated once you replace fragment and provide bundle data if (args != null) { if (args.get("your_info") != null) { // do what you want with restored information } } }
因此,继续我的示例:在显示F2之前,我使用callback将实例variables中的用户数据保存起来。 然后我开始F2,用户填写电话号码并按保存。 我在活动中使用另一个callback,收集这些信息并replace我的碎片F1,这次它有我可以使用的捆绑数据。
@Override public void onPhoneAdded(String phone) { //replace fragment F1 f1 = new F1 (); Bundle args = new Bundle(); yourInfo.setPhone(phone); args.putSerializable("you_info", yourInfo); f1.setArguments(args); getFragmentManager().beginTransaction() .replace(R.id.fragmentContainer, f1).addToBackStack(null).commit(); } }
有关callback的更多信息可以在这里find: https : //developer.android.com/training/basics/fragments/communicating.html
第一 :只是使用add方法而不是FragmentTransaction类的replace方法,那么你必须添加secondFragment通过addToBackStack方法堆栈
第二 :在后面点击你必须调用popBackStackImmediate()
Fragment sourceFragment = new SourceFragment (); final Fragment secondFragment = new SecondFragment(); final FragmentTransaction ft = getChildFragmentManager().beginTransaction(); ft.add(R.id.child_fragment_container, secondFragment ); ft.hide(sourceFragment ); ft.addToBackStack(NewsShow.class.getName()); ft.commit(); ((SecondFragment)secondFragment).backFragmentInstanceClick = new SecondFragment.backFragmentNewsResult() { @Override public void backFragmentNewsResult() { getChildFragmentManager().popBackStackImmediate(); } };