CoordinatorLayout を Compose に移行する

CoordinatorLayout は、複雑なレイアウト、オーバーラップ レイアウト、ネストされたレイアウトを可能にする ViewGroup です。このクラスは、内部の View で、ツールバーやボトムシートの展開/折りたたみなど、特定のマテリアル デザインのインタラクションを有効にするコンテナとして使用されます。

Compose では、CoordinatorLayout に最も近いのは Scaffold です。Scaffold は、マテリアル コンポーネントを一般的な画面パターンやインタラクションに組み合わせるためのコンテンツ スロットを提供します。このページでは、CoordinatorLayout の実装を移行して Compose で Scaffold を使用する方法について説明します。

移行手順

CoordinatorLayoutScaffold に移行する手順は次のとおりです。

  1. 次のスニペットでは、CoordinatorLayoutToolBarViewPagerFloatingActionButton を含む AppBarLayout が含まれています。UI 階層から CoordinatorLayout とその子をコメントアウトし、それを置き換える ComposeView を追加します。

    <!--  <androidx.coordinatorlayout.widget.CoordinatorLayout-->
    <!--      android:id="@+id/coordinator_layout"-->
    <!--      android:layout_width="match_parent"-->
    <!--      android:layout_height="match_parent"-->
    <!--      android:fitsSystemWindows="true">-->
    
    <!--    <androidx.compose.ui.platform.ComposeView-->
    <!--        android:id="@+id/compose_view"-->
    <!--        android:layout_width="match_parent"-->
    <!--        android:layout_height="match_parent"-->
    <!--        app:layout_behavior="@string/appbar_scrolling_view_behavior" />-->
    
    <!--    <com.google.android.material.appbar.AppBarLayout-->
    <!--        android:id="@+id/app_bar_layout"-->
    <!--        android:layout_width="match_parent"-->
    <!--        android:layout_height="wrap_content"-->
    <!--        android:fitsSystemWindows="true"-->
    <!--        android:theme="@style/Theme.Sunflower.AppBarOverlay">-->
    
        <!-- AppBarLayout contents here -->
    
    <!--    </com.google.android.material.appbar.AppBarLayout>-->
    
    <!--  </androidx.coordinatorlayout.widget.CoordinatorLayout>-->
    
    <androidx.compose.ui.platform.ComposeView
        android:id="@+id/compose_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
    
  2. フラグメントまたはアクティビティで、追加した ComposeView への参照を取得し、その setContent メソッドを呼び出します。メソッドの本体で、Scaffold をコンテンツとして設定します。

    composeView.setContent {
        Scaffold(Modifier.fillMaxSize()) { contentPadding ->
            // Scaffold contents
            // ...
        }
    }

  3. Scaffold のコンテンツ内に、画面のメイン コンテンツを追加します。上記の XML のメイン コンテンツは ViewPager2 であるため、Compose の同等の HorizontalPager を使用します。Scaffoldcontent ラムダは、コンテンツ ルートに適用される PaddingValues のインスタンスも受け取ります。Modifier.padding を使用して、同じ PaddingValuesHorizontalPager に適用できます。

    composeView.setContent {
        Scaffold(Modifier.fillMaxSize()) { contentPadding ->
            val pagerState = rememberPagerState {
                10
            }
            HorizontalPager(
                state = pagerState,
                modifier = Modifier.padding(contentPadding)
            ) { /* Page contents */ }
        }
    }

  4. Scaffold が提供する他のコンテンツ スロットを使用して、画面要素を追加し、残りの子ビューを移行します。topBar スロットを使用して TopAppBar を追加し、floatingActionButton スロットを使用して FloatingActionButton を指定できます。

    composeView.setContent {
        Scaffold(
            Modifier.fillMaxSize(),
            topBar = {
                TopAppBar(
                    title = {
                        Text("My App")
                    }
                )
            },
            floatingActionButton = {
                FloatingActionButton(
                    onClick = { /* Handle click */ }
                ) {
                    Icon(
                        Icons.Filled.Add,
                        contentDescription = "Add Button"
                    )
                }
            }
        ) { contentPadding ->
            val pagerState = rememberPagerState {
                10
            }
            HorizontalPager(
                state = pagerState,
                modifier = Modifier.padding(contentPadding)
            ) { /* Page contents */ }
        }
    }

一般的なユースケース

ツールバーを折りたたむ、展開する

