アダプティブ レイアウト

1. 始める前に

Android デバイスには、さまざまな形状、サイズ、フォーム ファクタがあります。小画面のデバイスから大画面のデバイスまで、さまざまな種類のデバイスで動作するようにアプリを設計する必要があります。製品レベルの機能を備えたアプリを作成するデベロッパーは Android WearAndroid AutoAndroid TV をサポートする場合がありますが、これらのトピックはこのコースの対象外です。アプリが多様な画面に対応していると、さまざまなデバイスを使用する多くのユーザーにアプリを提供できます。

アプリは柔軟なレイアウトを備えていなければなりません。特定のアスペクト比と画面サイズを想定した固定ディメンションでレイアウトを定義するのではなく、レイアウトがさまざまな画面サイズと向きに無理なく対応できるようにする必要があります。アプリの実行中に画面サイズとアスペクト比が変化することがある折りたたみ式デバイスでアプリが動作する場合も、同じ原則が適用されます。折りたたみ式デバイスについては、この Codelab の最後で簡単に説明します。

aecb59fc49fb4abf.png

前提条件

  • Android Studio にコードをダウンロードして実行する方法を理解している。
  • Android アーキテクチャ コンポーネントの ViewModelLiveData に精通している。
  • Navigation コンポーネントの基本的な知識を持っている。

学習内容

  • アプリに SlidingPaneLayout を追加する方法

作成するアプリの概要

  • Sports アプリを更新して大画面に適合させます。

必要なもの

  • Android Studio がインストールされているパソコン
  • Sports アプリのスターター コード

この Codelab のスターター コードをダウンロードする

この Codelab では、ここで学んだ機能を使って拡張するためのスターター コードが提供されます。スターター コードには、以前の Codelab で学んだコードだけでなく、今後の Codelab で学ぶ予定の、見慣れないコードが含まれていることもあります。

この Codelab のコードを GitHub から取得して Android Studio で開く手順は次のとおりです。

  1. Android Studio を起動します。
  2. [Welcome to Android Studio] ウィンドウで、[Get from VCS] をクリックします。

61c42d01719e5b6d.png

  1. [Get from Version Control] ダイアログの [Version control] で、[Git] が選択されていることを確認します。

9284cfbe17219bbb.png

  1. 提供されたコードの URL を [URL] ボックスに貼り付けます。
  2. 必要に応じて、[Directory] を推奨されるデフォルトとは異なるものに変更します。

5ddca7dd0d914255.png

  1. [Clone] をクリックします。Android Studio がコードの取得を開始します。
  2. Android Studio が開くまで待ちます。
  3. Codelab のスターター コード、アプリコード、または解答コードに適したモジュールを選択します。

2919fe3e0c79d762.png

  1. 実行ボタン 8de56cba7583251f.png をクリックし、コードをビルドして実行します。

2. Code-Along 動画を見る(省略可)

コースの講師が Codelab を完了する様子を視聴する場合は、以下の動画を再生してください。

動画を拡大して全画面表示にすることをおすすめします(動画の右下隅のアイコン 正方形の 4 つの角が強調表示されたこの記号は、全画面モードを表します。 を使用します)。そうすれば、Android Studio とコードがもっとはっきり見えるようになります。

このステップは省略可能です。動画をスキップして、すぐに Codelab の学習を開始することもできます。

3. スターター アプリの概要

Sports アプリは 2 つの画面で構成されます。最初の画面では、スポーツリストを表示します。ユーザーは特定のスポーツを選択できます。アイテムが選択されたら、2 番目の画面を表示します。2 番目の画面は、選択されたスポーツのニュースを表示する詳細画面です。詳細画面では、実装を簡単にするために、プレースホルダ テキストを表示します。

スターター コードのチュートリアル

ダウンロードしたスターター コードには、あらかじめ設計されたリスト画面と詳細画面のレイアウトが含まれています。このパスウェイでは、アプリを大画面に適合させることに専念します。大画面を活用するために、SlidingPaneLayout を使用します。作業の土台とするファイルの一部について簡単に説明します。

fragment_sports_list.xml

  • [Design] ビューで res/layout/fragment_sports_list.xml を開きます。
  • このファイルには、アプリの最初の画面であるスポーツリストのレイアウトが格納されています。
  • このレイアウトは、スポーツ ニュースのリストを表示する RecyclerView で構成されています。

