Android - 保存/恢复片段状态
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/22505327/
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
Android - save/restore fragment state
提问by Stanete
I have an Activity in which I go through several fragments. In every fragment I have several views (EditText, ListView, Map
, etc).
我有一个活动,我在其中浏览了几个片段。在每个片段中,我都有几个视图(EditText, ListView, Map
等)。
How can I save the instance of the fragment that is shown at that moment? I need it to work when the activity is onPause() --> onResume()
. Also I need it to work when I return from another fragment (pop from backstack).
如何保存当时显示的片段实例?当活动为onPause() --> onResume()
. 当我从另一个片段(从后台弹出)返回时,我也需要它工作。
From the main Activity
I call the first fragment, then from the the fragment I call the next one.
从主要Activity
我调用第一个片段,然后从片段我调用下一个。
Code for my Activity:
我的活动代码:
public class Activity_Main extends FragmentActivity{
public static Fragment_1 fragment_1;
public static Fragment_2 fragment_2;
public static Fragment_3 fragment_3;
public static FragmentManager fragmentManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
fragment_1 = new Fragment_1();
fragment_2 = new Fragment_2();
fragment_3 = new Fragment_3();
fragmentManager = getSupportFragmentManager();
FragmentTransaction transaction_1 = fragmentManager.beginTransaction();
transaction_1.replace(R.id.content_frame, fragment_1);
transaction_1.commit();
}}
Then here is the code for one of my fragments:
然后这是我的片段之一的代码:
public class Fragment_1 extends Fragment {
private EditText title;
private Button go_next;
@Override
public View onCreateView(final LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_1,
container, false);
title = (EditText) rootView.findViewById(R.id.title);
go_next = (Button) rootView.findViewById(R.id.go_next);
image.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
FragmentTransaction transaction_2 = Activity_Main.fragmentManager
.beginTransaction();
transaction_2.replace(R.id.content_frame,
Activity_Main.fragment_2);
transaction_2.addToBackStack(null);
transaction_2.commit();
});
}}
I have searched a lot of information but nothing clear. Can somebody give a clear solution and an example, please ?
我已经搜索了很多信息,但没有明确的内容。有人可以给出一个明确的解决方案和一个例子吗?
回答by Kirill Rakhman
When a fragment is moved to the backstack, it isn't destroyed. All the instance variables remain there. So this is the place to save your data. In onActivityCreated
you check the following conditions:
当一个片段被移动到 backstack 时,它不会被销毁。所有实例变量都保留在那里。所以这是保存数据的地方。在onActivityCreated
您检查以下条件时:
- Is the bundle != null? If yes, that's where the data is saved (probably orientation change).
- Is there data saved in instance variables? If yes, restore your state from them (or maybe do nothing, because everything is as it should be).
- Otherwise your fragment is shown for the first time, create everything anew.
- 捆绑包 != null 吗?如果是,那就是保存数据的地方(可能是方向改变)。
- 是否有数据保存在实例变量中?如果是,请从它们恢复您的状态(或者什么也不做,因为一切都应该如此)。
- 否则您的片段将首次显示,重新创建所有内容。
Edit: Here's an example
编辑:这是一个例子
public class ExampleFragment extends Fragment {
private List<String> myData;
@Override
public void onSaveInstanceState(final Bundle outState) {
super.onSaveInstanceState(outState);
outState.putSerializable("list", (Serializable) myData);
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (savedInstanceState != null) {
//probably orientation change
myData = (List<String>) savedInstanceState.getSerializable("list");
} else {
if (myData != null) {
//returning from backstack, data is fine, do nothing
} else {
//newly created, compute data
myData = computeData();
}
}
}
}
回答by nebyan
Android fragment has some advantages and some disadvantages.
The most disadvantage of the fragment is that when you want to use a fragment you create it ones.
When you use it, onCreateView
of the fragment is called for each time. If you want to keep state of the components in the fragment you must save fragment state and yout must load its state in the next shown.
This make fragment view a bit slow and weird.
Android fragment 有一些优点也有一些缺点。片段的最大缺点是,当您想使用片段时,您可以创建它。当您使用它时,onCreateView
每次都会调用片段的。如果要保持片段中组件的状态,则必须保存片段状态,并且必须在下一个显示中加载其状态。这使得片段视图有点缓慢和奇怪。
I have found a solution and I have used this solution: "Everything is great. Every body can try".
我找到了一个解决方案,我使用了这个解决方案:“一切都很棒。每个人都可以尝试”。
When first time onCreateView
is being run, create view as a global variable. When second time you call this fragment onCreateView
is called again you can return this global view. The fragment component state will be kept.
第一次onCreateView
运行时,将视图创建为全局变量。当您第二次调用此片段时onCreateView
再次调用您可以返回此全局视图。片段组件状态将被保留。
View view;
@Override
public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
setActionBar(null);
if (view != null) {
if ((ViewGroup)view.getParent() != null)
((ViewGroup)view.getParent()).removeView(view);
return view;
}
view = inflater.inflate(R.layout.mylayout, container, false);
}
回答by Sar
Try this :
尝试这个 :
@Override
protected void onPause() {
super.onPause();
if (getSupportFragmentManager().findFragmentByTag("MyFragment") != null)
getSupportFragmentManager().findFragmentByTag("MyFragment").setRetainInstance(true);
}
@Override
protected void onResume() {
super.onResume();
if (getSupportFragmentManager().findFragmentByTag("MyFragment") != null)
getSupportFragmentManager().findFragmentByTag("MyFragment").getRetainInstance();
}
Hope this will help.
希望这会有所帮助。
Also you can write this to activity tag in menifest file :
您也可以将其写入menifest文件中的活动标签:
android:configChanges="orientation|screenSize"
Good luck !!!
祝你好运 !!!
回答by Richard R
As stated here: Why use Fragment#setRetainInstance(boolean)?
如此处所述:为什么使用 Fragment#setRetainInstance(boolean)?
you can also use fragments method setRetainInstance(true)
like this:
你也可以使用这样的片段方法setRetainInstance(true)
:
public class MyFragment extends Fragment {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// keep the fragment and all its data across screen rotation
setRetainInstance(true);
}
}
回答by Raanan
In order to save the Fragment state you need to implement onSaveInstanceState()
:
"Also like an activity, you can retain the state of a fragment using a Bundle, in case the activity's process is killed and you need to restore the fragment state when the activity is recreated. You can save the state during the fragment's onSaveInstanceState()
callback and restore it during either onCreate()
, onCreateView()
, or onActivityCreated()
. For more information about saving state, see the Activities document."
为了保存 Fragment 状态,您需要实现onSaveInstanceState()
:“也像一个活动一样,您可以使用 Bundle 保留一个Fragment 的状态,以防 Activity 的进程被终止,并且您需要在重新创建活动时恢复Fragment 状态你可以在片段的过程中保存状态onSaveInstanceState()
回调过程中,可以还原onCreate()
,onCreateView()
或onActivityCreated()
欲了解更多信息有关保存状态,看到活动的文件。”
http://developer.android.com/guide/components/fragments.html#Lifecycle
http://developer.android.com/guide/components/fragments.html#Lifecycle
回答by Constantin Cerberus
You can get current Fragment from fragmentManager. And if there are non of them in fragment manager you can create Fragment_1
您可以从 fragmentManager 获取当前 Fragment。如果片段管理器中没有它们,您可以创建Fragment_1
public class MainActivity extends FragmentActivity {
public static Fragment_1 fragment_1;
public static Fragment_2 fragment_2;
public static Fragment_3 fragment_3;
public static FragmentManager fragmentManager;
@Override
protected void onCreate(Bundle arg0) {
super.onCreate(arg0);
setContentView(R.layout.main);
fragment_1 = (Fragment_1) fragmentManager.findFragmentByTag("fragment1");
fragment_2 =(Fragment_2) fragmentManager.findFragmentByTag("fragment2");
fragment_3 = (Fragment_3) fragmentManager.findFragmentByTag("fragment3");
if(fragment_1==null && fragment_2==null && fragment_3==null){
fragment_1 = new Fragment_1();
fragmentManager.beginTransaction().replace(R.id.content_frame, fragment_1, "fragment1").commit();
}
}
}
also you can use setRetainInstance
to true what it will do it ignore onDestroy()
method in fragment and your application going to back ground and os kill your application to allocate more memory you will need to save all data you need in onSaveInstanceState
bundle
您也可以使用setRetainInstance
它来实现它会做什么,它会忽略onDestroy()
片段中的方法,并且您的应用程序将返回后台并操作系统终止您的应用程序以分配更多内存您将需要将您需要的所有数据保存在onSaveInstanceState
包中
public class Fragment_1 extends Fragment {
private EditText title;
private Button go_next;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true); //Will ignore onDestroy Method (Nested Fragments no need this if parent have it)
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
onRestoreInstanceStae(savedInstanceState);
return super.onCreateView(inflater, container, savedInstanceState);
}
//Here you can restore saved data in onSaveInstanceState Bundle
private void onRestoreInstanceState(Bundle savedInstanceState){
if(savedInstanceState!=null){
String SomeText = savedInstanceState.getString("title");
}
}
//Here you Save your data
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString("title", "Some Text");
}
}
回答by butch
I'm not quite sure if this question is still bothering you, since it has been several months. But I would like to share how I dealt with this. Here is the source code:
我不太确定这个问题是否仍然困扰着你,因为已经好几个月了。但我想分享我是如何处理这个问题的。这是源代码:
int FLAG = 0;
private View rootView;
private LinearLayout parentView;
/**
* The fragment argument representing the section number for this fragment.
*/
private static final String ARG_SECTION_NUMBER = "section_number";
/**
* Returns a new instance of this fragment for the given section number.
*/
public static Fragment2 newInstance(Bundle bundle) {
Fragment2 fragment = new Fragment2();
Bundle args = bundle;
fragment.setArguments(args);
return fragment;
}
public Fragment2() {
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
Log.e("onCreateView","onCreateView");
if(FLAG!=12321){
rootView = inflater.inflate(R.layout.fragment_create_new_album, container, false);
changeFLAG(12321);
}
parentView=new LinearLayout(getActivity());
parentView.addView(rootView);
return parentView;
}
/* (non-Javadoc)
* @see android.support.v4.app.Fragment#onDestroy()
*/
@Override
public void onDestroy() {
// TODO Auto-generated method stub
super.onDestroy();
Log.e("onDestroy","onDestroy");
}
/* (non-Javadoc)
* @see android.support.v4.app.Fragment#onStart()
*/
@Override
public void onStart() {
// TODO Auto-generated method stub
super.onStart();
Log.e("onstart","onstart");
}
/* (non-Javadoc)
* @see android.support.v4.app.Fragment#onStop()
*/
@Override
public void onStop() {
// TODO Auto-generated method stub
super.onStop();
if(false){
Bundle savedInstance=getArguments();
LinearLayout viewParent;
viewParent= (LinearLayout) rootView.getParent();
viewParent.removeView(rootView);
}
parentView.removeView(rootView);
Log.e("onStop","onstop");
}
@Override
public void onPause() {
super.onPause();
Log.e("onpause","onpause");
}
@Override
public void onResume() {
super.onResume();
Log.e("onResume","onResume");
}
And here is the MainActivity:
这是 MainActivity:
/**
* Fragment managing the behaviors, interactions and presentation of the
* navigation drawer.
*/
private NavigationDrawerFragment mNavigationDrawerFragment;
/**
* Used to store the last screen title. For use in
* {@link #restoreActionBar()}.
*/
public static boolean fragment2InstanceExists=false;
public static Fragment2 fragment2=null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
setContentView(R.layout.activity_main);
mNavigationDrawerFragment = (NavigationDrawerFragment) getSupportFragmentManager()
.findFragmentById(R.id.navigation_drawer);
mTitle = getTitle();
// Set up the drawer.
mNavigationDrawerFragment.setUp(R.id.navigation_drawer,
(DrawerLayout) findViewById(R.id.drawer_layout));
}
@Override
public void onNavigationDrawerItemSelected(int position) {
// update the main content by replacing fragments
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction=fragmentManager.beginTransaction();
switch(position){
case 0:
fragmentTransaction.addToBackStack(null);
fragmentTransaction.replace(R.id.container, Fragment1.newInstance(position+1)).commit();
break;
case 1:
Bundle bundle=new Bundle();
bundle.putInt("source_of_create",CommonMethods.CREATE_FROM_ACTIVITY);
if(!fragment2InstanceExists){
fragment2=Fragment2.newInstance(bundle);
fragment2InstanceExists=true;
}
fragmentTransaction.addToBackStack(null);
fragmentTransaction.replace(R.id.container, fragment2).commit();
break;
case 2:
fragmentTransaction.addToBackStack(null);
fragmentTransaction.replace(R.id.container, FolderExplorerFragment.newInstance(position+1)).commit();
break;
default:
break;
}
}
The parentView
is the keypoint.
Normally, when onCreateView
, we just use return rootView
. But now, I add rootView to parentView
, and then return parentView
. To prevent "The specified child already has a parent. You must call removeView()
on the ..." error, we need to call parentView.removeView(rootView)
, or the method I supplied is useless.
I also would like to share how I found it. Firstly, I set up a boolean to indicate if the instance exists. When the instance exists, the rootView
will not be inflated again. But then, logcat gave the child already has a parent thing, so I decided to use another parent as a intermediate Parent View. That's how it works.
该parentView
是关键点。通常, when onCreateView
,我们只使用 return rootView
。但是现在,我将 rootView 添加到parentView
,然后返回parentView
。为了防止“指定的孩子已经有一个父母。你必须调用removeView()
...”错误,我们需要调用parentView.removeView(rootView)
,否则我提供的方法是无用的。我也想分享我是如何找到它的。首先,我设置了一个布尔值来指示实例是否存在。当实例存在时,rootView
将不会再次膨胀。但是后来,logcat 给孩子已经有了一个父对象,所以我决定使用另一个父对象作为中间父视图。这就是它的工作原理。
Hope it's helpful to you.
希望对你有帮助。