Androidナビゲーションコンポーネント。 自分でしなければならない簡単なこと

みなさんこんにちは! Navigation Architecture Componentの作業の機能についてお話したいと思います。その理由は、ライブラリーの印象が曖昧だったからです。



この記事はステップバイステップのガイドではなく、実装の詳細を省略して重要な点に焦点を当てています。 インターネットにも同様のユースケースが多数あります(翻訳もあります)-ライブラリを理解するのに役立ちます。 また、読む前に、 ドキュメントを勉強することを提案します



画像



ライブラリは間違いなく有用であり、誤用の可能性を排除するものではないことをすぐに言っておく必要がありますが、おそらくこの記事を書く前にすべてを試しました。



そのため、機能の期待が実装の現実と一致しなかった実装のシナリオは次のとおりです。





メニュー項目を切り替える



これは、ナビゲーションコンポーネントを使用する決定に影響を与えた機能の1つです。



メニュー項目IDを同一にする必要があります



activity_main_drawer.xml
<?xml version="1.0" encoding="utf-8"?> <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/importFragment" android:icon="@drawable/ic_menu_camera" android:title="Import"/> <item android:id="@+id/galleryFragment" android:icon="@drawable/ic_menu_gallery" android:title="Gallery"/> <item android:id="@+id/slideshowFragment" android:icon="@drawable/ic_menu_slideshow" android:title="Slideshow"/> <!--    -->
      
      







およびスクリーンID(ナビゲーショングラフの目的地)



mobile_navigation.xml
 <?xml version="1.0" encoding="utf-8"?> <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/mobile_navigation" app:startDestination="@id/importFragment"> <fragment android:id="@+id/importFragment" android:name="com.xiii.navigationapplication.ImportFragment" android:label="fragment_import" tools:layout="@layout/fragment_import"/> <fragment android:id="@+id/galleryFragment" android:name="com.xiii.navigationapplication.GalleryFragment" android:label="fragment_gallery" tools:layout="@layout/fragment_gallery"/> <fragment android:id="@+id/slideshowFragment" android:name="com.xiii.navigationapplication.SlideshowFragment" android:label="fragment_slideshow" tools:layout="@layout/fragment_slideshow"/> </navigation>
      
      







次に、メニューをNavigation Controllerに関連付ける必要があります。



