Navigation railってなんだっけ？

# はじめに
先日のGoogle I/Oで[Compose マテリアル 3 アダプティブライブラリ](https://developer.android.com/jetpack/androidx/releases/compose-material3-adaptive)が発表されましたね。

今回のGoogle I/Oはアダプティブ関連の話題が多めだったり、[Car ready mobile appsプログラム](https://developer.android.com/training/cars/car-ready-mobile-apps)で大画面対応済みモバイルアプリの自動車向けAndroidプラットフォーム進出が推進されていたりで、GoogleのAdaptive対応意向を強めに感じました。

アダプティブライブラリで追加されたNavigationSuiteScaffoldは、
「モバイルの場合はNavigationBar、タブレットの場合はNavigationRail」など、画面サイズに応じて最適なNavigationを切り替えてくれるComposableのようですが、

**「Navigation railってどんな特徴があるんだっけ？」**
**「そもそもなんでタブレットだとNavigation railなんだっけ？」**

のような疑問が沸いたので、今回はこれについて調べてみました。

# 1. Navigation railとは？
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/145704/166ce994-d739-8e26-a7b8-f197a9b4861f.png)


https://m3.material.io/components/navigation-rail/overview

Navigation railはMaterial Designで提供されているMaterialComponentsの一つです。
MaterialDesign3のサイトを読み込んでいくと以下のような記述がありました。

* * *
- *>UIを通してユーザの移動を助ける『Navigation components』に属する*
    - Navigation drawer/Bottom app bar/Tab/Searchなどの仲間になる
- *> medium以上のwindow sizeでユーザにUIビューを切り替えさせるために使う*
    - compactなwindowではNavigation bar（旧Bottom navigation）を使う
    - large/extra-largeではNavigation railでもNavigation drawerでも良い
- *> 日本語や英語の場合は左サイドに表示される*
    - （左から右に書くアラビア語などは右側に表示される）
- *> 3-7のdestinationsを含む*
- *> Navigation rail内のdestinationsの配置を調整したり、OptionでFloating action buttonやMenuを入れるなどのカスタマイズが可能*
* * *

モバイルアプリでよく使うNavigation bar（旧Bottom navigation）のdestinations制限は3-5個なので、より多くのdestinationsを入れられるみたいです。

「medium以上のwindow size」は下記の表に整理されている通り、width600dp以上のものが目安になります。
縦向きのモバイル端末以外全てがターゲットになり得そうですね。

![スクリーンショット 2024-06-26 22.23.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/145704/4235f115-7ff4-d6b0-3844-7af4d92c441e.png)


https://m3.material.io/foundations/layout/applying-layout/window-size-classes

ちなみに、普段あまり見かけないlarge/extra-largeという規格が時々出てくるなと思ったのですが、これらはMaterial Designの定義には存在するが、Androidのwindow classesではサポートしていないもののようです。

「yet」と書いているので、もしかしたら今後サポートする予定があるのかもしれませんね。

# 2. Navigation drawerとの使い分け

> large/extra-largeではNavigation RailでもNavigation drawerでも良い
> 

> Navigation rail内のdestinationsの配置を調整したり、OptionでFloating action buttonやMenuを入れるなどのカスタマイズが可能
> 

個人的に上記の記述から「**じゃあNavigation drawerとの使い分けはどうすればいいの？**」が気になったのですが、これはAndroidの公式ドキュメントに書いてありました。

![image (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/145704/9c939a66-88f5-3bb5-77e3-8df2b892e972.png)

https://developer.android.com/guide/topics/large-screens/navigation-for-responsive-uis?hl=ja

単純に、Navigation bar（旧Bottom navigation）はdestinationが7個までだからそれより多いとNavigation drawerということみたいです。

# 3. なぜタブレットでNavigation railが使われるのか

これがMaterial DesignのサイトやGoogle I/Oのセッション「[Designing adaptive apps](https://www.youtube.com/watch?v=G-76xfNlMxc&t=318s)」などでは意外と説明されていませんでした。
（恐らく「ユーザの期待、UIの一貫性、人間工学的なニーズが考慮されている」といった説明で、詳細については語られていなかったと思います。）

気になったので少し調べてみると、Googleの情報を専門に扱うニュースサイト「9to5Google」の記事で以下のように語られていました。

> This puts controls where your hand rests when holding a large screen device, similar to how your thumb has easy access to the bottom edge of a phone, rather than needing a second hand to navigate
> 

https://9to5google.com/2023/08/27/google-apps-navigation-rail/

モバイルだと画面下部のNavigation Barが親指で操作しやすいのと同じように、タブレットを持った時にサイドに両手親指がくる前提で、親指で操作しやすいでしょ？ってことみたいですね。

▼つまりこういうイメージですね
![つまりこういうイメージですね](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/145704/d4b4367f-3735-d809-448f-6bf5a232170c.jpeg)

これは後から気づいたのですが、「[Building UI with the Material 3 adaptive library](https://www.youtube.com/watch?v=xPUZENis4gc)」のセッションの一番最後に上記を示唆するような一幕もあったので、もしかしたらGoogle的にはすでに常識でしょ？って感じなのかもしれないです。

![image (2).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/145704/4cff802f-551f-a9a5-2d86-b86fec4e3994.png)

この記事で面白いなあと思ったのは、続きの部分で、例えタブレットでも**Navigation railを使わない例外**がいくつか存在するとのことでした。

例えば、グーグルニュースなどコンテンツの多いアプリではスペースの節約のためにあえてモバイル同様にNavigation barを使っているようです。
基本的には一貫性のために同デバイス内では縦でも横でも同じNavigationに揃えることが多いようですが、確かに、縦画面でNavigation railを使うとスペースが無駄になってしまう面はありそうですね。

> Using a rail in both orientations would be the consistent thing to do, but this approach does allow the phone layout to be reused. An argument about how this conserves development resources by reusing the phone layout can be made, while another is that nav rail takes more space than a bottom bar in these configurations and is actually better for the end user experience in content-heavy apps.
> 

モバイル版とのUIの一貫性を重視して、逆にNavigation barに統一してしまうケースもあるようです。

> While the vast majority have seen updates, there are still some first-party applications that use a bottom bar. The biggest holdouts are the YouTube family of apps, including Music and TV. Sticking to a bottom bar maintains phone-tablet consistency and is presumably easier for development. However, it does make YouTube stick out, especially when it’s otherwise optimized with two column layouts.
> 

この辺りのUIをこだわりたい場合は、単純に画面サイズによってNavigationを出し分けるデフォルト設定のままNavigationSuiteScaffoldを使うのではなく、カスタマイズが必要かもしれません。

# 4. せっかくなのでNavigationSuiteScaffoldを動かしてみる

Navigation railが何者か分かってスッキリしたので、NavigationSuiteScaffoldを手元で動かしてみました。

NavigationSuiteScaffoldではWindow Size ClassによってWidthとHeightの組み合わせに基づき最適なNavigationを判定します。Medium以上の場合は前述したNavigation railに切り替えてくれます。

こちらのAndroid公式ドキュメントを参考に実装していきます。

https://developer.android.com/develop/ui/compose/layouts/adaptive/build-adaptive-navigation?hl=ja&_gl=1

まずは依存関係を追加します（適宜最適なバージョンに変更してください）

```groovy
implementation("androidx.compose.material3:material3-adaptive-navigation-suite-android:1.3.0-beta03")
```

destinationを管理するStateを作成しておきます。

```kotlin
var currentDestination by rememberSaveable { mutableStateOf(AppDestinations.HOME) }
```

あとはNavigationSuiteScaffoldコンポーザブルを追加し、必要な引数を渡してあげるだけです。

必須の引数は、
navigationのアイテムを定義するnavigationSuiteItems引数と、
画面部分に使用するComposableを定義するcontent引数のみでした。

navigationSuiteItemsは、ラムダブロック内でNavigationのDestinationとなるItemを一つずつ定義していきます。

（NavigationBarの中でNavigationBarItemを定義していくのに似ていますね）

```kotlin
AdaptiveSampleTheme {
    NavigationSuiteScaffold(
        navigationSuiteItems = {
            AppDestinations.entries.forEach {
                this.item(
                    icon = {
                        Icon(it.icon, contentDescription = null)
                    },
                    label = { Text(text = it.name) },
                    selected = it == currentDestination,
                    onClick = { currentDestination = it }
                )
            }
        }
    ) {
        DummyPane(text = "It is ${currentDestination.name} pane.")
    }
}

enum class AppDestinations(
    val icon: ImageVector
) {
    HOME(Icons.Default.Home),
    FAVORITES(Icons.Default.Favorite),
    SHOPPING(Icons.Default.ShoppingCart),
    PROFILE(Icons.Default.AccountBox),
}
```

上記のアプリをビルドして表示を確認してみます。

ExperimentalのResizableエミュレーターを使うことで実行中のアプリの画面サイズの変化を確認することができます。

ResizableエミュレーターでPhone/Foldable/Tabletを切り替えていくと、FoldableとTabletではNavigationがNavigationRailになっているのがわかります。

![NavigationSuiteScaffold.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/145704/f876d1e1-c590-0260-3ff5-cfc95b357990.gif)


# 5. Navigationのタイプをカスタマイズする

デフォルトの表示はかなり簡単にできることがわかりました。

では、以下のように縦状態のタブレットを含むMediumで例外的にNavigationRailを使いたくない場合、どうすれば良いでしょうか。

> 例えば、グーグルニュースなどコンテンツの多いアプリではスペースの節約のためにあえてモバイル同様NavigationBarを使っているようです。

NavigationScaffoldはoptionでlayoutType引数にNavigationSuiteTypeを渡すことで、使用するNavigationを指定することができます。
極端な話、ここに固定値でNavigationRailを入れておけば、モバイル時にもNavigationRailを表示させることができてしまうわけですね。

先ほどの例は、例えば以下のような形で指定することができます。

```kotlin
val adaptiveInfo = currentWindowAdaptiveInfo()

val customNavSuiteType: NavigationSuiteType = with(adaptiveInfo) {
  // Window sizeがmediumの場合は例外的にNavigationBarを名指しする
	if (windowSizeClass.windowWidthSizeClass == WindowWidthSizeClass.MEDIUM) {
		NavigationSuiteType.NavigationBar
	// その他のケースはデフォルトにしシステムに任せる
	} else {
		NavigationSuiteScaffoldDefaults.calculateFromAdaptiveInfo(adaptiveInfo)
	}
}
```

あとは定義したcustomNavSuiteTypeをlayoutTypeに渡すだけです

```kotlin
  NavigationSuiteScaffold(
      modifier = Modifier.padding(it),
      layoutType = customNavSuiteType,
      //・・・
```

タブレットの縦画面でもちゃんとNavigation barが表示されていることがわかります

![NavigationSuiteScaffold (1).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/145704/9cb153d5-2389-815b-8f8c-fee1defc8f89.gif)


ちなみにNavigationSuiteTypeは、NavigationBar/NavigationDrawer/NavigationRail/Noneから選べるようです。
デフォルトで使われている2つ以外にも、ドロワーや表示しないことも選べるのが面白いですね。

![スクリーンショット 2024-06-27 9.46.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/145704/f18dc20f-04f5-d576-d783-cec1a1a06398.png)


# 6. （要調査）既存のScaffoldを使ったアプリを置き換えたい場合

NavigationSuiteScaffoldの引数には、通常のScaffoldに存在するtopBarやfloatingActionButtonが存在しません。
そのため、少なくとも既存のScaffoldをそのままNavigationSuiteScaffoldに置き換えることはできなさそうです。

topBarやfloatingActionButtonを使いつつNavigationSuiteScaffoldを使うとなると、
恐らく通常のScaffold部分はそのまま、content引数内でNavigationSuiteScaffoldをネストしbottomBar部分の記述のみをNavigationSuiteScaffoldに移行する、という感じにするしかないと思うのですが、これが最適な方法かどうかはわかりませんでした…
もしどなたかご存知の方がいれば教えてください。

# 7. まとめ

Navigation railはmedium以上のwindow sizeでユーザの移動を助けるコンポーネントであり、
端末のボトムではなくサイドにナビを配置することで、タブレットなどの使用時にユーザが親指で操作しやすくなるメリットがあることがわかりました。

タブレットでも特定のユースケースではNavigation railを表示することが最適でない場合があったり、逆にいうと、画面サイドに親指がこないタブレット以外の大画面端末ではNavigation railが最適でないケースもあるのかなと思いました。

NavigationSuiteScaffoldはカスタマイズすることもできるので、ユースケースに応じて丁寧にUIを検討していくことでより良いUXを作っていきたいと思います。

ここまで読んでいただきありがとうございました！