f50d3e7b41fcb338.png

d9af155f87ddbcdf.png

sports_list_item.xml

  • [Design] ビューで res/layout/sports_list_item.xml を開きます。
  • このファイルには、RecyclerView の各アイテムのレイアウトが格納されています。
  • このレイアウトは、スポーツのサムネイル画像、ニュースのタイトル、簡潔なスポーツ ニュースのプレースホルダ テキストで構成されています。

b19fd0e779c1d7c3.png

fragment_sports_news.xml

  • [Design] ビューで res/layout/fragment_sports_news.xml を開きます。
  • このファイルには、アプリの 2 番目の画面のレイアウトが格納されています。この画面は、ユーザーが RecyclerView からスポーツを選択したときに表示されます。
  • このレイアウトは、スポーツの画像バナーと、スポーツ ニュースのプレースホルダ テキストで構成されています。

c2073b1752342d97.png

main_activity.xml と content_main.xml

この 2 つのファイルでは、単一のフラグメントを含むメイン アクティビティのレイアウトが定義されています。

ナビゲーション グラフには 2 つのデスティネーションが含まれています。1 つはスポーツリスト用、もう 1 つはスポーツ ニュース用です。

res/values フォルダ

このフォルダにあるリソース ファイルについては学習済みです。

  • colors.xml には、アプリで使用されるテーマカラーが格納されています。
  • strings.xml には、アプリに必要なすべての文字列が格納されています。
  • themes.xml には、アプリ用に行われた UI のカスタマイズが格納されています。

MainActivity.kt

このファイルには、デフォルト テンプレートによって生成された、アクティビティのコンテンツ ビューを main_activity.xml として設定するコードが格納されています。メソッド onSupportNavigateUp() は、アプリバーからのデフォルトの「上へ」ナビゲーションを処理するためにオーバーライドされています。

model/Sport.kt

これは、スポーツリストの RecyclerView の各行に表示されるデータを保持するデータクラスです。

data/SportsData.kt

このファイルには、getSportsData() という関数が格納されています。この関数は、ハードコードされたスポーツデータが事前入力されている ArrayList を返します。

SportsViewModel.kt

これはアプリの共有 ViewModel です。ViewModel は、SportsListFragment(スポーツリストを表示する最初の画面)と NewsDetailsFragment(詳細なスポーツ ニュースを表示する 2 番目の画面)によって共有されます。

  • _currentSport は、ユーザーが選択した現在のスポーツを保存する MutableLiveData, 型のプロパティです。currentSport プロパティは _currentSport のバッキング プロパティであり、他のクラスのためのパブリックな読み取り専用バージョンとして公開されます。
  • _sportsData プロパティは、スポーツデータのリストを格納します。前述のプロパティと同様に、このプロパティにはパブリックな読み取り専用バージョンとして sportsData があります。
  • イニシャライザである init{} ブロックは、_currentSport_sportsData を初期化します。_sportsData については、data/SportsData.kt から取得されるスポーツのリスト全体が初期化されます。_currentSport については、リストの最初のアイテムが初期化されます。
  • 関数 updateCurrentSport()Sports インスタンスを受け取り、渡された値で _currentSport を更新します。

SportsAdapter.kt

これは RecyclerView のアダプターです。コンストラクタ内で、クリック リスナーが渡されます。このファイルに含まれるコードの大半は、以前の Codelab でお馴染みのボイラープレート コードです。

SportsListFragment.kt

これは、スポーツリストが表示される最初の画面のフラグメントです。

  • onCreateView() 関数は、バインディング オブジェクトを使用して fragment_sports_list レイアウト XML をインフレートします。
  • onViewCreated() 関数は RecyclerView のアダプターをセットアップします。共有 ViewModel である SportsViewModel で、ユーザーが選択したスポーツを現在のスポーツとして更新します。スポーツ ニュースの詳細画面に移動し、submitList(List) を使用して、表示するスポーツリストをアダプターに送信します。

NewsDetailsFragment.kt