MainActivity.kt
 class MainActivity : AppCompatActivity() { private val navController by lazy(LazyThreadSafetyMode.NONE) { Navigation.findNavController(this, R.id.nav_host_fragment) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) setSupportActionBar(toolbar) //   ""  toolbar NavigationUI.setupWithNavController(toolbar, navController, drawer_layout) //      nav_view.setupWithNavController(navController) } //     override fun onSupportNavigateUp() = navController.navigateUp() }
      
      







メニュー内のナビゲーションが獲得されました-それは奇跡ではありませんか?!



メニューナビゲーション






「ハンバーガー」(メニューアイコン)に注意してください。メニュー項目を切り替えると、その状態が「戻る」ボタンに変わります。 この振る舞いは珍しいようで( おなじみ -プレイマーケットアプリケーションのように)、しばらくの間、何が間違っていたかを把握しようとしましたか?



それだけです! ナビゲーションの原則(つまり、ポイント2および3 )に関するドキュメントを読んだ後、「ハンバーガー」はstartDestinationに対してのみ表示されることに気付きました 。 状況は、サブスクリプション( addOnNavigatedListener() )でさまざまなトリックを適用して宛先を変更することで変更できますが、説明することすらありません。 これはこのように機能します。条件に合わせる必要があります。



新しいアクティビティを開く



アクティビティはナビゲーションホストとして機能すると同時に、ナビゲーショングラフで目的地の 1つとして機能できます 。 ネストされたナビゲーショングラフなしでアクティビティを開くと、 期待どおりに動作ます、つまり呼び出し:



 navController.navigate(R.id.editActivity)
      
      





(フラグメントの場合のように)遷移を行い、要求されたアクティビティを開きます。

ターゲットActivity自体がナビゲーションホストとして機能する場合、つまりドキュメントのオプション2の場合を考えると、さらに興味深いものになります



ナビゲーションスキーム



例として、メモを追加するアクティビティを見てみましょう。 これには、EditFragment入力フィールドを持つメインフラグメントが含まれ、ナビゲーショングラフではstartDestinationになります。 編集する際に写真を添付する必要があると仮定しましょう。そのためには、 PhotoFragmentに移動してカメラから画像を取得します。 ナビゲーショングラフは次のようになります。



edit_navigation.xml
 <?xml version="1.0" encoding="utf-8"?> <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/edit_navigation" app:startDestination="@id/editFragment"> <fragment android:id="@+id/editFragment" android:name="com.xiii.navigationapplication.ui.edit.EditFragment" android:label="fragment_edit" tools:layout="@layout/fragment_edit"> <action android:id="@+id/action_editFragment_to_photoFragment" app:destination="@id/photoFragment"/> </fragment> <fragment android:id="@+id/photoFragment" android:name="com.xiii.navigationapplication.ui.edit.PhotoFragment" android:label="fragment_photo" tools:layout="@layout/fragment_photo"/> </navigation>
      
      







EditActivityはMainActivityとそれほど変わりません。 主な違いは、 EditActivityにメニューがないことです。



EditActivity.kt
 class EditActivity : AppCompatActivity() { private val navController by lazy(LazyThreadSafetyMode.NONE) { Navigation.findNavController(this, R.id.nav_host_fragment) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_edit) setSupportActionBar(toolbar) //    ""  toolbar NavigationUI.setupWithNavController(toolbar, navController) } override fun onSupportNavigateUp() = navController.navigateUp() fun takePhoto(view: View) { navController.navigate(R.id.action_editFragment_to_photoFragment) } }
      
      







アクティビティが開き、その中のナビゲーションが機能します:



アクティビティナビゲーションを編集






繰り返しますが、ツールバーのナビゲーションボタンに注意してください-開始EditFragmentには「親アクティビティに戻る」ボタンはありません(しかし、私はしたいです)。 ドキュメントの観点から見ると、ここではすべてが合法です。新しいナビゲーショングラフ、新しいstartDestination値、「戻る」ボタンはstartDestinationの最後には表示されません。



親アクティビティを使用して通常の動作に戻りたい場合は、フラグメント間の切り替え機能を維持しながら、このような松葉杖のアプローチを提供できます。



1.マニフェストで親アクティビティを指定します
 <activity android:name=".EditActivity" android:parentActivityName=".MainActivity" android:theme="@style/AppTheme.NoActionBar"> <!-- Parent activity meta-data to support 4.0 and lower --> <meta-data android:name="android.support.PARENT_ACTIVITY" android:value=".MainActivity" /> </activity>
      
      







2. id startDestinationを置き換えるサブスクリプションを追加します
 class EditActivity : AppCompatActivity() { private val navController by lazy(LazyThreadSafetyMode.NONE) { Navigation.findNavController(this, R.id.nav_host_fragment) } private var isStartDestination = true override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_edit) setSupportActionBar(toolbar) val startDestinationId = navController.graph.startDestination //    id,  NavigationUI.ActionBarOnNavigatedListener     //      destination    startDestination navController.addOnNavigatedListener { controller, destination -> isStartDestination = destination.id == startDestinationId // R.id.fake_start_destination  id        controller.graph.startDestination = if (isStartDestination) R.id.fake_start_destination else startDestinationId } //    ""  toolbar NavigationUI.setupActionBarWithNavController(this, navController) } override fun onSupportNavigateUp(): Boolean { //  startDestination      Navigation Component return if (isStartDestination) super.onSupportNavigateUp() else navController.navigateUp() } fun takePhoto(view: View) { navController.navigate(R.id.action_editFragment_to_photoFragment) } }
      
      







NavigationUI.ActionBarOnNavigatedListenerの すべての宛先が startDestinationにならないように、サブスクリプションが必要です 。 したがって、 NavigationUI.ActionBarOnNavigatedListenerはナビゲーションボタンを非表示にしません(詳細についてはソースを参照してください)。 これに、 startDestinationで定期的にonSupportNavigateUp()処理を追加し、 必要なものを取得します。



ライブラリの動作に対する明白でない介入であるという理由だけで、ソリューションが理想からかけ離れていることは言うに値します。 ディープリンクを使用すると問題が発生する可能性があると考えています (まだテストしていません)。



startDestinationにパラメーターを渡す



ナビゲーションコンポーネントには、ある宛先から別の宛先にパラメータ渡すメカニズムがあります。 コード生成によって型の安全性確保するツールもあります (悪くない)。



次に、ケースを分析します。そのため、この機能にしっかりした5つを付けることができませんでした。



EditActivityに戻りましょう。これは、1つのActivityを使用してオブジェクトを作成および編集する、かなり馴染みのあるシナリオです。 Activityで編集するためにオブジェクトを開くとき、たとえば、オブジェクトのIDを転送する必要があります-通常の方法でそれをしましょう:



1. EditActivityのパラメーターをグラフに追加します
グラフのルート要素に直接パラメーターを追加しました(ナビゲーション)が、ターゲットフラグメントに追加できます。 これから、パラメータを取得する方法のみが変更されます。



 <?xml version="1.0" encoding="utf-8"?> <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/edit_navigation" app:startDestination="@id/editFragment"> <argument android:name="id" app:argType="integer"/> <fragment android:id="@+id/editFragment" android:name="com.xiii.navigationapplication.ui.edit.EditFragment" android:label="fragment_edit" tools:layout="@layout/fragment_edit"> <action android:id="@+id/action_editFragment_to_photoFragment" app:destination="@id/photoFragment"/> </fragment> <fragment android:id="@+id/photoFragment" android:name="com.xiii.navigationapplication.ui.edit.PhotoFragment" android:label="fragment_photo" tools:layout="@layout/fragment_photo"/> </navigation>
      
      







2.アクションをメイングラフに追加します
追加および編集アクションをフラグメントの1つに追加したので、フラグメントからのみ使用可能になります。



 <?xml version="1.0" encoding="utf-8"?> <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/mobile_navigation" app:startDestination="@id/importFragment"> <fragment android:id="@+id/importFragment" android:name="com.xiii.navigationapplication.ImportFragment" android:label="fragment_import" tools:layout="@layout/fragment_import"> <action android:id="@+id/add" app:destination="@id/editActivity"> <argument android:name="id" app:argType="integer" android:defaultValue="0"/> </action> <action android:id="@+id/edit" app:destination="@id/editActivity"> <argument android:name="id" app:argType="integer"/> </action> </fragment> <fragment android:id="@+id/galleryFragment" android:name="com.xiii.navigationapplication.GalleryFragment" android:label="fragment_gallery" tools:layout="@layout/fragment_gallery"/> <fragment android:id="@+id/slideshowFragment" android:name="com.xiii.navigationapplication.SlideshowFragment" android:label="fragment_slideshow" tools:layout="@layout/fragment_slideshow"/> <activity android:id="@+id/editActivity" android:name="com.xiii.navigationapplication.EditActivity" android:label="activity_edit" tools:layout="@layout/activity_edit"/> </navigation>
      
      







3.パラメーターを準備し、移行をリクエストする
この例では、 ImportFragmentDirectionsは自動生成されたsafe-argsクラスです。



 val direction = ImportFragmentDirections.edit(123 /* id  */) navController.navigate(direction)
      
      







3.フラグメントのIDを取得します
 class EditFragment : Fragment() { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { //    ,  fragment     startDestination // val id = EditFragmentArgs.fromBundle(arguments) val id = EditFragmentArgs.fromBundle(requireActivity().intent.extras) return inflater.inflate(R.layout.fragment_edit, container, false) } }
      
      







確かに、あなたはEditFragmentでパラメーターを取得する機能に注意を払いました 。 これが機能するのは、編集アクション(ポイント1から)が引数をEditActivityに渡すためであり、その理由のために、何らかの理由で、それをグラフに渡さないように貪欲です(たとえば、 navController.graph.setDefaultArguments()を呼び出して)。 この機能は、 Navigation Controllerを手動で準備することで回避できます。 1つの方法はStackOwerflowで説明されています



startDestinationと通常の宛先として同時に使用すると、おそらく最大の困難が発生します 。 つまりこのグラフの他の宛先からstartDestinationにパラメーターを渡したり受け渡したりする場合、フラグメントは、引数またはintent.extrasからパラメーターを抽出する場所を個別に決定する必要があります。 これは、パラメータを渡すトランジションを設計するときに留意する必要があります。






要約すると、私自身はライブラリの使用を停止していないことに注意してください。リストされている機能の欠点にもかかわらず、使用することをお勧めするのに十分役立つと思います。 少なくともstartDestinationにパラメーターを転送することで、次のリリースで状況が変わることを本当に願っています。



ご清聴ありがとうございました。 あなたの作業コード!



記事のソースはGitHubに投稿されています



All Articles