View システムでは、CoordinatorLayout でツールバーを折りたたんだり展開したりするために、ツールバーのコンテナとして AppBarLayout を使用します。次に、関連付けられたスクロール可能な View(RecyclerViewNestedScrollView など)の XML で layout_behavior を介して Behavior を指定し、スクロール時にツールバーが折りたたまれたり展開されたりする動作を宣言します。

Compose では、TopAppBarScrollBehavior を使用して同様の効果を実現できます。たとえば、上にスクロールするとツールバーが表示されるように、折りたたみ/展開可能なツールバーを実装する手順は次のとおりです。

  1. TopAppBarDefaults.enterAlwaysScrollBehavior() を呼び出して TopAppBarScrollBehavior を作成します。
  2. 作成した TopAppBarScrollBehaviorTopAppBar に指定します。
  3. ScaffoldModifier.nestedScroll を介して NestedScrollConnection を接続し、スクロール可能なコンテンツが上下にスクロールしたときに Scaffold がネストされたスクロール イベントを受け取れるようにします。これにより、コンテンツのスクロールに合わせて、含まれているアプリバーが適切に折りたたまれたり展開されたりします。

    // 1. Create the TopAppBarScrollBehavior
    val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior()
    
    Scaffold(
        topBar = {
            TopAppBar(
                title = {
                    Text("My App")
                },
                // 2. Provide scrollBehavior to TopAppBar
                scrollBehavior = scrollBehavior
            )
        },
        // 3. Connect the scrollBehavior.nestedScrollConnection to the Scaffold
        modifier = Modifier
            .fillMaxSize()
            .nestedScroll(scrollBehavior.nestedScrollConnection)
    ) { contentPadding ->
        /* Contents */
        // ...
    }

折りたたみ/展開スクロール効果をカスタマイズする

enterAlwaysScrollBehavior に複数のパラメータを指定して、折りたたみ/展開のアニメーション効果をカスタマイズできます。TopAppBarDefaults は、コンテンツが一番下までスクロールされた場合にのみアプリバーを拡大する exitUntilCollapsedScrollBehavior などの他の TopAppBarScrollBehavior も提供します。

完全にカスタムのエフェクト(視差効果など)を作成するには、独自の NestedScrollConnection を作成し、コンテンツのスクロールに合わせてツールバーを手動でオフセットすることもできます。コード例については、AOSP のネストされたスクロールのサンプルをご覧ください。

ドロワー

Views では、DrawerLayout をルートビューとして使用して、ナビゲーション ドロワーを実装します。一方、CoordinatorLayoutDrawerLayout の子ビューです。DrawerLayout には、ドロワーにナビゲーション オプションを表示するための NavigationView などの別の子ビューも含まれています。

Compose では、ModalNavigationDrawer コンポーザブルを使用してナビゲーション ドロワーを実装できます。ModalNavigationDrawer は、ドロワー用の drawerContent スロットと、画面のコンテンツ用の content スロットを提供します。

ModalNavigationDrawer(
    drawerContent = {
        ModalDrawerSheet {
            Text("Drawer title", modifier = Modifier.padding(16.dp))
            HorizontalDivider()
            NavigationDrawerItem(
                label = { Text(text = "Drawer Item") },
                selected = false,
                onClick = { /*TODO*/ }
            )
            // ...other drawer items
        }
    }
) {
    Scaffold(Modifier.fillMaxSize()) { contentPadding ->
        // Scaffold content
        // ...
    }
}

詳しくは、ドロワーをご覧ください。

スナックバー

ScaffoldsnackbarHost スロットを提供します。このスロットは、Snackbar を表示する SnackbarHost コンポーザブルを受け取ることができます。

val scope = rememberCoroutineScope()
val snackbarHostState = remember { SnackbarHostState() }
Scaffold(
    snackbarHost = {
        SnackbarHost(hostState = snackbarHostState)
    },
    floatingActionButton = {
        ExtendedFloatingActionButton(
            text = { Text("Show snackbar") },
            icon = { Icon(Icons.Filled.Image, contentDescription = "") },
            onClick = {
                scope.launch {
                    snackbarHostState.showSnackbar("Snackbar")
                }
            }
        )
    }
) { contentPadding ->
    // Screen content
    // ...
}

詳しくは、スナックバーをご覧ください。

詳細

CoordinatorLayout を Compose に移行する方法について詳しくは、以下のリソースをご覧ください。