2つのフラグメントを切り替えるダミーアクティビティを作成しました。 FragmentAからFragmentBに移動すると、FragmentAがバックスタックに追加されます。ただし、FragmentAに戻る(押す)と、まったく新しいFragmentAが作成され、その状態は失われます。 this の質問と同じことを追求しているような気がしますが、問題の根絶に役立つ完全なコードサンプルを含めました。
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を呼び出すことはありません。また、ヒットすると新しいFragmentAを作成します。ただし、FragmentAで画面をロックすると、FragmentA.onSaveInstanceStateが呼び出されます。とても奇妙な...バックスタックに追加されたフラグメントが再作成を必要としないと思うのは間違っていますか? docs の意味は次のとおりです。
一方、フラグメントを削除するときにaddToBackStack()を呼び出すと、フラグメントは停止し、ユーザーが戻るとフラグメントが再開されます。
バックスタックからフラグメントに戻ると、フラグメントは再作成されませんが、同じインスタンスを再使用し、フラグメントライフサイクルでonCreateView()
で始まります。 フラグメントライフサイクル を参照してください。
したがって、状態を保存する場合は、インスタンス変数を使用し、notはonSaveInstanceState()
に依存する必要があります。
AppleのUINavigationController
およびUIViewController
と比較すると、GoogleはAndroidソフトウェアアーキテクチャではうまくいきません。そして、Fragment
に関するAndroidのドキュメントはあまり役に立ちません。
FragmentAからFragmentBを入力すると、既存のFragmentAインスタンスは破棄されません。 FragmentBで[戻る]を押してFragmentAに戻ると、新しいFragmentAインスタンスは作成されません。既存のFragmentAインスタンスのonCreateView()
が呼び出されます。
重要なことは、既存のFragmentAのインスタンスを使用しているため、FragmentAのonCreateView()
でビューを再び膨らませないことです。 rootViewを保存して再利用する必要があります。
次のコードはうまく機能します。フラグメントの状態を保持するだけでなく、RAMとCPUの負荷を軽減します(必要な場合にのみレイアウトを拡張するため)。 Googleのサンプルコードとドキュメントでは決して言及されていないと思いますが、 常にレイアウトを拡張する です。
バージョン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日に更新:-------
前述のコメントのように、onCreateView
の_rootView.getParent()
がnullであることがあり、これがクラッシュの原因になります。 Dell116が提案したように、バージョン2はonDestroyView()で_rootViewを削除します。 Android 4.0.3、4.4.4、5.1.0でテスト済み。
バージョン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();
}
}
警告!!!
これはハックです!アプリで使用していますが、コメントを注意深くテストして読む必要があります。
あなたが探しているものを達成する別の方法があると思います。完全な解決策とは言いませんが、私の場合は目的を果たしました。
私がしたことは、フラグメントを置き換える代わりに、ターゲットフラグメントを追加しただけです。したがって、基本的にはadd()
メソッドの代わりにreplace()
メソッドを使用します。
他に何をしました。現在のフラグメントを非表示にし、バックスタックに追加します。
したがって、ビューを破壊することなく、現在のフラグメントの上に新しいフラグメントをオーバーラップします(そのonDestroyView()
メソッドが呼び出されていないことを確認し、さらに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();
AFAIKシステムは、ビューが破棄されるか作成されない場合にのみonCreateView()
を呼び出します。ただし、ここではビューをメモリから削除しないことで保存しているため、新しいビューは作成されません。
また、Destination Fragmentから戻ると、最後のFragmentTransaction
がポップされ、一番上のフラグメントが削除され、一番上の(SourceFragmentの)ビューが画面上に表示されます。
コメント:私が言ったように、それはソースフラグメントのビューを削除せず、したがって通常よりも多くのメモリを占有しないため、完全なソリューションではありませんが、目的を果たします。また、ビューを置き換えるのではなく、ビューを隠すまったく異なるメカニズムを使用していますそれは非伝統的です。
そのため、実際には状態を維持する方法ではなく、ビューを維持する方法が重要です。
マップを含むフラグメントでこの問題に遭遇しました。マップには、保存/再ロードするにはセットアップの詳細が多すぎます。私の解決策は、基本的にこのフラグメントをずっとアクティブにしておくことでした(@kaushalが述べたことと同様)。
現在のフラグメントAがあり、フラグメントBを表示したいとします。結果を要約します。
したがって、両方のフラグメントを「保存」したい場合は、hide()/ show()を使用してそれらを切り替えるだけです。
---(長所:複数のフラグメントを実行し続ける簡単でシンプルな方法
短所:より多くのメモリを使用して、それらすべてを実行し続けます。問題が発生する可能性があります。多くの大きなビットマップを表示する
onSaveInstanceState()
は、構成が変更された場合にのみ呼び出されます。
あるフラグメントから別のフラグメントに変更するため、構成の変更はないため、onSaveInstanceState()
の呼び出しはありません。保存されていない状態は何ですか?指定できますか?
EditTextにテキストを入力すると、自動的に保存されます。 IDのないUIアイテムは、ビューステートが保存されないアイテムです。
ここでは、フラグメントをバックスタックに追加しても、フラグメント内のonSaveInstanceState
は呼び出されません。復元時のバックスタックのフラグメントライフサイクルは、onCreateView
を開始し、onDestroyView
を終了し、onSaveInstanceState
をonDestroyView
とonDestroy
の間で呼び出します。私の解決策は、onCreate
にインスタンス変数と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
}
first:FragmentTransactionクラスのreplaceメソッドの代わりにaddメソッドを使用し、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();
}
};
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の2つのフラグメントを持つアクティビティがあるとします。 F1は最初に開始され、ユーザー情報を含むように言うことができます。そして、ある条件でF2がユーザーに追加属性-電話番号を入力するよう求めます。次に、その電話番号をF1にポップしてサインアップを完了させますが、以前のユーザー情報はすべて失われ、以前のデータはないことに気付きます。フラグメントはゼロから再作成され、この情報をonSaveInstanceState
に保存した場合でも、バンドルはonActivityCreated
でnullに戻ります。
解決策:呼び出しアクティビティで必要な情報をインスタンス変数として保存します。次に、そのインスタンス変数をフラグメントに渡します。
@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を表示する前に、コールバックを使用してインスタンス変数にユーザーデータを保存します。次にF2を起動し、ユーザーが電話番号を入力して[保存]を押します。アクティビティで別のコールバックを使用し、この情報を収集してフラグメントF1を置き換えます。今回はhas使用できるバンドルデータを持っています。
@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();
}
}
コールバックの詳細については、こちらをご覧ください: https://developer.Android.com/training/basics/fragments/communicating.html
Public void replaceFragment(Fragment mFragment, int id, String tag, boolean addToStack) {
FragmentTransaction mTransaction = getSupportFragmentManager().beginTransaction();
mTransaction.replace(id, mFragment);
hideKeyboard();
if (addToStack) {
mTransaction.addToBackStack(tag);
}
mTransaction.commitAllowingStateLoss();
}
replaceFragment(new Splash_Fragment(), R.id.container, null, false);
次のコードを使用してフラグメントを置き換えます。
Fragment fragment = new AddPaymentFragment();
getSupportFragmentManager().beginTransaction().replace(R.id.frame, fragment, "Tag_AddPayment")
.addToBackStack("Tag_AddPayment")
.commit();
アクティビティのonBackPressed()は次のとおりです。
@Override
public void onBackPressed() {
Android.support.v4.app.FragmentManager fm = getSupportFragmentManager();
if (fm.getBackStackEntryCount() > 1) {
fm.popBackStack();
} else {
finish();
}
Log.e("popping BACKSTRACK===> ",""+fm.getBackStackEntryCount());
}
スタック内の古いフラグメントを見つけ、スタック内に存在する場合はそれをロードする完璧なソリューション。
/**
* replace or add fragment to the container
*
* @param fragment pass Android.support.v4.app.Fragment
* @param bundle pass your extra bundle if any
* @param popBackStack if true it will clear back stack
* @param findInStack if true it will load old fragment if found
*/
public void replaceFragment(Fragment fragment, @Nullable Bundle bundle, boolean popBackStack, boolean findInStack) {
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
String tag = fragment.getClass().getName();
Fragment parentFragment;
if (findInStack && fm.findFragmentByTag(tag) != null) {
parentFragment = fm.findFragmentByTag(tag);
} else {
parentFragment = fragment;
}
// if user passes the @bundle in not null, then can be added to the fragment
if (bundle != null)
parentFragment.setArguments(bundle);
else parentFragment.setArguments(null);
// this is for the very first fragment not to be added into the back stack.
if (popBackStack) {
fm.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
} else {
ft.addToBackStack(parentFragment.getClass().getName() + "");
}
ft.replace(R.id.contenedor_principal, parentFragment, tag);
ft.commit();
fm.executePendingTransactions();
}
のように使用します
Fragment f = new YourFragment();
replaceFragment(f, null, boolean true, true);