Navigation コンポーネントは、複雑なナビゲーションや、遷移アニメーション、ディープリンク、アプリの画面間で渡されるコンパイル時チェック引数を管理できるライブラリです。
このドキュメントでは、Navigation コンポーネントを使用するように既存のアプリを移行する一般的な方法について説明します。
移行は、大きく分けて以下のステップで構成されます。
画面固有の UI ロジックをアクティビティから移動する - アプリの UI ロジックをアクティビティから移動して、各アクティビティには
Toolbar
などのグローバル ナビゲーション UI コンポーネントのロジックだけが含まれるようにし、各画面の実装はフラグメントやカスタム デスティネーションに委任します。Navigation コンポーネントを統合する - アクティビティごとに、そのアクティビティが管理する 1 つまたは複数のフラグメントを組み込んだナビゲーション グラフを作成します。フラグメント トランザクションを Navigation コンポーネント処理に置き換えます。
アクティビティ デスティネーションを追加する -
startActivity()
呼び出しを、アクティビティ デスティネーションを使用するアクションに置き換えます。アクティビティを結合する - 複数のアクティビティが共通のレイアウトを共有している場合、ナビゲーション グラフを結合します。
前提条件
このガイドでは、すでに AndroidX ライブラリを使用するようアプリを移行済みであることを前提としています。この移行をまだ行っていない場合は、AndroidX を使用するようにプロジェクトを移行してから、下記の手順に進んでください。
画面固有の UI ロジックをアクティビティから移動する
アクティビティは、アプリと Android との間のグラフィカル インタラクションを促進するシステムレベル コンポーネントです。アクティビティは、アプリのマニフェスト内に登録します。これにより、Android は起動可能なアクティビティを認識します。アクティビティ クラスを使用すると、アプリ側も Android の変化(アプリ UI のフォアグラウンド化 / バックグラウンド化、回転など)に対応できるようになります。また、アクティビティは、画面間で状態を共有する場所としても機能します。
アプリのコンテキスト内において、アクティビティは、ナビゲーションのホストとして機能し、画面間の遷移方法やデータの受け渡し方法などに関するロジックと情報を保持する必要があります。ただし、UI の詳細を管理する機能は、UI 内の再利用可能な小さなパーツに任せる方が便利です。このパターンに関して推奨される実装は、フラグメントです。フラグメントを利用するメリットの詳細については、単一アクティビティ: 移行する理由、タイミング、方法をご覧ください。ナビゲーションは、ナビゲーションとフラグメントの依存関係を通じてフラグメントをサポートします。また、ナビゲーションは、カスタム デスティネーション タイプもサポートしています。
アプリがフラグメントを使用していない場合は、まず、フラグメントを使用するようにアプリ内の各画面を移行する必要があります。この段階では、まだアクティビティは削除しません。画面を表現するフラグメントを作成して、役割ごとに UI ロジックを分割します。
フラグメントを導入する
2 つの画面(アイテムリスト画面とアイテム情報画面)で構成されるアプリを例として、フラグメントを導入するプロセスについて説明します。このアプリでは、リスト画面でアイテムをタップすると、そのアイテムの詳細情報画面が表示されます。
この例では、現在のところ、リスト画面と詳細情報画面はそれぞれ個別のアクティビティになっています。
新しいレイアウトを作成して UI をホストする
フラグメントを導入するには、まず、フラグメントをホストするアクティビティ用の新しいレイアウト ファイルを作成します。この新しいファイルが、アクティビティの現在のコンテンツ ビュー レイアウトに取って代わります。
シンプルなビューの場合、FrameLayout
を使用できます。下記の product_list_host
サンプルをご覧ください。
<FrameLayout
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/main_content"
android:layout_height="match_parent"
android:layout_width="match_parent" />
id
属性は、後でフラグメントを追加するコンテンツ セクションを参照しています。
次に、アクティビティの onCreate()
関数内のレイアウト ファイル参照を編集して、この新しいレイアウト ファイルを指定します。
Kotlin
class ProductListActivity : AppCompatActivity() { ... override fun onCreate(savedInstanceState: Bundle?) { ... // Replace setContentView(R.layout.product_list) with the line below setContentView(R.layout.product_list_host) ... } }
Java
public class ProductListActivity extends AppCompatActivity { ... @Override public void onCreate(@Nullable Bundle savedInstanceState) { ... // Replace setContentView(R.layout.product_list); with the line below setContentView(R.layout.product_list_host); ... } }
既存のレイアウト(この例の場合は product_list
)は、これから作成するフラグメントのルートビューとして使用されます。
フラグメントを作成する
画面の UI を管理する新しいフラグメントを作成します。アクティビティのホスト名と一致させることをおすすめします。以下のスニペットでは、例として ProductListFragment
を使用しています。
Kotlin
class ProductListFragment : Fragment() { // Leave empty for now. }
Java
public class ProductListFragment extends Fragment { // Leave empty for now. }
アクティビティ ロジックをフラグメントに移動する
フラグメントを定義したら、対象となる画面の UI ロジックをアクティビティからこの新しいフラグメントに移動します。アクティビティ ベースのアーキテクチャを使用している場合、アクティビティの onCreate()
関数内に多数のビュー作成ロジックが含まれていると考えられます。
移動する必要がある UI ロジックが含まれる、アクティビティ ベース画面の例を以下に示します。
Kotlin
class ProductListActivity : AppCompatActivity() { // Views and/or ViewDataBinding references, Adapters... private lateinit var productAdapter: ProductAdapter private lateinit var binding: ProductListActivityBinding ... // ViewModels, System Services, other Dependencies... private val viewModel: ProductListViewModel by viewModels() ... override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // View initialization logic DataBindingUtil.setContentView(this, R.layout.product_list_activity) // Post view initialization logic // Connect adapters productAdapter = ProductAdapter(productClickCallback) binding.productsList.setAdapter(productAdapter) // Initialize view properties, set click listeners, etc. binding.productsSearchBtn.setOnClickListener {...} // Subscribe to state viewModel.products.observe(this, Observer { myProducts -> ... }) // ...and so on } ... }
Java
public class ProductListActivity extends AppCompatActivity { // Views and/or ViewDataBinding references, adapters... private ProductAdapter productAdapter; private ProductListActivityBinding binding; ... // ViewModels, system services, other dependencies... private ProductListViewModel viewModel; ... @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); // View initialization logic DataBindingUtil.setContentView(this, R.layout.product_list_activity); // Post view initialization logic // Connect adapters productAdapter = new ProductAdapter(productClickCallback); binding.productsList.setAdapter(productAdapter); // Initialize ViewModels and other dependencies ProductListViewModel viewModel = new ViewModelProvider(this).get(ProductListViewModel.java); // Initialize view properties, set click listeners, etc. binding.productsSearchBtn.setOnClickListener(v -> { ... }); // Subscribe to state viewModel.getProducts().observe(this, myProducts -> ... ); // ...and so on }
また、ユーザーが次の画面に移動するタイミングや方法についても、アクティビティによって制御していることがあります。次の例をご覧ください。
Kotlin
// Provided to ProductAdapter in ProductListActivity snippet. private val productClickCallback = ProductClickCallback { product -> show(product) } fun show(product: Product) { val intent = Intent(this, ProductActivity::class.java) intent.putExtra(ProductActivity.KEY_PRODUCT_ID, product.id) startActivity(intent) }
Java
// Provided to ProductAdapter in ProductListActivity snippet. private ProductClickCallback productClickCallback = this::show; private void show(Product product) { Intent intent = new Intent(this, ProductActivity.class); intent.putExtra(ProductActivity.KEY_PRODUCT_ID, product.getId()); startActivity(intent); }
フラグメント内で、この役割を onCreateView()
と onViewCreated()
に分配します。アクティビティ内には、ナビゲーション ロジックだけが残ります。
Kotlin
class ProductListFragment : Fragment() { private lateinit var binding: ProductListFragmentBinding private val viewModel: ProductListViewModel by viewModels() // View initialization logic override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { binding = DataBindingUtil.inflate( inflater, R.layout.product_list, container, false ) return binding.root } // Post view initialization logic override fun onViewCreated(view: View, savedInstanceState: Bundle?) { // Connect adapters productAdapter = ProductAdapter(productClickCallback) binding.productsList.setAdapter(productAdapter) // Initialize view properties, set click listeners, etc. binding.productsSearchBtn.setOnClickListener {...} // Subscribe to state viewModel.products.observe(this, Observer { myProducts -> ... }) // ...and so on } // Provided to ProductAdapter private val productClickCallback = ProductClickCallback { product -> if (lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) { (requireActivity() as ProductListActivity).show(product) } } ... }
Java
public class ProductListFragment extends Fragment { private ProductAdapter productAdapter; private ProductListFragmentBinding binding; // View initialization logic @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { binding = DataBindingUtil.inflate( inflater, R.layout.product_list_fragment, container, false); return binding.getRoot(); } // Post view initialization logic @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { // Connect adapters binding.productsList.setAdapter(productAdapter); // Initialize ViewModels and other dependencies ProductListViewModel viewModel = new ViewModelProvider(this) .get(ProductListViewModel.class); // Initialize view properties, set click listeners, etc. binding.productsSearchBtn.setOnClickListener(...) // Subscribe to state viewModel.getProducts().observe(this, myProducts -> { ... }); // ...and so on // Provided to ProductAdapter private ProductClickCallback productClickCallback = new ProductClickCallback() { @Override public void onClick(Product product) { if (getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.STARTED)) { ((ProductListActivity) requireActivity()).show(product); } } }; ... }
ProductListFragment
内には、レイアウトをインフレートして接続するための setContentView()
呼び出しはありません。フラグメント内で、onCreateView()
がルートビューを初期化します。onCreateView()
は、LayoutInflater
のインスタンスを受け取ります。このインスタンスを使用することで、レイアウト リソース ファイルに基づいてルートビューをインフレートできます。この例の場合、レイアウト自体は変更する必要がないため、アクティビティが使用していた既存の product_list
レイアウトを再利用します。
アクティビティの onStart()
関数や、onResume()
関数、onPause()
関数、onStop()
関数内に、ナビゲーションとは関係のない UI ロジックが存在している場合は、フラグメント上の同じ名前の関数に移動できます。
ホスト アクティビティ内でフラグメントを初期化する
すべての UI ロジックをフラグメントに移動すると、アクティビティにはナビゲーション ロジックだけが残ります。
Kotlin
class ProductListActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.product_list_host) } fun show(product: Product) { val intent = Intent(this, ProductActivity::class.java) intent.putExtra(ProductActivity.KEY_PRODUCT_ID, product.id) startActivity(intent) } }
Java
public class ProductListActivity extends AppCompatActivity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.product_list_host); } public void show(Product product) { Intent intent = new Intent(this, ProductActivity.class); intent.putExtra(ProductActivity.KEY_PRODUCT_ID, product.getId()); startActivity(intent); } }
最後のステップとして、コンテンツ ビューを設定した直後に、onCreate()
内でフラグメントのインスタンスを作成します。
Kotlin
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.product_list_host) if (savedInstanceState == null) { val fragment = ProductListFragment() supportFragmentManager .beginTransaction() .add(R.id.main_content, fragment) .commit() } }
Java
@Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.product_list_host); if (savedInstanceState == null) { ProductListFragment fragment = new ProductListFragment(); getSupportFragmentManager() .beginTransaction() .add(R.id.main_content, fragment) .commit(); } }
この例に示すように、FragmentManager
は、設定の変更に対してフラグメントを自動的に保存、復元します。そのため、手動でフラグメントを追加する必要があるのは、savedInstanceState
が null の場合に限られます。
インテント エクストラをフラグメントに渡す
アクティビティがインテントを通じて Extras
を受け取った場合、引数として直接フラグメントに渡すことができます。
この例の場合、ProductDetailsFragment
が、アクティビティのインテント エクストラから直接引数を受け取ります。
Kotlin
... if (savedInstanceState == null) { val fragment = ProductDetailsFragment() // Intent extras and Fragment Args are both of type android.os.Bundle. fragment.arguments = intent.extras supportFragmentManager .beginTransaction() .add(R.id.main_content, fragment) .commit() } ...
Java
... if (savedInstanceState == null) { ProductDetailsFragment fragment = new ProductDetailsFragment(); // Intent extras and fragment Args are both of type android.os.Bundle. fragment.setArguments(getIntent().getExtras()); getSupportFragmentManager() .beginTransaction() .add(R.id.main_content, fragment) .commit(); } ...
この段階で、フラグメントを使用するように最初の画面を更新したので、アプリの実行をテストできます。続けて残りのアクティビティ ベースの画面を移行し、その都度テストを行います。
Navigation コンポーネントを統合する
フラグメント ベース アーキテクチャを使用するようになったら、Navigation コンポーネントの統合を開始できます。
まず、Navigation ライブラリ リリースノートに記載された手順に沿って、最新の Navigation 依存関係をプロジェクトに追加します。
ナビゲーション グラフを作成する
Navigation コンポーネントは、アプリのビュー表示と同様に、リソース ファイル内のアプリのナビゲーション構成をグラフとして表現します。これにより、コードベースの外部でアプリのナビゲーションを簡単に整理し、視覚的に編集できるようになります。
ナビゲーション グラフを作成するには、まず「navigation
」という名前の新しいリソース フォルダを作成します。グラフを追加するには、このディレクトリを右クリックして、[New] > [Navigation resource file] を選択します。
Navigation コンポーネントは、アクティビティをナビゲーションのホストとして使用し、ユーザーがアプリ内を移動する際、各フラグメントをそのホストにスワップします。アプリのナビゲーションを視覚的にレイアウトするには、このグラフをホストするアクティビティ内に NavHost
を設定する必要があります。フラグメントを使用しているため、Navigation コンポーネントのデフォルト NavHost
実装である NavHostFragment
を使用できます。
NavHostFragment
は、ホスト アクティビティ内に配置した FragmentContainerView
を通じて設定します。次の例をご覧ください。
<androidx.fragment.app.FragmentContainerView
android:name="androidx.navigation.fragment.NavHostFragment"
app:navGraph="@navigation/product_list_graph"
app:defaultNavHost="true"
android:id="@+id/main_content"
android:layout_width="match_parent"
android:layout_height="match_parent" />
app:NavGraph
属性は、このナビゲーション ホストに関連付けられているナビゲーション グラフをポイントします。このプロパティを設定すると、ナビゲーション グラフがインフレートし、NavHostFragment
に対してグラフ プロパティが設定されます。app:defaultNavHost
属性を使用すると、NavHostFragment
が、システムの [戻る] ボタンをインターセプトできるようになります。
DrawerLayout
や BottomNavigationView
といったトップレベル ナビゲーションを使用している場合、この FragmentContainerView
は、メイン コンテンツ ビュー要素に取って代わります。例については、NavigationUI を使用して UI コンポーネントを更新するをご覧ください。
シンプルなレイアウトの場合、この FragmentContainerView
要素をルート ViewGroup
の子として組み込むことができます。
<FrameLayout
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="match_parent"
android:layout_width="match_parent">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/main_content"
android:name="androidx.navigation.fragment.NavHostFragment"
app:navGraph="@navigation/product_list_graph"
app:defaultNavHost="true"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
下部にある [Design] タブをクリックすると、下記のようなグラフが表示されます。グラフの左上にある [Destinations] の下に、NavHost
アクティビティへの参照が layout_name (resource_id)
形式で表示されています。
このグラフにフラグメントを追加するには、上部にあるプラスボタン()をクリックします。
Navigation コンポーネントでは、各画面のことを「デスティネーション」と呼びます。 デスティネーションとして指定できるのは、フラグメント、アクティビティ、カスタム デスティネーションです。どのタイプのデスティネーションでもグラフに追加できますが、アクティビティ デスティネーションは「ターミナル デスティネーション」と見なされます。アクティビティ デスティネーションに移動すると、別個のナビゲーション ホストおよびナビゲーション グラフの枠内に移ることになります。
Navigation コンポーネントでは、ユーザーがデスティネーション間を移動する仕組みのことを「アクション」と呼びます。アクションでは、遷移アニメーションやポップ動作を表現することもできます。
フラグメント トランザクションを削除する
Navigation コンポーネントを使用している場合、同一のアクティビティの下でフラグメント ベース画面間を移動するのであれば、FragmentManager
インタラクションを削除できます。
同一のアクティビティまたはトップレベル ナビゲーション(ドロワー レイアウトやボトム ナビゲーション)の下で複数のフラグメントを使用するアプリの場合、これまでは、UI のメイン コンテンツ セクション内でフラグメントの追加や置換を行うには、通常 FragmentManager
や FragmentTransactions
を使用していました。今後は、Navigation コンポーネントを使用する方法に置き換えられ、グラフ内でアクションとデスティネーションをリンクさせて、NavController
を使用して移動することで、簡単に設定できます。
実際に起きる可能性のあるシナリオと、各シナリオでの移行方法について以下に示します。
単一のアクティビティで複数のフラグメントを管理する場合
単一のアクティビティで複数のフラグメントを管理する場合、アクティビティ コードは次のようになります。
Kotlin
class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // Logic to load the starting destination // when the Activity is first created if (savedInstanceState == null) { val fragment = ProductListFragment() supportFragmentManager.beginTransaction() .add(R.id.fragment_container, fragment, ProductListFragment.TAG) .commit() } } // Logic to navigate the user to another destination. // This may include logic to initialize and set arguments on the destination // fragment or even transition animations between the fragments (not shown here). fun navigateToProductDetail(productId: String) { val fragment = new ProductDetailsFragment() val args = Bundle().apply { putInt(KEY_PRODUCT_ID, productId) } fragment.arguments = args supportFragmentManager.beginTransaction() .addToBackStack(ProductDetailsFragment.TAG) .replace(R.id.fragment_container, fragment, ProductDetailsFragment.TAG) .commit() } }
Java
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // Logic to load the starting destination when the activity is first created. if (savedInstanceState == null) { val fragment = ProductListFragment() supportFragmentManager.beginTransaction() .add(R.id.fragment_container, fragment, ProductListFragment.TAG) .commit(); } } // Logic to navigate the user to another destination. // This may include logic to initialize and set arguments on the destination // fragment or even transition animations between the fragments (not shown here). public void navigateToProductDetail(String productId) { Fragment fragment = new ProductDetailsFragment(); Bundle args = new Bundle(); args.putInt(KEY_PRODUCT_ID, productId); fragment.setArguments(args); getSupportFragmentManager().beginTransaction() .addToBackStack(ProductDetailsFragment.TAG) .replace(R.id.fragment_container, fragment, ProductDetailsFragment.TAG) .commit(); } }
ソース デスティネーションの内部では、以下のように、イベントに応答してナビゲーション関数を呼び出していることがあります。
Kotlin
class ProductListFragment : Fragment() { ... override fun onViewCreated(view: View, savedInstanceState: Bundle?) { // In this example a callback is passed to respond to an item clicked // in a RecyclerView productAdapter = ProductAdapter(productClickCallback) binding.productsList.setAdapter(productAdapter) } ... // The callback makes the call to the activity to make the transition. private val productClickCallback = ProductClickCallback { product -> (requireActivity() as MainActivity).navigateToProductDetail(product.id) } }
Java
public class ProductListFragment extends Fragment { ... @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { // In this example a callback is passed to respond to an item clicked in a RecyclerView productAdapter = new ProductAdapter(productClickCallback); binding.productsList.setAdapter(productAdapter); } ... // The callback makes the call to the activity to make the transition. private ProductClickCallback productClickCallback = product -> ( ((MainActivity) requireActivity()).navigateToProductDetail(product.getId()) ); }
これに代わる手法として、ナビゲーション グラフを更新します。開始デスティネーションを設定し、デスティネーション間をリンクするアクション、および引数を必要に応じて定義します。
<navigation 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:id="@+id/product_list_graph"
app:startDestination="@id/product_list">
<fragment
android:id="@+id/product_list"
android:name="com.example.android.persistence.ui.ProductListFragment"
android:label="Product List"
tools:layout="@layout/product_list">
<action
android:id="@+id/navigate_to_product_detail"
app:destination="@id/product_detail" />
</fragment>
<fragment
android:id="@+id/product_detail"
android:name="com.example.android.persistence.ui.ProductDetailFragment"
android:label="Product Detail"
tools:layout="@layout/product_detail">
<argument
android:name="product_id"
app:argType="integer" />
</fragment>
</navigation>
そして、アクティビティを更新します。
Kotlin
class MainActivity : AppCompatActivity() { // No need to load the start destination, handled automatically by the Navigation component override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) } }
Java
public class MainActivity extends AppCompatActivity { // No need to load the start destination, handled automatically by the Navigation component @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } }
アクティビティに navigateToProductDetail()
メソッドは不要になりました。次のセクションでは、NavController
を使用して次のアイテム情報画面に移動するように ProductListFragment
を更新します。
引数を安全に渡す
Navigation コンポーネントには、Safe Args と呼ばれる Gradle プラグインが含まれています。Safe Args は、デスティネーションとアクション用に指定された引数に対してタイプセーフにアクセスするためのシンプルなオブジェクトとビルダークラスを生成します。
このプラグインを適用し、ナビゲーション グラフ内のデスティネーションに対して引数を定義すると、Navigation コンポーネント フレームワークが、ターゲット デスティネーションへのタイプセーフな引数を提供する Arguments
クラスを生成します。アクションを定義すると、このプラグインは Directions
構成クラスを生成します。この構成クラスを使用することで、ユーザーをターゲット デスティネーションに移動させる方法を NavController
に伝えることができます。引数を必要とするデスティネーションをアクションがポイントしていた場合、生成される Directions
クラスには、そのようなパラメータを要求するコンストラクタ メソッドが組み込まれます。
フラグメント内で、NavController
と、生成された Directions
クラスを使用して、ターゲット デスティネーションへのタイプセーフ引数を提供します。次の例をご覧ください。
Kotlin
class ProductListFragment : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { // In this example a callback is passed to respond to an item clicked in a RecyclerView productAdapter = ProductAdapter(productClickCallback) binding.productsList.setAdapter(productAdapter) } ... // The callback makes the call to the NavController to make the transition. private val productClickCallback = ProductClickCallback { product -> val directions = ProductListDirections.navigateToProductDetail(product.id) findNavController().navigate(directions) } }
Java
public class ProductListFragment extends Fragment { ... @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { // In this example a callback is passed to respond to an item clicked in a RecyclerView productAdapter = new ProductAdapter(productClickCallback); binding.productsList.setAdapter(productAdapter); } ... // The callback makes the call to the activity to make the transition. private ProductClickCallback productClickCallback = product -> { ProductListDirections.ViewProductDetails directions = ProductListDirections.navigateToProductDetail(product.getId()); NavHostFragment.findNavController(this).navigate(directions); }; }
トップレベル ナビゲーション
DrawerLayout
を使用しているアプリの場合、ドロワーの開閉や他のデスティネーションへのナビゲーションを管理するアクティビティ内に、多数の構成ロジックが含まれることがあります。
その結果、アクティビティは以下のようになります。
Kotlin
class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelectedListener { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val toolbar: Toolbar = findViewById(R.id.toolbar) setSupportActionBar(toolbar) val drawerLayout: DrawerLayout = findViewById(R.id.drawer_layout) val navView: NavigationView = findViewById(R.id.nav_view) val toggle = ActionBarDrawerToggle( this, drawerLayout, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close ) drawerLayout.addDrawerListener(toggle) toggle.syncState() navView.setNavigationItemSelectedListener(this) } override fun onBackPressed() { val drawerLayout: DrawerLayout = findViewById(R.id.drawer_layout) if (drawerLayout.isDrawerOpen(GravityCompat.START)) { drawerLayout.closeDrawer(GravityCompat.START) } else { super.onBackPressed() } } override fun onNavigationItemSelected(item: MenuItem): Boolean { // Handle navigation view item clicks here. when (item.itemId) { R.id.home -> { val homeFragment = HomeFragment() show(homeFragment) } R.id.gallery -> { val galleryFragment = GalleryFragment() show(galleryFragment) } R.id.slide_show -> { val slideShowFragment = SlideShowFragment() show(slideShowFragment) } R.id.tools -> { val toolsFragment = ToolsFragment() show(toolsFragment) } } val drawerLayout: DrawerLayout = findViewById(R.id.drawer_layout) drawerLayout.closeDrawer(GravityCompat.START) return true } } private fun show(fragment: Fragment) { val drawerLayout = drawer_layout as DrawerLayout val fragmentManager = supportFragmentManager fragmentManager .beginTransaction() .replace(R.id.main_content, fragment) .commit() drawerLayout.closeDrawer(GravityCompat.START) }
Java
public class MainActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Toolbar toolbar = findViewById(R.id.toolbar); setSupportActionBar(toolbar); DrawerLayout drawer = findViewById(R.id.drawer_layout); NavigationView navigationView = findViewById(R.id.nav_view); ActionBarDrawerToggle toggle = new ActionBarDrawerToggle( this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close); drawer.addDrawerListener(toggle); toggle.syncState(); navigationView.setNavigationItemSelectedListener(this); } @Override public void onBackPressed() { DrawerLayout drawer = findViewById(R.id.drawer_layout); if (drawer.isDrawerOpen(GravityCompat.START)) { drawer.closeDrawer(GravityCompat.START); } else { super.onBackPressed(); } } @Override public boolean onNavigationItemSelected(MenuItem item) { // Handle navigation view item clicks here. int id = item.getItemId(); if (id == R.id.home) { Fragment homeFragment = new HomeFragment(); show(homeFragment); } else if (id == R.id.gallery) { Fragment galleryFragment = new GalleryFragment(); show(galleryFragment); } else if (id == R.id.slide_show) { Fragment slideShowFragment = new SlideShowFragment(); show(slideShowFragment); } else if (id == R.id.tools) { Fragment toolsFragment = new ToolsFragment(); show(toolsFragment); } DrawerLayout drawer = findViewById(R.id.drawer_layout); drawer.closeDrawer(GravityCompat.START); return true; } private void show(Fragment fragment) { DrawerLayout drawerLayout = findViewById(R.id.drawer_layout); FragmentManager fragmentManager = getSupportFragmentManager(); fragmentManager .beginTransaction() .replace(R.id.main_content, fragment) .commit(); drawerLayout.closeDrawer(GravityCompat.START); } }
Navigation コンポーネントをプロジェクトに追加してナビゲーション グラフを作成したら、グラフから各コンテンツ デスティネーション(上記の例の場合、「Home」、「Gallery」、「SlideShow」、「Tools」)を追加します。メニュー項目の id
値と、関連付けられたデスティネーションの id
値が一致している必要があります。以下をご覧ください。
<!-- activity_main_drawer.xml -->
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
tools:showIn="navigation_view">
<group android:checkableBehavior="single">
<item
android:id="@+id/home"
android:icon="@drawable/ic_menu_camera"
android:title="@string/menu_home" />
<item
android:id="@+id/gallery"
android:icon="@drawable/ic_menu_gallery"
android:title="@string/menu_gallery" />
<item
android:id="@+id/slide_show"
android:icon="@drawable/ic_menu_slideshow"
android:title="@string/menu_slideshow" />
<item
android:id="@+id/tools"
android:icon="@drawable/ic_menu_manage"
android:title="@string/menu_tools" />
</group>
</menu>
<!-- activity_main_graph.xml -->
<navigation 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:id="@+id/main_graph"
app:startDestination="@id/home">
<fragment
android:id="@+id/home"
android:name="com.example.HomeFragment"
android:label="Home"
tools:layout="@layout/home" />
<fragment
android:id="@+id/gallery"
android:name="com.example.GalleryFragment"
android:label="Gallery"
tools:layout="@layout/gallery" />
<fragment
android:id="@+id/slide_show"
android:name="com.example.SlideShowFragment"
android:label="Slide Show"
tools:layout="@layout/slide_show" />
<fragment
android:id="@+id/tools"
android:name="com.example.ToolsFragment"
android:label="Tools"
tools:layout="@layout/tools" />
</navigation>
メニューとグラフの id
値が一致していると、このアクティビティに対して NavController
を接続することで、メニュー項目に基づいて自動的にナビゲーションを処理できるようになります。また、NavController
により、DrawerLayout
の開閉や、[上へ] ボタンの動作、[戻る] ボタンの動作を適切に処理できます。
そして、MainActivity
を更新して、NavController
を Toolbar
と NavigationView
に接続できます。
例として、次のスニペットをご覧ください。
Kotlin
class MainActivity : AppCompatActivity() { val drawerLayout by lazy { findViewById<DrawerLayout>(R.id.drawer_layout) } val navController by lazy { (supportFragmentManager.findFragmentById(R.id.main_content) as NavHostFragment).navController } val navigationView by lazy { findViewById<NavigationView>(R.id.nav_view) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val toolbar = findViewById<Toolbar>(R.id.toolbar) setSupportActionBar(toolbar) // Show and Manage the Drawer and Back Icon setupActionBarWithNavController(navController, drawerLayout) // Handle Navigation item clicks // This works with no further action on your part if the menu and destination id’s match. navigationView.setupWithNavController(navController) } override fun onSupportNavigateUp(): Boolean { // Allows NavigationUI to support proper up navigation or the drawer layout // drawer menu, depending on the situation return navController.navigateUp(drawerLayout) } }
Java
public class MainActivity extends AppCompatActivity { private DrawerLayout drawerLayout; private NavController navController; private NavigationView navigationView; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); drawerLayout = findViewById(R.id.drawer_layout); NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.main_content); navController = navHostFragment.getNavController(); navigationView = findViewById(R.id.nav_view); Toolbar toolbar = findViewById(R.id.toolbar); setSupportActionBar(toolbar); // Show and Manage the Drawer and Back Icon NavigationUI.setupActionBarWithNavController(this, navController, drawerLayout); // Handle Navigation item clicks // This works with no further action on your part if the menu and destination id’s match. NavigationUI.setupWithNavController(navigationView, navController); } @Override public boolean onSupportNavigateUp() { // Allows NavigationUI to support proper up navigation or the drawer layout // drawer menu, depending on the situation. return NavigationUI.navigateUp(navController, drawerLayout); } }
BottomNavigationView ベースのナビゲーションやメニューベースのナビゲーションでも、同じ手法を使用できます。他の例については、NavigationUI を使用して UI コンポーネントを更新するをご覧ください。
アクティビティ デスティネーションを追加する
Navigation コンポーネントを使用するようにアプリ内の各画面を設定し、FragmentTransactions
によるフラグメント ベース デスティネーション間の遷移を削除したら、次のステップとして、startActivity
呼び出しを削除します。
まず、2 つのナビゲーション グラフと、startActivity
を使用してグラフ間を遷移しているアプリ内の場所を見つけます。
この例には、2 つのグラフ(A、B)と、A から B への遷移を行う startActivity()
呼び出しがあります。
Kotlin
fun navigateToProductDetails(productId: String) { val intent = Intent(this, ProductDetailsActivity::class.java) intent.putExtra(KEY_PRODUCT_ID, productId) startActivity(intent) }
Java
private void navigateToProductDetails(String productId) { Intent intent = new Intent(this, ProductDetailsActivity.class); intent.putExtra(KEY_PRODUCT_ID, productId); startActivity(intent);
次に、この設定をグラフ A 内のアクティビティ デスティネーションに置き換えて、グラフ B のホスト アクティビティに対するナビゲーションを指定します。グラフ B の開始デスティネーションに渡す引数がある場合は、アクティビティ デスティネーション定義内で指定できます。
次の例の場合、グラフ A は、アクションと一緒に product_id
引数を取るアクティビティ デスティネーションを定義しています。グラフ B に変更はありません。
グラフ A とグラフ B の XML 表現は次のようになります。
<!-- Graph A -->
<navigation 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:id="@+id/product_list_graph"
app:startDestination="@id/product_list">
<fragment
android:id="@+id/product_list"
android:name="com.example.android.persistence.ui.ProductListFragment"
android:label="Product List"
tools:layout="@layout/product_list_fragment">
<action
android:id="@+id/navigate_to_product_detail"
app:destination="@id/product_details_activity" />
</fragment>
<activity
android:id="@+id/product_details_activity"
android:name="com.example.android.persistence.ui.ProductDetailsActivity"
android:label="Product Details"
tools:layout="@layout/product_details_host">
<argument
android:name="product_id"
app:argType="integer" />
</activity>
</navigation>
<!-- Graph B -->
<navigation 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"
app:startDestination="@id/product_details">
<fragment
android:id="@+id/product_details"
android:name="com.example.android.persistence.ui.ProductDetailsFragment"
android:label="Product Details"
tools:layout="@layout/product_details_fragment">
<argument
android:name="product_id"
app:argType="integer" />
</fragment>
</navigation>
フラグメント デスティネーションに移動する場合と同じメカニズムを使用して、グラフ B のホスト アクティビティに移動できます。
Kotlin
fun navigateToProductDetails(productId: String) { val directions = ProductListDirections.navigateToProductDetail(productId) findNavController().navigate(directions) }
Java
private void navigateToProductDetails(String productId) { ProductListDirections.NavigateToProductDetail directions = ProductListDirections.navigateToProductDetail(productId); Navigation.findNavController(getView()).navigate(directions);
アクティビティ デスティネーション引数を開始デスティネーション フラグメントに渡す
上記の例のように、デスティネーション アクティビティがエクストラを受け取る場合、引数として開始デスティネーションに直接渡すことができます。ただし、ホスト アクティビティの onCreate()
メソッド内で、ホストのナビゲーション グラフを手動で設定して、インテント エクストラを引数としてフラグメントに渡すことができるようにする必要があります。以下をご覧ください。
Kotlin
class ProductDetailsActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.product_details_host) val navHostFragment = supportFragmentManager.findFragmentById(R.id.main_content) as NavHostFragment val navController = navHostFramgent.navController navController .setGraph(R.navigation.product_detail_graph, intent.extras) } }
Java
public class ProductDetailsActivity extends AppCompatActivity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.product_details_host); NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.main_content); NavController navController = navHostFragment.getNavController(); navController .setGraph(R.navigation.product_detail_graph, getIntent().getExtras()); } }
生成された args クラスを使用して、フラグメント引数 Bundle
からデータを引き出すことができます。次の例をご覧ください。
Kotlin
class ProductDetailsFragment : Fragment() { val args by navArgs<ProductDetailsArgs>() override fun onViewCreated(view: View, savedInstanceState: Bundle?) { val productId = args.productId ... } ...
Java
public class ProductDetailsFragment extends Fragment { ProductDetailsArgs args; @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); args = ProductDetailsArgs.fromBundle(requireArguments()); } @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { int productId = args.getProductId(); ... } ...
アクティビティを結合する
単一のフラグメントで構成されるシンプルな FrameLayout
など、複数のアクティビティが同じレイアウトを共有している場合、ナビゲーション グラフを結合できます。ほとんどの場合、各ナビゲーション グラフのすべての要素を結合し、すべてのアクティビティ デスティネーション要素をフラグメント デスティネーションに更新するだけで済みます。
上記のセクションのグラフ A とグラフ B を結合する例を以下に示します。
結合前:
<!-- Graph A -->
<navigation 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:id="@+id/product_list_graph"
app:startDestination="@id/product_list">
<fragment
android:id="@+id/product_list"
android:name="com.example.android.persistence.ui.ProductListFragment"
android:label="Product List Fragment"
tools:layout="@layout/product_list">
<action
android:id="@+id/navigate_to_product_detail"
app:destination="@id/product_details_activity" />
</fragment>
<activity
android:id="@+id/product_details_activity"
android:name="com.example.android.persistence.ui.ProductDetailsActivity"
android:label="Product Details Host"
tools:layout="@layout/product_details_host">
<argument android:name="product_id"
app:argType="integer" />
</activity>
</navigation>
<!-- Graph B -->
<navigation 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:id="@+id/product_detail_graph"
app:startDestination="@id/product_details">
<fragment
android:id="@+id/product_details"
android:name="com.example.android.persistence.ui.ProductDetailsFragment"
android:label="Product Details"
tools:layout="@layout/product_details">
<argument
android:name="product_id"
app:argType="integer" />
</fragment>
</navigation>
結合後:
<!-- Combined Graph A and B -->
<navigation 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:id="@+id/product_list_graph"
app:startDestination="@id/product_list">
<fragment
android:id="@+id/product_list"
android:name="com.example.android.persistence.ui.ProductListFragment"
android:label="Product List Fragment"
tools:layout="@layout/product_list">
<action
android:id="@+id/navigate_to_product_detail"
app:destination="@id/product_details" />
</fragment>
<fragment
android:id="@+id/product_details"
android:name="com.example.android.persistence.ui.ProductDetailsFragment"
android:label="Product Details"
tools:layout="@layout/product_details">
<argument
android:name="product_id"
app:argType="integer" />
</fragment>
</navigation>
アクション名を変えずにマージすると、既存のコードベースを変更する必要がなく、シームレスなプロセスにすることができます。たとえば、この例の場合は navigateToProductDetail
のままにします。唯一の違いは、このアクションが、アクティビティ デスティネーションへのナビゲーションではなく、同じ NavHost
内のフラグメント デスティネーションへのナビゲーションを示すようになったことです。
Kotlin
fun navigateToProductDetails(productId: String) { val directions = ProductListDirections.navigateToProductDetail(productId) findNavController().navigate(directions) }
Java
private void navigateToProductDetails(String productId) { ProductListDirections.NavigateToProductDetail directions = ProductListDirections.navigateToProductDetail(productId); Navigation.findNavController(getView()).navigate(directions);
参考情報
ナビゲーション関連の詳細については、以下のトピックをご覧ください。
- NavigationUI を使用して UI コンポーネントを更新する - トップ アプリバーやナビゲーション ドロワー、ボトム ナビゲーションを使用してナビゲーションを管理する方法について学びます。
- ナビゲーションをテストする - アプリのナビゲーション ワークフローをテストする方法について学びます。