Android导航组件。 简单的事情,你必须自己做

大家好! 我想谈谈Navigation Architecture Component工作中的功能,因此我对库有一个模糊的印象。

本文不是循序渐进的指南;它省略了实现细节来重点关注关键点。 互联网上有许多类似的用例(也有翻译)-它们将帮助您熟悉图书馆。 另外,在阅读之前,我建议研究文档

图片

我必须马上说,我绝对认为该库很有用,并且不排除滥用的可能性,但是我可能在写本文之前就尝试了所有方法。

因此,以下是实现中的功能预期与实现中的实际情况不符的场景:

  • 在导航抽屉中的菜单项之间切换
  • 通过导航图发现新的活动
  • 将参数传递给startDestination

在菜单项之间切换


这是影响使用导航组件的决定的那些功能之一。

您只需要使菜单项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> 


那么您需要将菜单与导航控制器相关联:

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() } 


菜单中的导航有效-这不是奇迹吗?

菜单导航

注意“汉堡”(菜单图标),当在菜单项之间切换时,它的状态变为“后退”按钮。 这种行为似乎很不寻常很熟悉 -就像在游戏市场应用程序中一样),并且有一段时间,我试图找出出什么问题了?

仅此而已! 在阅读了有关导航原理的文档(即,第二点和第三点)之后,我意识到“汉堡”仅针对startDestination显示 ,或者这样显示:除startDestination之外,所有用户均显示后退按钮。 可以通过在订阅中应用各种技巧( addOnNavigatedListener() )来更改目的地 ,从而改变这种情况,但是甚至不应对其进行描述。 它的工作原理是这样的,您需要达成共识。

开启新活动


活动可以充当导航主机 ,同时,在导航图中可以充当目的地之一 。 打开没有嵌套导航图的Activity可以按预期进行 ,即调用:

 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> 


EditActivityMainActivity没有太大区别。 主要区别在于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 ,这是一个相当熟悉的场景,其中一个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.将动作添加到主图中
我在其中一个片段中添加了添加和编辑操作,因此只能从中使用它们。

 <?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中获取参数的功能 。 之所以可行,是因为edit动作(从点1开始)将参数传递给EditActivity ,并且出于某种原因,它很贪婪地没有将其传递给图形(例如,通过调用navController.graph.setDefaultArguments() )。 可以通过手动准备导航控制器来规避此功能。 在StackOwerflow上描述了一种方法。

当同时用作startDestination和通常的目的地时,可能会遇到最大的困难。 也就是说,当从该图的任何其他目标传递参数并将其传递给startDestination时 ,该片段将必须独立地确定从以下位置提取参数的位置:参数或intent.extras。 在设计带有传递参数的过渡时必须牢记这一点。



总而言之,我想指出的是,我本人并没有停止使用该库,尽管列出了该功能的缺点 ,但我发现它足以推荐使用。 我真的希望在下一个发行版中,情况至少会发生变化,至少将参数传递给startDestination

谢谢您的关注。 您的工作代码!

文章的来源发布在GitHub上

Source: https://habr.com/ru/post/zh-CN429152/


All Articles