2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Trackrアプリから学ぶ大画面対応

Last updated at Posted at 2021-12-22

この記事はAndroid Advent Calendar 2021 22日目の記事です。

Android12Lもベータがリリースされ、タブレットや折りたたみ式デバイスなど大画面対応の機運も高まってきているように感じます。
この記事では、そんな大画面対応をGoogleが公開しているTrackrアプリでどのように対応・実装されているのかについて紹介していきます。

Trackrについて

Trackrはサンプルのタスク管理アプリで、新規タスクの追加や編集、タスクのアーカイブ、タスクのお気に入りといった定番の機能を持っています。モダンな技術を利用しつつ、アクセシビリティの観点からも実装が行われている他、Composeでの実装パターンも開発が進んでいます。
今回はその中で大画面対応にスポットを当てて紹介します。

Navigation Rail

Navigation Railは、3~7画面のナビゲーションを設けることのできるコンポーネントで、Bottom Navigationが画面下に配置されるのに対して、画面横に配置されるため大きな画面サイズでもスペースを有効活用できます。

実装

Trackrアプリでは、レイアウトリソースを分けることで、w600dp以上の画面の場合にはNavigation Railが利用され、それ以下の場合には、Bottom App Barが利用されるようになっています。

Bottom App Bar Navigation Rail

Bottom Navigationと同様に app:menu でアイテムを設定し、app:labelVidibilityMode でラベルの表示有無を設定します。

main_activity.xml
<com.google.android.material.navigationrail.NavigationRailView
            android:id="@+id/navigation_rail"
            android:layout_width="wrap_content"
            android:layout_height="0dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:headerLayout="@layout/navigation_rail_header"
            app:labelVisibilityMode="unlabeled"
            app:menu="@menu/navigation_rail" />

Trackrアプリでは、Navigation RailはActivityで持っていますが、Bottom App BarはそれぞれのFragmentで保持しています。これは、Navigation Railがトップレベルでどこからでもアクセスできるという観点で常時表示されるコンポーネントであるためと思われます。

このため、縦画面ではタスク一覧画面のみでタスク追加用のFloating Action Buttonが表示されますが、横画面などNavigation Railで表示されている場合には常時表示されるため、どの画面からもタスク追加を行うことができるようになっています。

Navigation Railで表示されているFloating Action Buttonはapp:headerLayout パラメータで別のレイアウトを指定することで設定されています。ガイドラインでは、Floating Action Buttonをオプションで配置できるという記載になっていますが、やろうと思えば他のコンポーネントも配置できそうです。

次にKotlinコード側では、 androidx.navigation を利用して遷移が行われています。こちらも、Bottom Navigationで実装する場合と近いものになっていると思います。

MainActivty.kt
val navHostFragment = supportFragmentManager
            .findFragmentById(R.id.nav_host_fragment) as NavHostFragment
        val navController = navHostFragment.navController

        binding.navigationRail?.apply {
            setupWithNavController(navController)
            setOnItemReselectedListener { } // Prevent navigating to the same item.
            setOnApplyWindowInsetsListener(null) // See above about consuming window insets.

            headerView?.setOnClickListener {
                navController.navigate(R.id.nav_task_edit_graph)
            }
        }

このアプリでは、 setDecorFitsSystemWindows することで、システムバー領域にもコンテンツが表示されるようになっています。この時、Navigation Railがステータスバーにめり込むようになりますが、この位置を調整するために以下の処理が行われ、個別にpaddingを当てて対応しています。
Navigation RailはLTRの言語の場合には左側、RTLの言語の場合には右側に配置されるため、それぞれで個別に調整が行われています。

MainActivity.kt
binding.navigationRail?.let { navRail ->
            binding.activityRoot.doOnApplyWindowInsets { v, insets, _, _ ->
                val isRtl = v.isRtl
                val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
                navRail.updatePadding(
                    left = if (isRtl) 0 else systemBars.left,
                    right = if (isRtl) systemBars.right else 0,
                    top = systemBars.top,
                    bottom = systemBars.bottom
                )

                // Consume either left or right insets, but not both.
                WindowInsetsCompat.Builder(insets).setInsets(
                    WindowInsetsCompat.Type.systemBars(),
                    Insets.of(
                        if (isRtl) systemBars.left else 0,
                        systemBars.top,
                        if (isRtl) 0 else systemBars.right,
                        systemBars.bottom
                    )
                ).build()

            }
        }

        WindowCompat.setDecorFitsSystemWindows(window, false)

SlidingPaneLayout

SlidingPaneLayoutは大画面では2ペインのレイアウト、小さい画面では1ペインのみのレイアウトのように自動的に切り替えを行うコンポーネントです。
SlidingPaneLayoutでは、layout_width の合計を上回る画面幅を持つ端末で表示された場合には、並べて表示されます。画面に入りきらない場合には、スライドアニメーションとともに画面が切り替わるような挙動になります。

record-211222233112.gif

record-211222233513.gif

実装

Trackrの場合には、@dimen/list_pane_width は360dp、@detail_pane_width は400dpが設定されていることから、760dp以上の横幅の場合、並べて表示される挙動となります。

tasks_two_pane_fragment.xml
<layout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <androidx.slidingpanelayout.widget.SlidingPaneLayout
        android:id="@+id/sliding_pane_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <androidx.fragment.app.FragmentContainerView
            android:id="@+id/list_pane"
            android:name="com.example.android.trackr.ui.tasks.TasksFragment"
            android:layout_width="@dimen/list_pane_width"
            android:layout_height="match_parent"
            android:layout_weight="@dimen/list_pane_weight"
            tools:layout="@layout/tasks_fragment" />

        <androidx.fragment.app.FragmentContainerView
            android:id="@+id/detail_pane"
            android:name="androidx.navigation.fragment.NavHostFragment"
            android:layout_width="@dimen/detail_pane_width"
            android:layout_height="match_parent"
            android:layout_weight="@dimen/detail_pane_weight"
            app:navGraph="@navigation/task_detail"
            tools:layout="@layout/task_detail_fragment" />

    </androidx.slidingpanelayout.widget.SlidingPaneLayout>
</layout>

また、詳細側には navGraph で詳細用のnavigationが設定されており、以下のように通知を受け取って選択したタスクが切り替わるようになっています。

TasksTwoPaneFragment
repeatWithViewLifecycle {
            launch {
                tasksViewModel.showTaskDetailEvents.collect {
                    if (it.isNewSelection) {
                        // Change the detail pane contents.
                        detailNavController.navigate(TaskDetailGraphDirections.toTaskDetail(it.taskId))
                    }
                    if (it.isUserSelection) {
                        // Slide the detail pane into view. If both panes are visible, this has no
                        // visible effect.
                        binding.slidingPaneLayout.openPane()
                    }
                }
            }
        }

DialogFragment

タスク追加・編集画面は、DialogFragmentで実装されており、画面によって利用されるxmlが切り替わるようになっています。これによって、大画面の場合にはダイアログらしいUIで表示され、小さい画面では通常のFragmentのような表示がされるようになっています。

record-211222234011.gif

ダイアログらしくない画面でもDialogFragmentを利用して表現する方法は自分にはない発想で目から鱗でした。

まとめ

大画面への対応に加えて、デザインやnavigationの利用方法についても学びのある点が多く、大画面対応をする上でどこから手をつけるか検討する際には、非常に参考になると感じました。また、アクセシビリティのサポートをはじめとして大画面対応以外にも見どころのあるアプリなので、今後の変更もチェックしていきたいと思います。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?