android fragment- How to save states of views in a fragment when another fragment is pushed on top of it
In android, a fragment (say FragA
) gets added to the backstack and another fragment (say FragB
) comes to the top. Now on hitting back FragA
comes to the top and the onCreateView()
is called. Now I had FragA
in a particular state before FragB
got pushed on top of it.
My 开发者_JAVA百科Question is how can I restore FragA
to its previous state ? Is there a way to save state (like say in a Bundle) and if so then which method should I override ?
In fragment guide FragmentList example you can find:
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt("curChoice", mCurCheckPosition);
}
Which you can use later like this:
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (savedInstanceState != null) {
// Restore last state for checked position.
mCurCheckPosition = savedInstanceState.getInt("curChoice", 0);
}
}
I'm a beginner in Fragments but it seems like solution of your problem ;) OnActivityCreated is invoked after fragment returns from back stack.
Fragment's onSaveInstanceState(Bundle outState)
will never be called unless fragment's activity call it on itself and attached fragments. Thus this method won't be called until something (typically rotation) force activity to SaveInstanceState
and restore it later.
But if you have only one activity and large set of fragments inside it (with intensive usage of replace
) and application runs only in one orientation activity's onSaveInstanceState(Bundle outState)
may not be called for a long time.
I know three possible workarounds.
The first:
use fragment's arguments to hold important data:
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);
}
}
The second but less pedantic way - hold variables in singletons
The third - don't replace()
fragments but add()
/show()
/hide()
them instead.
Just inflate your View for once.
Exemple follows:
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;
}
}
Just notice that if you work with Fragments using ViewPager, it's pretty easy. You only need to call this method: setOffscreenPageLimit()
.
Accordign to the docs:
Set the number of pages that should be retained to either side of the current page in the view hierarchy in an idle state. Pages beyond this limit will be recreated from the adapter when needed.
Similar issue here
I worked with an issue very similar to this. Since I knew I would frequently be returning back to a previous fragment, I checked to see whether the fragment .isAdded()
was true, and if so, rather than doing a transaction.replace()
I just do a transaction.show()
. This keeps the fragment from being recreated if it's already on the stack - no state saving needed.
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();
Another thing to keep in mind is that while this preserves the natural order for fragments themselves, you might still need to handle the activity itself being destroyed and recreated on orientation (config) change. To get around this in AndroidManifest.xml for your node:
android:configChanges="orientation|screenSize"
In Android 3.0 and higher, the screenSize
is apparently required.
Good luck
if you are handling the config changes in your fragment activity specified in android manifest like this
<activity
android:name=".courses.posts.EditPostActivity"
android:configChanges="keyboardHidden|orientation"
android:screenOrientation="unspecified" />
then the onSaveInstanceState
of the fragment will not be invoked and the savedInstanceState
object will always be null.
The Best Solution I found is below:
onSavedInstanceState(): always called inside fragment when activity is going to shut down(Move activity from one to another or config changes). So if we are calling multiple fragments on same activity then We have to use the following approach:
Use OnDestroyView() of the fragment and save the whole object inside that method. Then OnActivityCreated(): Check that if object is null or not(Because this method calls every time). Now restore state of an object here.
Its works always!
i donot think onSaveInstanceState
is a good solution. it just use for activity which had been destoryed.
From android 3.0 the Fragmen has been manager by FragmentManager, the condition is: one activity mapping manny fragments, when the fragment is added(not replace: it will recreated) in backStack, the view will be destored. when back to the last one, it will display as before.
So i think the fragmentManger and transaction is good enough to handle it.
I used a hybrid approach for fragments containing a list view. It seems to be performant since I don't replace the current fragment but rather add the new fragment and hide the current one. I have the following method in the activity that hosts my fragments:
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();
}
I use this method in my fragment (containing the list view) whenever a list item is clicked/tapped (and thus I need to launch/display the details fragment):
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()
returns an array of strings that I use as tags for different fragments when I add a new fragment (see transaction.add
method in addFragment
method above).
In the fragment containing the list view, I do this in its onPause() method:
@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();
}
Then in onCreateView of the fragment (actually in a method that is invoked in onCreateView), I restore the state:
// Restore previous state (including selected item index and scroll position)
if(mListViewState != null) {
Log.d(TAG, "Restoring the listview's state.");
lv.onRestoreInstanceState(mListViewState);
}
In the end after trying many of these complicated solutions as I only needed to save/restore a single value in my Fragment (the content of an EditText), and although it might not be the most elegant solution, creating a SharedPreference and storing my state there worked for me
A simple way of keeping the values of fields in different fragments in an activity
Create the Instances of fragments and add instead of replace and remove
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();
Then just show and hide the fragments instead of adding and removing those again
fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.hide(fa);
fragmentTransaction.show(fb);
fragmentTransaction.hide(fc);
fragmentTransaction.commit()
;
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) {
}
精彩评论