これはアプリの 2 番目の画面であり、スポーツ ニュースのプレースホルダ テキストが表示されます。

  • onCreateView() 関数は、バインディング オブジェクトを使用して fragment_sports_news レイアウト XML をインフレートします。
  • onViewCreated() 関数は、データが変更されたときに UI を自動的に更新するために、SportsViewModel のプロパティである currentSport にオブザーバーをアタッチします。オブザーバー内では、スポーツのタイトル、画像、ニュースが更新されます。

アプリをビルドして実行する

  1. アプリをビルドして、エミュレータまたはデバイスで実行します。スポーツリストからいずれかのアイテムを選択すると、アプリは 2 番目の画面に移動してニュースのプレースホルダ テキストを表示します。

4. リスト / 詳細パターン

現在のスターター アプリは、タブレットなどの大型デバイスでは画面スペースを最大限に活用できません。この問題を解決するには、この Codelab で学習するリスト / 詳細パターンを使用してアプリの UI を表示します。

タブレットでアプリを実行する

このタスクでは、タブレット プロファイルを備えたエミュレータを作成します。エミュレータを作成したら、Sports アプリのスターター コードを実行して UI を確認します。

  1. Android Studio で、[Tools] > [AVD Manager] に移動します。
  2. [Android Virtual Device Manager] ウィンドウが表示されます。下部に表示される [+ Create New Virtual Device...] をクリックします。
  3. [Virtual Device Configuration] ウィンドウが表示されます。ここで、エミュレータのハードウェアと OS を構成します。左側のペインで [Tablet] をクリックします。中央のペインで [Pixel C] またはそれと似たハードウェア プロファイルを選択します。

8303f9b3e70321eb.png

  1. [Next] をクリックします。
  2. 最新のシステム イメージを選択します。この Codelab の作成時点における最新バージョンは R(API レベル 30)です。
  3. [Next] をクリックします。
  4. ここで仮想デバイスの名前を変更できますが、変更するかどうかは任意です。
  5. [Finish] をクリックします。
  6. [Android Virtual Device Manager] ウィンドウに自動的に戻ります。新しく作成した仮想デバイスの横にある起動アイコン 38752506de85d293.png をクリックします。
  7. タブレット プロファイルを備えたエミュレータが起動します。起動するまで時間がかかることがあるので、しばらくお待ちください。
  8. [Android Virtual Device Manager] ウィンドウを閉じます。
  9. 新しく作成したエミュレータで Sports アプリを実行します。

200e209de7a2f0ad.png

ご覧のとおり、大型デバイスではアプリは画面全体を活用していません。大画面では、リストのみを表示するより、リストと詳細を表示する方が効果的です。リスト / 詳細パターンはマスター / 詳細パターンとも呼ばれます。このパターンでは、レイアウトの片側にアイテムのリストが表示され、アイテムをタップするとリストの横に詳細が表示されます。一般的に、このようなビューは、より多くのコンテンツを表示できるスペースがあるタブレットなどの大画面でのみ表示されます。

以下の画像は、リスト / 詳細パターンの例です。

71698910dd129a91.png

上記のリスト / 詳細パターンでは、アイテムのリストが左側に表示され、選択されたアイテムの詳細が右側に表示されます。

Sports アプリで上記のパターンを使用する場合は、ニュース フラグメントが詳細画面になります。

51c9542717d2f875.png

この Codelab では、SlidingPaneLayout を使用してリスト / 詳細 UI を実装する方法を学びます。

5. SlidingPaneLayout パターン

リスト / 詳細 UI は、画面サイズに応じて異なる動作をしなければなりません。大きなディスプレイには、リストペインと詳細ペインを並べて配置できる十分なスペースがあります。リストアイテムをクリックすると、その詳細が詳細ペインに表示されます。しかし、小さな画面では、このパターンは窮屈に見えます。両方のペインを一度に表示するよりも、一度に 1 つずつ表示する方が適切です。最初は、リストペインが画面全体に表示されます。アイテムをタップすると、リストペインに代わってそのアイテムの詳細ペインが画面全体に表示されます。

ここでは、SlidingPaneLayout を使用して、現在の画面サイズを基に適切なユーザー エクスペリエンスを選択するロジックを処理する方法を学びます。

b0a205de3494e95d.gif

この例では、小さな画面で詳細ペインがリストペインの上にスライドして表示されます。

