はじめに
AndroidでFragmentを表示する際のコンテナに、何を利用していますか?
一般的なコンテナとして<FrameLayout>
を、Navigation ComponentのFragmentのコンテナとして<fragment>
を利用できます。
これからはandroidx.fragment 1.2.0から導入されたFragmentContainerView
を利用するべきだと考えています。
この記事ではFragmentContainerView
を調査した結果を記載しています。
FragmentContainerViewの概要
ActivityでFragmentを表示する際にActivity側でFragmentのコンテナとなるViewを作成する必要があります。
コンテナとなり得るViewの1つがFragmentContainerViewです。
xmlで記載する場合の例が下記です。
<androidx.fragment.app.FragmentContainerView
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent" />
FragmentContainerViewはFragmentのコンテナに特化したViewとなるので、FrameLayoutのようにそれ以外の用途で利用することはできません。
FragmentContainerViewとFrameLayoutの比較
Android Dev Summit 2019でFragmentContainerViewとFrameLayoutを比較した発表がありました。Fragments: Past, Present, and Future (Android Dev Summit '19)
この発表で取り上げている遷移アニメーション時のZ orderingのissueについて記載します。
Z orderingのissue
準備
Z orderingのissueを再現するために、ホストとなるActivity、3つのFragment(FirstFragment、SecondFragment、ThirdFragment)、アニメーションのレイアウトを作成します。
ActivityはFragmentを表示する処理のみです。
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding =
DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
val fragmentTransaction = supportFragmentManager
.beginTransaction()
.setCustomAnimations(R.anim.nav_enter, R.anim.nav_exit)
.replace(R.id.container, FirstFragment())
.addToBackStack(null)
fragmentTransaction.commit()
}
}
activity_main.xmlはFragmentのコンテナとなるViewのみとなります。
比較のため、まずはFrameLayoutを置きます。
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".activity.MainActivity">
<FrameLayout
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<!-- <androidx.fragment.app.FragmentContainerView-->
<!-- android:id="@+id/container"-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="match_parent" />-->
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
次にFragmentです。遷移アニメーションを確認するために、ボタンを押下すると次のFragmentに遷移させています。処理が同じであるためFirstFragmentのみ記載します。
class FirstFragment : Fragment() {
@SuppressLint("CheckResult")
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
super.onCreateView(inflater, container, savedInstanceState)
val binding = DataBindingUtil.inflate<FragmentFirstBinding>(
inflater,
R.layout.fragment_first,
container,
false
)
binding.apply {
lifecycleOwner = this@FirstFragment
destinationButton.clicks().subscribe {
val fragmentTransaction = parentFragmentManager
.beginTransaction()
.setCustomAnimations(R.anim.nav_enter, R.anim.nav_exit)
.replace(R.id.container, SecondFragment())
.addToBackStack(null)
fragmentTransaction.commit()
}
}
return binding.root
}
}
次にアニメーションです。上記のsetCustomAnimations
の引数にしていしているEnterアニメーションとExitアニメーションです。
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/decelerate_interpolator"
android:shareInterpolator="true">
<translate
android:duration="400"
android:fromXDelta="100%"
android:fromYDelta="0%"
android:toXDelta="0%"
android:toYDelta="0%" />
</set>
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/decelerate_interpolator"
android:shareInterpolator="true"
android:zAdjustment="bottom">
<translate
android:duration="400"
android:fromXDelta="0%"
android:fromYDelta="0%"
android:toXDelta="-50%"
android:toYDelta="0%" />
</set>
FragmentのコンテナがFrameLayoutの場合の結果
FragmentのコンテナがFragmentContainerViewの場合の結果
activity_main.xmlのFrameLayoutをコメントアウトし、FragmentContainerViewのコメントアウトを外し実行します。
考察
FrameLayoutの場合、まず次のFragmentのEnterアニメーションが始まり、次に現在のFragmentのExitアニメーションが始まるので、このような結果となっています。
これを防ぐために、nav_exit.xml
にandroid:zAdjustment="bottom"
を指定(Z ordering)することにより解決を試みていますが、効いていません。これがZ orderingのissueです。
FragmentContainerViewの場合、まず現在のFragmentのExitアニメーションが始まり、次に次のFragmentのEnterアニメーションが始まるので、このような結果となっています。
おまけ
Navigation ComponentのFragmentのコンテナとして<fragment>
がよく利用されます。
このコンテナをFragmentContainerViewに置き換えることができます。
実際にFragmentContainerViewを利用している例をこちらの記事に記載しています。
Android NavigationとSharedViewModel