Android FragmentContainerViewとは

# はじめに
AndroidでFragmentを表示する際のコンテナに、何を利用していますか？
一般的なコンテナとして`<FrameLayout>`を、Navigation ComponentのFragmentのコンテナとして`<fragment>`を利用できます。

これからはandroidx.fragment 1.2.0から導入された`FragmentContainerView `を利用するべきだと考えています。
この記事では`FragmentContainerView`を調査した結果を記載しています。

# FragmentContainerViewの概要
ActivityでFragmentを表示する際にActivity側でFragmentのコンテナとなるViewを作成する必要があります。
コンテナとなり得るViewの1つがFragmentContainerViewです。
xmlで記載する場合の例が下記です。

```xml:example.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)](https://www.youtube.com/watch?v=RS1IACnZLy4&feature=youtu.be&t=548)
この発表で取り上げている遷移アニメーション時のZ orderingのissueについて記載します。

## Z orderingのissue
### 準備
Z orderingのissueを再現するために、ホストとなるActivity、3つのFragment(FirstFragment、SecondFragment、ThirdFragment)、アニメーションのレイアウトを作成します。
ActivityはFragmentを表示する処理のみです。

```Kotlin:MainActivity.kt
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:activity_main.xml
<?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のみ記載します。

```Kotlin: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:nav_enter.xml
<?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:nav_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"
    android:zAdjustment="bottom">
    <translate
        android:duration="400"
        android:fromXDelta="0%"
        android:fromYDelta="0%"
        android:toXDelta="-50%"
        android:toYDelta="0%" />
</set>
```

### FragmentのコンテナがFrameLayoutの場合の結果
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/356260/5c9d6d60-f159-dce2-dd42-ccd24d843956.gif" width=30%>

### FragmentのコンテナがFragmentContainerViewの場合の結果
activity_main.xmlのFrameLayoutをコメントアウトし、FragmentContainerViewのコメントアウトを外し実行します。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/356260/8b2b14d7-7863-8bf1-fb0b-a7b692894327.gif" width=30%>

### 考察
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](https://qiita.com/yoppie_x/items/e0ac35adf87881797786)