下記の画像は、小さな画面で SlidingPaneLayout がどのように表示されるかを示しています。リストからアイテムが選択されると、詳細ペインがリストペインの上に重ねられることに注意してください。つまり、常に両方のペインが存在しています。

e26f94d9579b6121.png

471b0b38d4dfa95a.png

このように、SlidingPaneLayout は、大型デバイスでは 2 つのペインを並べて表示し、スマートフォンなどの小型デバイスでは一度に 1 つのペインのみを表示するよう自動的に調整する機能を備えています。

6. ライブラリの依存関係を追加する

  1. build.gradle (Module: Sports.app) を開きます。
  2. アプリで SlidingPaneLayout を使用するため、dependencies セクションに次の依存関係を含めます。
dependencies {
...
    implementation "androidx.slidingpanelayout:slidingpanelayout:1.2.0-beta01"
}

7. スポーツリストのフラグメント XML を構成する

このタスクでは、fragment_sports_list のルート レイアウトを SlidingPaneLayout に変換します。すでに学習したとおり、SlidingPaneLayout は、最上位レベルの UI で使用できる、横に並んだ 2 つのペインのレイアウトを提供します。このレイアウトでは、最初のペインをコンテンツの閲覧用リストとして使用します。このペインは、もう 1 つのペインでコンテンツを表示する主要な詳細ビューに従属します。

Sports アプリでは、最初のペインはスポーツリストを表示する RecyclerView であり、2 番目のペインはスポーツ ニュースを表示します。

SlidingPaneLayout を追加する

  1. fragment_sports_list.xml を開きます。ルート レイアウトは FrameLayout であることに注目してください。
  2. FrameLayoutandroidx.slidingpanelayout.widget.SlidingPaneLayout. に変更します。
<androidx.slidingpanelayout.widget.SlidingPaneLayout
   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"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   tools:context=".SportsListFragment">

   <androidx.recyclerview.widget.RecyclerView...>
</androidx.slidingpanelayout.widget.SlidingPaneLayout>
  1. SlidingPaneLayoutandroid:id 属性を追加し、その値を @+id/sliding_pane_layout に設定します。
<androidx.slidingpanelayout.widget.SlidingPaneLayout
   ...
   android:id="@+id/sliding_pane_layout"
   ...>

SlidingPaneLayout に 2 番目のペインを追加する

このタスクでは、SlidingPaneLayout に 2 番目の子を追加します。これが右側のコンテンツ ペインとして表示されます。

  1. fragment_sports_list.xmlRecyclerView の下に、2 番目の子 androidx.fragment.app.FragmentContainerView を追加します。
  2. 必須の属性 layout_height および layout_widthFragmentContainerView に追加します。それらの値を match_parent に設定します。これらの値は後で更新します。
<androidx.fragment.app.FragmentContainerView
   android:layout_height="match_parent"
   android:layout_width="match_parent"/>
  1. FragmentContainerViewandroid:id 属性を追加し、その値を @+id/detail_container に設定します。
android:id="@+id/detail_container"
  1. android:name 属性を使用して、FragmentContainerViewNewsDetailsFragment を追加します。
android:name="com.example.android.sports.NewsDetailsFragment"

layout_width 属性を更新する

SlidingPaneLayout は、ペインの幅を使用して両方のペインを並べて表示するかどうかを決定します。たとえば、リストペインの最小幅が 300dp と測定され、詳細ペインに 400dp の幅が必要な場合、SlidingPaneLayout は、最低 700dp の幅が使用可能であれば、自動的に 2 つのペインを並べて表示します。

幅の合計が SlidingPaneLayout の使用可能な幅を超える場合、子ビューは重ねられます。この場合、子ビューは SlidingPaneLayout の使用可能な幅いっぱいに展開されます。

子ビューの幅を決定するには、デバイス画面の幅に関する基本的な情報が必要です。サイズ変更が可能なアプリ レイアウトの設計、開発、テストに使用できる不変のブレークポイントの一覧を次の表に示します。これらのブレークポイントは、柔軟性とレイアウトのシンプルさのバランスを取りつつ、固有のケースに合わせてアプリを最適化できるように、特別に選択されたものです。

ブレークポイント

デバイスによる表現

コンパクトな幅

