Android 添加到返回堆栈时如何保持片段状态?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/11353075/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How can I maintain fragment state when added to the back stack?
提问by Eric
I've written up a dummy activity that switches between two fragments. When you go from FragmentA to FragmentB, FragmentA gets added to the back stack. However, when I return to FragmentA (by pressing back), a totally new FragmentA is created and the state it was in is lost. I get the feeling I'm after the same thing as thisquestion, but I've included a complete code sample to help root out the issue:
我写了一个在两个片段之间切换的虚拟活动。当您从 FragmentA 转到 FragmentB 时,FragmentA 被添加到后堆栈中。但是,当我返回到 FragmentA(通过按回)时,会创建一个全新的 FragmentA 并且它所处的状态丢失了。我觉得我和这个问题一样,但我已经包含了一个完整的代码示例来帮助解决这个问题:
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);
}
}
}
With some log messages added:
添加了一些日志消息:
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
It's never calling FragmentA.onSaveInstanceState and it creates a new FragmentA when you hit back. However, if I'm on FragmentA and I lock the screen, FragmentA.onSaveInstanceState does get called. So weird...am I wrong in expecting a fragment added to the back stack to not need re-creation? Here's what the docssay:
它从不调用 FragmentA.onSaveInstanceState 并且在您回击时创建一个新的 FragmentA。但是,如果我在 FragmentA 上并锁定屏幕,FragmentA.onSaveInstanceState 确实会被调用。太奇怪了……我期望添加到后台堆栈中的片段不需要重新创建是错误的吗?这是文档所说的:
Whereas, if you do call addToBackStack() when removing a fragment, then the fragment is stopped and will be resumed if the user navigates back.
然而,如果您在删除片段时确实调用了 addToBackStack(),则该片段将停止并在用户返回时恢复。
采纳答案by Jan-Henk
If you return to a fragment from the back stack it does not re-create the fragment but re-uses the same instance and starts with onCreateView()
in the fragment lifecycle, see Fragment lifecycle.
如果您从返回堆栈返回片段,它不会重新创建片段而是重新使用相同的实例并onCreateView()
在片段生命周期中开始,请参阅片段生命周期。
So if you want to store state you should use instance variables and notrely on onSaveInstanceState()
.
所以如果你想存储状态,你应该使用实例变量而不是依赖onSaveInstanceState()
.
回答by Vince Yuan
Comparing to Apple's UINavigationController
and UIViewController
, Google does not do well in Android software architecture. And Android's document about Fragment
does not help much.
与苹果UINavigationController
和UIViewController
谷歌相比,谷歌在安卓软件架构方面做得并不好。而 Android 的文档 aboutFragment
并没有多大帮助。
When you enter FragmentB from FragmentA, the existing FragmentA instance is not destroyed. When you press Back in FragmentB and return to FragmentA, we don't create a new FragmentA instance. The existing FragmentA instance's onCreateView()
will be called.
当您从 FragmentA 进入 FragmentB 时,不会销毁现有的 FragmentA 实例。当您在 FragmentB 中按 Back 并返回到 FragmentA 时,我们不会创建新的 FragmentA 实例。现有的 FragmentA 实例onCreateView()
将被调用。
The key thing is we should not inflate view again in FragmentA's onCreateView()
, because we are using the existing FragmentA's instance. We need to save and reuse the rootView.
关键是我们不应该在 FragmentA 中再次膨胀视图onCreateView()
,因为我们正在使用现有的 FragmentA 实例。我们需要保存和重用 rootView。
The following code works well. It does not only keep fragment state, but also reduces the RAM and CPU load (because we only inflate layout if necessary). I can't believe Google's sample code and document never mention it but always inflate layout.
以下代码运行良好。它不仅保持片段状态,还减少了 RAM 和 CPU 负载(因为我们只在必要时膨胀布局)。我不敢相信谷歌的示例代码和文档从未提及它,但总是夸大布局。
Version 1(Don't use version 1. Use version 2)
版本 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;
}
}
------Update on May 3 2005:-------
------2005 年 5 月 3 日更新:-------
As the comments mentioned, sometimes _rootView.getParent()
is null in onCreateView
, which causes the crash. Version 2 removes _rootView in onDestroyView(), as dell116 suggested. Tested on Android 4.0.3, 4.4.4, 5.1.0.
正如评论中提到的,有时在 中_rootView.getParent()
为空onCreateView
,这会导致崩溃。正如 dell116 建议的那样,版本 2 删除了 onDestroyView() 中的 _rootView。在 Android 4.0.3、4.4.4、5.1.0 上测试。
Version 2
版本 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();
}
}
WARNING!!!
警告!!!
This is a HACK! Though I am using it in my app, you need to test and read comments carefully.
这是一个黑客!虽然我在我的应用程序中使用它,但您需要仔细测试和阅读评论。
回答by kaushal trivedi
I guess there is an alternative way to achieve what you are looking for. I don't say its a complete solution but it served the purpose in my case.
我想有一种替代方法可以实现您的目标。我不是说它是一个完整的解决方案,但它在我的情况下达到了目的。
What I did is instead of replacing the fragment I just added target fragment.
So basically you will be going to use add()
method instead replace()
.
我所做的不是替换我刚刚添加的目标片段的片段。所以基本上你会改用add()
method replace()
。
What else I did. I hide my current fragment and also add it to backstack.
我还做了什么。我隐藏了当前的片段并将其添加到 backstack。
Hence it overlaps new fragment over the current fragment without destroying its view.(check that its onDestroyView()
method is not being called. Plus adding it to backstate
gives me the advantage of resuming the fragment.
因此它在当前片段上重叠了新片段而不破坏它的视图。(检查它的onDestroyView()
方法没有被调用。加上它backstate
给我恢复片段的优势。
Here is the code :
这是代码:
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();
AFAIK System only calls onCreateView()
if the view is destroyed or not created.
But here we have saved the view by not removing it from memory. So it will not create a new view.
AFAIK 系统仅onCreateView()
在视图被销毁或未创建时调用。但是在这里我们通过没有从内存中删除它来保存视图。所以它不会创建一个新的视图。
And when you get back from Destination Fragment it will pop the last FragmentTransaction
removing top fragment which will make the topmost(SourceFragment's) view to appear over the screen.
当您从 Destination Fragment 返回时,它将弹出最后一个FragmentTransaction
移除的顶部片段,这将使最顶层(SourceFragment 的)视图出现在屏幕上。
COMMENT: As I said it is not a complete solution as it doesn't remove the view of Source fragment and hence occupying more memory than usual. But still, serve the purpose. Also, we are using a totally different mechanism of hiding view instead of replacing it which is non traditional.
评论:正如我所说,它不是一个完整的解决方案,因为它不会删除源片段的视图,因此比平时占用更多的内存。但是,服务于目的。此外,我们正在使用一种完全不同的隐藏视图机制,而不是替换它,这是非传统的。
So it's not really for how you maintain the state, but for how you maintain the view.
所以这不是关于你如何维护状态,而是关于你如何维护视图。
回答by Mandeep Singh
I would suggest a very simple solution.
我会建议一个非常简单的解决方案。
Take the View reference variable and set view in OnCreateView. Check if view already exists in this variable, then return same view.
获取 View 引用变量并在 OnCreateView 中设置 view。检查此变量中是否已存在视图,然后返回相同的视图。
private View fragmentView;
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
if (fragmentView != null) {
return fragmentView;
}
View view = inflater.inflate(R.layout.yourfragment, container, false);
fragmentView = view;
return view;
}
回答by Teo Inke
I came across this problem in a Fragment containing a map, which has too many setup details to save/reload. My solution was to basically keep this Fragment active the whole time (similar to what @kaushal mentioned).
我在包含地图的 Fragment 中遇到了这个问题,它有太多的设置细节需要保存/重新加载。我的解决方案是基本上一直保持这个 Fragment 处于活动状态(类似于@kaushal 提到的)。
Say you have current Fragment A and wants to display Fragment B. Summarizing the consequences:
假设您有当前的 Fragment A 并希望显示 Fragment B。总结结果:
- replace() - remove Fragment A and replace it with Fragment B. Fragment A will be recreated once brought to the front again
- add() - (create and) add a Fragment B and it overlap Fragment A, which is still active in the background
- remove() - can be used to remove Fragment B and return to A. Fragment B will be recreated when called later on
- replace() - 移除 Fragment A 并用 Fragment B 替换它。 Fragment A 将在再次出现在前面时重新创建
- add() -(创建并)添加一个片段 B,它与片段 A 重叠,片段 A 在后台仍处于活动状态
- remove() - 可用于移除片段 B 并返回到 A。片段 B 将在稍后调用时重新创建
Hence, if you want to keep both Fragments "saved", just toggle them using hide()/show().
因此,如果您想保持两个 Fragment “已保存”,只需使用 hide()/show() 切换它们。
Pros: easy and simple method to keep multiple Fragments running
Cons: you use a lot more memory to keep all of them running. May run into problems, e.g. displaying many large bitmaps
优点:保持多个片段运行的简单方法
缺点:您使用更多的内存来保持所有片段的运行。可能会遇到问题,例如显示许多大的位图
回答by user2779311
onSaveInstanceState()
is only called if there is configuration change.
onSaveInstanceState()
仅在配置更改时调用。
Since changing from one fragment to another there is no configuration change so no call to onSaveInstanceState()
is there. What state is not being save? Can you specify?
由于从一个片段更改为另一个片段,因此没有配置更改,因此没有调用onSaveInstanceState()
。什么状态没有被保存?可以指定吗?
If you enter some text in EditText it will be saved automatically. Any UI item without any ID is the item whose view state shall not be saved.
如果您在 EditText 中输入一些文本,它将自动保存。任何没有任何 ID 的 UI 项都是不应保存其视图状态的项。
回答by Ling Boo
Here, since onSaveInstanceState
in fragment does not call when you add fragment into backstack. The fragment lifecycle in backstack when restored start onCreateView
and end onDestroyView
while onSaveInstanceState
is called between onDestroyView
and onDestroy
. My solution is create instance variable and init in onCreate
. Sample code:
在这里,因为onSaveInstanceState
当您将片段添加到后台堆栈时,片段中不会调用。在堆栈中的片段的生命周期时恢复开始onCreateView
和结束onDestroyView
,而onSaveInstanceState
被称为之间onDestroyView
和onDestroy
。我的解决方案是在onCreate
. 示例代码:
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();
}
And check it in onActivityCreated
:
并检查它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
}
回答by Hardik Vasani
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();
}
}
回答by Dodi
My problem was similar but I overcame me without keeping the fragment alive. Suppose you have an activity that has 2 fragments - F1 and F2. F1 is started initially and lets say in contains some user info and then upon some condition F2 pops on asking user to fill in additional attribute- their phone number. Next, you want that phone number to pop back to F1 and complete signup but you realize all previous user info is lost and you don't have their previous data. The fragment is recreated from scratch and even if you saved this information in onSaveInstanceState
the bundle comes back null in onActivityCreated
.
我的问题很相似,但我在没有保持片段存活的情况下克服了我。假设您有一个包含 2 个片段的活动 - F1 和 F2。F1 最初启动并让我们说 in 包含一些用户信息,然后在某些条件下 F2 弹出要求用户填写其他属性- 他们的电话号码。接下来,您希望该电话号码返回 F1 并完成注册,但您意识到所有以前的用户信息都丢失了,而且您没有他们以前的数据。该片段是从头开始重新创建的,即使您将此信息保存在onSaveInstanceState
包中也会在onActivityCreated
.
Solution:Save required information as an instance variable in calling activity. Then pass that instance variable into your fragment.
解决方案:在调用活动时将所需信息保存为实例变量。然后将该实例变量传递到您的片段中。
@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
}
}
}
So following on with my example: before I display F2 I save user data in the instance variable using a callback. Then I start F2, user fills in phone number and presses save. I use another callback in activity, collect this information and replace my fragment F1, this time it hasbundle data that I can use.
因此,继续我的示例:在显示 F2 之前,我使用回调将用户数据保存在实例变量中。然后我启动 F2,用户填写电话号码并按保存。我在活动中使用另一个回调,收集这些信息并替换我的片段 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();
}
}
More information about callbacks can be found here: https://developer.android.com/training/basics/fragments/communicating.html
关于回调的更多信息可以在这里找到:https: //developer.android.com/training/basics/fragments/communicating.html
回答by Milad Ahmadi
first: just use add method instead of replace method of FragmentTransaction class then you have to add secondFragment to stack by addToBackStack method
second:on back click you have to call popBackStackImmediate()
第一:只需使用 add 方法而不是 FragmentTransaction 类的 replace 方法然后你必须通过 addToBackStack 方法将 secondFragment 添加到堆栈
第二个:点击后你必须调用 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();
}
};