やりたいこと
Fragmentで作られた画面A,B,Cがあり、A->B->Cと遷移し、
Cでバックキーをタップした際は、Bを飛ばしてAに戻りたい
レイアウト
ベースのレイアウト
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<FrameLayout
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
画面Aのレイアウト
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:background="#00000000"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<Button
android:id="@+id/button"
android:text="Fragment A Button"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</FrameLayout>
画面Bのレイアウト
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:background="#00000000"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<Button
android:id="@+id/button"
android:text="Fragment B Button"
android:layout_gravity="right"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</FrameLayout>
画面Cのレイアウト
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#00000000">
<Button
android:id="@+id/button"
android:text="Fragment C Button"
android:layout_gravity="bottom"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</FrameLayout>
画面遷移処理
画面Aの追加処理
val transaction = supportFragmentManager.beginTransaction()
transaction.add(R.id.container, FragmentA.newInstance())
transaction.commit()
画面Bへの遷移処理
val transaction = fragmentManager.beginTransaction()
transaction.replace(R.id.container, FragmentB.newInstance())
transaction.addToBackStack(null)
transaction.commit()
画面Cへの遷移処理
val transaction = fragmentManager.beginTransaction()
transaction.replace(R.id.container, FragmentC.newInstance())
// Bを飛ばしてAに戻りたいのでbackStack追加しない
//transaction.addToBackStack(null)
transaction.commit()
上記実装で画面A->B->Cと遷移し、バックキーを押すと、なぜか画面Aと画面Cが両方表示されてしまう。
原因
http://extra-vision.blogspot.jp/2016/02/android-fragment-transaction-back-stack.html
上記リンクが参考になった。
addToBackStackはfragmentをスタックに積む命令ではなく、fragment操作のトランザクションを記録する命令であり、バックキータップ時の処理はスタックに積んだfragmentをpopしているわけではなく、記録されたトランザクションの逆の操作を行っている、ということである。
今回のケースでは、
add(fragment A) トランザクション記録しない
replace(fragment B) トランザクション記録する
replace(fragment C) トランザクション記録しない
となっており、replaceは内部的にはremoveとaddを連続で行うのと同じことなので、展開すると、
add(fragment A) トランザクション記録しない
remove(fragment A), add(fragment B) トランザクション記録する
remove(fragment B), add(fragment C) トランザクション記録しない
となる。この状態でバックキーをタップすると、記録されたトランザクションの逆の操作を行うため、
remove(fragment A), add(fragment B) トランザクション記録する
の逆の操作である、
remove(fragment B), add(fragment A)
の操作が実行され、fragmentAが最前面に表示され、fragmentCは残るということになる。
解決策
画面Cでバックキーをタップした際の処理をフックし、自身を殺した上でpopBackStackする
fragmentManager.beginTransaction().remove(this).commit()
fragmentManager.popBackStack()
もしくは、addToBackStack時にタグを指定しておき、タグ指定でpopBackStackする。
transaction.replace(R.id.container, FragmentB.newInstance())
transaction.addToBackStack("fragmentA")
....
....
fragmentManager.popBackStack("fragmentA", FragmentManager.POP_BACK_STACK_INCLUSIVE)