600 dp 未満

縦向きのスマートフォンの 99.96%

中程度の幅

600 dp 以上

縦向きのタブレット(広げた状態の大型縦向きインナー ディスプレイ)の 93.73%

拡大幅

840 dp 以上

横向きのタブレット(広げた状態の大型横向きインナー ディスプレイ)の 97.22%

a247a843310d061a.png

Sports アプリでは、スポーツリストをスマートフォンの単一のペインに表示できます(これは幅が 600dp 未満のデバイスに当たります)。タブレットで両方のペインを表示するには、幅の合計を 840dp より大きくする必要があります。最初の子(リサイクラー ビュー)には 550dp の幅、2 番目の子(FragmentContainerView)には 300dp の幅を使用できます。

  1. fragment_sports_list.xml で、RecyclerView のレイアウト幅を 550dp に変更し、FragmentContainerView のレイアウト幅を 300dp に変更します。
<androidx.recyclerview.widget.RecyclerView
   ...
   android:layout_width="550dp"
   .../>

<androidx.fragment.app.FragmentContainerView
   ...
   android:layout_width="300dp"
   .../>
  1. タブレット プロファイルを備えたエミュレータと、スマートフォン プロファイルを備えたエミュレータでアプリを実行します。

ad148a96d7487e66.png

タブレットでは 2 つのペインが表示されます。タブレットの 2 番目のペインの幅は、この後のステップで修正します。

  1. スマートフォン プロファイルを備えたエミュレータでアプリを実行します。

a6be6d199d2975ac.png

layout_weight を追加する

このタスクでは、タブレットの UI を修正して、2 番目のペインを残りのスペース全体に広げます。

SlidingPaneLayout では、ビューが重ならない場合に、子ビューのレイアウト パラメータ layout_weight を使用して、測定後に残りのスペースを分割する方法を定義できます。このパラメータは幅にのみ適用されます。

  1. fragment_sports_list.xml で、FragmentContainerViewlayout_weight を追加し、その値を 1 に設定します。これにより、リストペインが測定された後、2 番目のペインが展開されて残りのスペースを埋めるようになります。
android:layout_weight="1"
  1. アプリを実行します。

ce3a93fe501ee5dc.png

これで、SlidingPaneLayout が首尾よく追加されました。しかし、これで終わりではありません。「戻る」ナビゲーションを実装し、リストからアイテムが選択されたときに 2 番目のペインを更新する必要があります。この後のタスクでそれらを実装します。

8. 詳細ペインを入れ替える

タブレット プロファイルを備えたエミュレータでアプリを実行します。スポーツリストからリストアイテムを選択します。ご覧のとおり、アプリは詳細ペインに移動します。

8fedee8d4837909.png

このタスクでは、この問題を修正します。現在、デュアルペイン コンテンツは選択されたスポーツで更新され、その後アプリは NewsDetailsFragment に移動します。

  1. SportsListFragment ファイルの onViewCreated() 関数で以下の行を見つけます。これが詳細画面に移動するコードです。
// Navigate to the details screen
val action = SportsListFragmentDirections.actionSportsListFragmentToNewsFragment()
this.findNavController().navigate(action)
  1. 上記の行を次のコードに置き換えます。
binding.slidingPaneLayout.openPane()

SlidingPaneLayoutopenPane() を呼び出して、2 番目のペインを最初のペインと入れ替えます。タブレットなどで両方のペインが表示されている場合、これにより目に見える効果は生じません。

  1. タブレットとスマートフォンのエミュレータでアプリを実行します。デュアルペイン コンテンツが適切に更新されることを確認します。

b0d3c8c263be15f8.png

次のタスクでは、カスタムの「戻る」ナビゲーション機能をアプリに追加します。

9. カスタムの「戻る」ナビゲーションを追加する

リストペインと詳細ペインが重ねられる小型デバイスでは、システムの [戻る] ボタンでユーザーを詳細ペインからリストペインに戻す必要があります。そのためには、カスタムの「戻る」ナビゲーションを提供して、OnBackPressedCallbackSlidingPaneLayout の現在の状態に接続します。

「戻る」ナビゲーション

