问题
I have prepared a very simple test case with 1 activity and 2 fragments for my question:
- MainActivity.java (stores selected item in shared preferences)
- MyMainFragment.java (displays a text view and a "select" button)
- MyListFragment.java (displays a list of items)
Initially MainActivity displays MyMainFragment.
When user touches "Select item..." button, MainActivity displays MyListFragment with:
public void selectedButtonClicked() {
SharedPreferences prefs = getSharedPreferences(PREFS, 0);
int index = prefs.getInt(INDEX, -1);
Fragment fragment = MyListFragment.newInstance(index);
getFragmentManager().beginTransaction()
.addToBackStack(null)
.replace(R.id.root, fragment, LIST)
.commit();
}
After user touches one of the items in the list, MainActivity stores the selected item position in shared preferences and calls popBackStack() to display the MyMainFragment again:
public void itemSelected(int index) {
SharedPreferences prefs = getSharedPreferences(PREFS, 0);
Editor editor = prefs.edit();
editor.putInt(INDEX, index);
editor.commit();
getFragmentManager().popBackStack();
// How to show the index in mSelectedTextView?
}
I.e. the MainActivity in my app does all the "heavy work": it displays 1 of 2 fragments and keeps the user data in shared preferences.
My question is: how to display the selected item (the "Item 07" in the above screenshot) in the mSelectedTextView of the MyMainFragment?
I'm looking for a way to do it properly, without any hacks (for example fragments shouldn't touch shared preferences).
UPDATE: I've tried the suggestion by corsair992 (thanks!)
public void itemSelected(int index) {
SharedPreferences prefs = getSharedPreferences(PREFS, 0);
Editor editor = prefs.edit();
editor.putInt(INDEX, index);
editor.commit();
getFragmentManager().popBackStackImmediate();
MyMainFragment f = (MyMainFragment) getFragmentManager().findFragmentById(R.id.root);
// call a method in MyMainFragment to update mSelectedTextView
f.setText("Selected index: " + index);
}
but it only works, when user touches an item in MyListFragment. It does not however work, when user returns to MyMainFragment by touching Back button.
回答1:
As I see it, you already have a reasonable first part to the answer (suggestion by corsair992 - pass the value from the item selected) the second part is how do you handle the back key.
There are a few ways of doing that (listening to the back stack etc.) but the one I have used most fequently is:
declare a back key listener interface:
public interface BackKeyListener {
boolean onBackPressed();
}
in your main activity add the following:
import BackKeyListener;
void onBackKeyPressed() {
boolean backKeyHandled = false;
Fragment activeFragment = getActiveFragment();
if (activeFragment instanceof BackKeyListener) {
backKeyHandled = ((BackKeyListener) activeFragment).onBackKeyPressed();
}
if (!handled) {
// If should process back key normally, pass to super..
super.onBackKeyPressed();
}
}
Fragment getActiveFragment() {
// I'm assuming you place all your fragments here, if not..
// alter to suit
return getFragmentManager().findFragmentById(R.id.root);
}
in MyListFragment add the following:
import BackKeyListener;
class MyListFragment extends Fragment(orsomething) implements BackKeyListener
public boolean onBackPressed() {
// User pressed back, select default item..
((MainActivity) getActivity()).itemSelected(selectedItem);
// In this case, prevent normal back processing or
// you might get a double backstack pop.
return true;
}
Using an interface on the fragment allows you to "not care" what the active fragment is. If it implements the BackKeyListener, the MainActivity will call it when the back key is pressed. This gives you the option of passing a value back to the activity.
回答2:
Is your first fragment still kept in memory when this happens? I assume so. This means you could use a SharedPreferences.OnSharedPreferenceChangeListener. Just make MyMainFragment implement the interface and then use the registerOnSharedPreferenceChangeListener to monitor for changes. Don't forget to unregister, though!
An even better idea might be to register your activity as the listener and make it pass that data to MyMainFragment when it displays it again.
回答3:
First of all, you can use SharedPrefences inside UI thread, i.e. inside fragments. This common misconception takes root from the time when there was only commit() method. Now we have apply() working asynchronously and get*() using cached data.
Second, the simplest way to solve your problem, without juggling with SDK, will be to notify Activity of items selection in your second fragment (for example, inside your fragment: ((MainActivity)getActivity()).setSelectedItem(itemId);) and get this value on your first fragment's onResume() (for example, ((MainActivity)getActivity()).getSelectedItem();). Of course you should check this value for null in case nothing was selected.
回答4:
Assign a tag to MyMainFragment, suppose it will be MY_MAIN_FRAGMENT_TAG.
Add this code to your itemSelected method:
Fragment myMainFragment = getFragmentManger.findFragmentByTag("MY_MAIN_FRAGMENT_TAG");
if (null != myMainFragment) {
Bundle args = new Bundle();
myMainFragment.getArguments().putInt("selectedItem", index);
getFragmentManager().popBackStack();
}
Then in your MyMainFragment override onViewStateRestored() method:
@Override
public void onViewStateRestored(@Nullable Bundle savedInstanceState) {
super.onViewStateRestored(savedInstanceState);
mSelectedTextView.setText(String.valueOf(getArguments.getInt("selectedItem")));
}
Probably you will need some additional checks if null != getArguments() and set apropriate value to a TextView.
回答5:
A solution is to use setTargetFragment/getTargetFragment approach. With this approach you can use just your Fragments without the Activity and SharedPreferences help. In your MyMainFragment, when the user click the button "Select item..." you can use your callback selectedButtonClicked() as :
public void selectedButtonClicked() {
Fragment fragment = MyListFragment.newInstance(index);
//Set the TargetFragment
fragment.setTargetFragment(MyMainFragment.this, MyListFragment.REQUESTCODE)
getFragmentManager().beginTransaction()
.addToBackStack(null)
.replace(R.id.root, fragment, LIST)
.commit();
}
You have to define the callback onActivityResult of the MainFragment:
public void onActivityResult(int requestCode, int resultCode, Intent data){
if(requestCode == MyListFragment.REQUESTCODE){
f.setText("Selected index: " + intent.getInt("INDEX");
}
}
In the MyListFragment once the user selects an item, you can call your callback itemSelected()
public void itemSelected(int index) {
Intent i = new Intent();
i.putExtraInt("INDEX",index);
getTargetFragment.onActivityResult(MyListFragment.REQUESTCODE,Context.Result_OK,i);
getFragmentManager().popBackStackImmediate();
}
来源:https://stackoverflow.com/questions/28168342/how-to-pass-selected-item-from-listfragment-to-previous-fragment-after-calling-p