「戻る」ナビゲーションとは、ユーザーがこれまでにアクセスした画面の履歴を遡って移動する機能です。すべての Android デバイスは、このタイプのナビゲーションの用に [戻る] ボタンを備えています。ユーザーの Android デバイスによって、このボタンは物理ボタンの場合もあれば、ソフトウェア ボタンの場合もあります。

カスタムの「戻る」ナビゲーション

Android では、ユーザーがアプリ内を移動する際に、デスティネーションの「バックスタック」が保持されます。これにより、通常は [戻る] ボタンを押すと、以前のデスティネーションに適切に移動できます。ただし、可能な限り最高のユーザー エクスペリエンスを提供するために、アプリ独自の「戻る」動作を実装する必要が生じる場合があります。

たとえば、Chrome ブラウザなどの WebView を使用する場合は、デフォルトの [戻る] ボタンの動作をオーバーライドして、ユーザーがアプリの前の画面ではなくウェブの閲覧履歴に戻れるようにすることができます。

同様に、SlidingPaneLayout に対してカスタムの「戻る」ナビゲーションを提供し、アプリが詳細ペインからリストペインに戻るようにする必要があります。

カスタムの「戻る」ナビゲーションを実装する

Sports アプリにカスタムの「戻る」ナビゲーションを実装するには、次の手順を実施する必要があります。

  • OnBackPressedCallback をオーバーライドして、[戻る] ボタンの押下を処理するカスタム コールバックを定義します。
  • コールバック インスタンスを登録して追加します。

まず、カスタム コールバックを定義します。

  1. SportsListFragment ファイルで、SportsListFragment クラス定義の下に新しいクラスを追加します。SportsListOnBackPressedCallback という名前を付けます。
  2. SlidingPaneLayoutprivate インスタンスをコンストラクタ パラメータとして渡します。
class SportsListOnBackPressedCallback(
   private val slidingPaneLayout: SlidingPaneLayout
)
  1. OnBackPressedCallback からクラスを拡張します。OnBackPressedCallback クラスは onBackPressed コールバックを処理します。コンストラクタ パラメータのエラーはこの後すぐ修正します。
class SportsListOnBackPressedCallback(
   private val slidingPaneLayout: SlidingPaneLayout
): OnBackPressedCallback()

OnBackPressedCallback のコンストラクタは、初期有効状態のブール値を取ります。コールバックが有効になっている場合(つまり、isEnabled() が true を返す場合)に限り、ディスパッチャはコールバックの handleOnBackPressed() を呼び出して [戻る] ボタンイベントを処理します。

  1. slidingPaneLayout.isSlideable* && slidingPaneLayout.isOpen* をコンストラクタ パラメータとして OnBackPressedCallback に渡します。ブール値 isSlideable は、2 番目のペインがスライド可能な場合(小さな画面上にあり、単一のペインが表示されている場合)にのみ、true になります。isOpen の値は、2 番目のペイン(コンテンツ ペイン)が完全に開いている場合に true になります。
class SportsListOnBackPressedCallback(
   private val slidingPaneLayout: SlidingPaneLayout
): OnBackPressedCallback(slidingPaneLayout.isSlideable && slidingPaneLayout.isOpen)

このコードにより、小さな画面のデバイスでコンテンツ ペインが開いている場合にのみコールバックが有効になることが保証されます。

  1. 未実装のメソッドに関するエラーを修正するため、赤い電球アイコン 5fdf362480bfe665.png をクリックして [Implement members] を選択します。
  2. [Implement members] ポップアップで [OK] をクリックして、handleOnBackPressed メソッドをオーバーライドします。

クラスは次のようになります。

class SportsListOnBackPressedCallback(
   private val slidingPaneLayout: SlidingPaneLayout
): OnBackPressedCallback(slidingPaneLayout.isSlideable && slidingPaneLayout.isOpen) {
   /**
    * Callback for handling the [OnBackPressedDispatcher.onBackPressed] event.
    */
   override fun handleOnBackPressed() {
       TODO("Not yet implemented")
   }
}
  1. handleOnBackPressed() 関数内の TODO ステートメントを削除し、コンテンツ ペインを閉じてリストペインに戻る次のコードを追加します。
slidingPaneLayout.closePane()

SlidingPaneLayout のイベントをモニタリングする

[戻る] ボタンの押下イベントの処理に加えて、スライディング ペインに関連するイベントをリッスンしてモニタリングする必要があります。コンテンツ ペインがスライドする際、それに応じてコールバックを有効または無効にしなければなりません。そのためには、PanelSlideListener を使用します。

インターフェース SlidingPaneLayout.PanelSlideListener には、3 つの抽象メソッド onPanelSlide()onPanelOpened()onPanelClosed() が含まれています。これらのメソッドは、それぞれ詳細ペインがスライドしたとき、開かれたとき、閉じられたときに呼び出されます。

  1. SlidingPaneLayout.PanelSlideListener から SportsListOnBackPressedCallback クラスを拡張します。
  2. エラーを解決するために、3 つのメソッドを実装します。Android Studio で赤い電球アイコンをクリックして、[Implement members] を選択します。

ad52135eecbee09f.png

  1. SportsListOnBackPressedCallback クラスは次のようになります。
class SportsListOnBackPressedCallback(
   private val slidingPaneLayout: SlidingPaneLayout
): OnBackPressedCallback(slidingPaneLayout.isSlideable && slidingPaneLayout.isOpen),
  SlidingPaneLayout.PanelSlideListener{

   override fun handleOnBackPressed() {
       slidingPaneLayout.closePane()
   }

   override fun onPanelSlide(panel: View, slideOffset: Float) {
       TODO("Not yet implemented")
   }

   override fun onPanelOpened(panel: View) {
       TODO("Not yet implemented")
   }

   override fun onPanelClosed(panel: View) {
       TODO("Not yet implemented")
   }
}
  1. TODO ステートメントを削除します。
  2. 詳細ペインが開かれている(表示されている)ときは、OnBackPressedCallback コールバックを有効にします。そのためには、setEnabled() 関数を呼び出して true を渡します。onPanelOpened() 内に次のコードを記述します。
setEnabled(true)
  1. 上記のコードは、プロパティ アクセス構文を使用して簡略化できます。
override fun onPanelOpened(panel: View) {
   isEnabled = true
}
  1. 同様に、詳細ペインが閉じられたときは、isEnabledfalse に設定します。
override fun onPanelClosed(panel: View) {
   isEnabled = false
}
  1. コールバックを完成させる最後のステップは、詳細ペインのスライド イベントの通知を受け取るリスナーのリストに SportsListOnBackPressedCallback リスナークラスを追加することです。SportsListOnBackPressedCallback クラスに init ブロックを追加します。init ブロック内で、slidingPaneLayout.addPanelSlideListener() を呼び出して this を渡します。
init {
   slidingPaneLayout.addPanelSlideListener(this)
}

完成した SportsListOnBackPressedCallback クラスは次のようになります。

class SportsListOnBackPressedCallback(
   private val slidingPaneLayout: SlidingPaneLayout
): OnBackPressedCallback(slidingPaneLayout.isSlideable && slidingPaneLayout.isOpen),
  SlidingPaneLayout.PanelSlideListener{

   init {
       slidingPaneLayout.addPanelSlideListener(this)
   }

   override fun handleOnBackPressed() {
       slidingPaneLayout.closePane()
   }

   override fun onPanelSlide(panel: View, slideOffset: Float) {
   }

   override fun onPanelOpened(panel: View) {
       isEnabled = true
   }

   override fun onPanelClosed(panel: View) {
       isEnabled = false
   }
}

コールバックを登録する

コールバックの実際の動作を確認するため、ディスパッチャ OnBackPressedDispatcher を使用してコールバックを登録します。

FragmentActivity の基本クラスを使用すると、OnBackPressedDispatcher を使用して [戻る] ボタンの動作を制御できます。OnBackPressedDispatcher は、[戻る] ボタンイベントを 1 つまたは複数の OnBackPressedCallback オブジェクトにディスパッチする方法を制御します。

addCallback() メソッドを使用してコールバックを追加します。このメソッドは LifecycleOwner を受け取ります。これにより、LifecycleOwnerLifecycle.State.STARTED の場合に限り、OnBackPressedCallback が追加されるようになります。また、アクティビティまたはフラグメントは、関連する LifecycleOwner が破棄されたときに、登録済みのコールバックを削除します。これにより、メモリリークが防止されるとともに、より寿命が短いフラグメントや他のライフサイクル オーナー内でコールバックを使用しやすくなります。

また、addCallback() メソッドは、コールバック クラスを 2 番目のパラメータとしてインスタンスに渡します。コールバックを登録する手順は次のとおりです。

  1. SportsListFragment ファイルの関数 onViewCreated() 内で、バインディング変数の宣言のすぐ下に SlidingPaneLayout のインスタンスを作成し、binding.slidingPaneLayout の値を割り当てます。
val slidingPaneLayout = binding.slidingPaneLayout
  1. SportsListFragment ファイルの onViewCreated() 関数内で、slidingPaneLayout の宣言のすぐ下に次のコードを追加します。
// Connect the SlidingPaneLayout to the system back button.
requireActivity().onBackPressedDispatcher.addCallback(
   viewLifecycleOwner,
   SportsListOnBackPressedCallback(slidingPaneLayout)
)

上記のコードは、addCallback() を使用して viewLifecycleOwner と、SportsListOnBackPressedCallback のインスタンスを渡します。このコールバックは、フラグメントのライフサイクル中にのみ、アクティブになります。

  1. 次に、スマートフォン プロファイルを備えたエミュレータでアプリを実行し、カスタムの [戻る] ボタン機能の動作を確認します。

33967fa8fde5b902.gif

10. ロックモード

スマートフォンなどの小さな画面でリストペインと詳細ペインが重なっている場合、ユーザーはデフォルトでは両方向にスワイプして、ジェスチャー ナビゲーションを使用していない場合でも 2 つのペインを自由に切り替えることができます。SlidingPaneLayout のロックモードを設定すると、詳細ペインをロックまたはロック解除できます。

  1. スマートフォン プロファイルを備えたエミュレータで、詳細ペインを画面外にスワイプしてみてください。
  2. 詳細ペインをスワイプインすることもできます。ご自分で試してみてください。
  3. これは、Sports アプリでは望ましくない機能です。SlidingPaneLayout をロックして、ユーザーがジェスチャーを使用してスワイプインまたはスワイプアウトできないようにする方が適切です。これを実装するには、slidingPaneLayout 定義の下の onViewCreated() メソッドで lockModeLOCK_MODE_LOCKED に設定します。
slidingPaneLayout.lockMode = SlidingPaneLayout.LOCK_MODE_LOCKED

その他のロックモードについて詳しくは、ドキュメントをご覧ください。

  1. アプリをもう一度実行して、詳細ペインがロックされていることを確認します。

これで、アプリに SlidingPaneLayout が追加されました。

11. 解答コード

この Codelab の解答コードは、以下に示すプロジェクトとモジュールにあります。

  1. プロジェクト用に提供されている GitHub リポジトリ ページに移動します。
  2. ブランチ名が Codelab で指定されたブランチ名と一致していることを確認します。たとえば、次のスクリーンショットでは、ブランチ名は main です。

1e4c0d2c081a8fd2.png

  1. プロジェクトの GitHub ページで、[Code] ボタンをクリックすると、ポップアップが表示されます。

1debcf330fd04c7b.png

  1. ポップアップで、[Download ZIP] をクリックして、プロジェクトをパソコンに保存します。ダウンロードが完了するまで待ちます。
  2. パソコンに保存したファイルを見つけます([ダウンロード] フォルダなど)。
  3. ZIP ファイルをダブルクリックして展開します。プロジェクト ファイルが入った新しいフォルダが作成されます。

Android Studio でプロジェクトを開く

  1. Android Studio を起動します。
  2. [Welcome to Android Studio] ウィンドウで、[Open] をクリックします。

d8e9dbdeafe9038a.png

注: Android Studio がすでに開いている場合は、メニューから [File] > [Open] を選択します。

8d1fda7396afe8e5.png

  1. ファイル ブラウザで、展開したプロジェクト フォルダがある場所([ダウンロード] フォルダなど)に移動します。
  2. そのプロジェクト フォルダをダブルクリックします。
  3. Android Studio でプロジェクトが開かれるまで待ちます。
  4. 実行ボタン 8de56cba7583251f.png をクリックし、アプリをビルドして実行します。正常にビルドされたことを確認します。

12. 関連リンク