Android学习笔记七(JAVA):初识Navigation组件
上篇笔记中,实现了RecyclerView显示的笔记列表,本篇笔记实现下方动图所示的功能,即点击笔记列表中的某一项,能够跳转到笔记详情页面。实现该功能,使用Navigation组件,即导航组件。
1.导航组件的简介以及配置
导航是指支持用户导航、进入和退出应用中不同内容片段的交互。更具体地,Navigation组件旨在实现托管于一个主activity上的多个fragments之间的交互。关于导航原则,请参见:https://developer.android.google.cn/guide/navigation/navigation-principles?hl=zh-cn
Navigation组件包含三个关键部分:
1.导航图(Navigation graph),在一个集中位置包含所有导航相关信息的XML资源。包括应用内所有单个内容区域(称为目标)以及用户可以通过应用获取的可能路径。
2.导航宿主(NavHost), 显示导航图中目标的空白容器。用户在应用中导航时,目的地会在该容器中交换进出。导航组件包含一个默认的NavHost实现—NavHostFragment,可显示Fragment目标。
3.NavController,在NavHost中管理应用导航的对象。当用户在整个应用中移动时,NavController会安排NavHost中目标内容的交换。
主Activity与导航图相关联,且包含一个负责根据需要交换目的地的NavHostFragment。在具有多个Activity目的地的应用中,每个Activity均有自己专属的导航图。
使用导航组件具有很多优点,包括:
- 处理Fragment事务,不用再显示使用FragmentManager类了。
- 默认情况下,能够正确处理往返操作。
- 为动画和转换提供标准化资源。
- 实现和处理深层链接。
- 包括导航界面模式(例如抽屉式导航栏和底部导航),用户只需完成极少的额外工作。
- Safe Args,可在目标之间导航和传递数据时提供类型安全的Gradle插件。
- 支持将ViewModel的范围限定为导航图,以在图表的目标之间共享与界面相关的数据。
使用导航组件,需要添加依赖。有两种方法可以添加依赖:
1.如下方动图所示。依次点击File->Project Structure->app,添加dependencies。在新打开的页面中输入androidx.navigation,然后检索。添加androidx.navigation:navigation-fragment和androidx.navigation:navigation-ui依赖项。添加后,可以看到build.gradle(module)文件中dependencies节点下多了这两个依赖的实现。
2.第二种添加依赖的方法就是直接在build.gradle(module)的dependencies节点下输入,然后别忘了点击Sync gradle:
def nav_version = "2.5.1"
implementation "androidx.navigation:navigation-fragment:$nav_version"
implementation "androidx.navigation:navigation-ui:$nav_version"
如果要使用Safe Args,还需要进行一些配置。在build.gradle(project)的顶部添加下面的代码:
buildscript {
repositories {
google()
}
dependencies {
def nav_version = "2.5.1"
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
}
}
在build.gradle(module)的plugins节点添加:
id 'androidx.navigation.safeargs'
2.导航组件的使用
第一步,创建导航图。在res文件夹处右键,选择New->Android Resource File。按照下图所示新建导航图XML文件,命名为nav_graph。
打开nav_graph.xml文件,选择Design模式,按下方动图所示依次添加NoteListFragment和NoteFragment为目的地。
现在点击Code模式,看一下这一番操作生成了什么样的代码。nav_graph.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/nav_graph" app:startDestination="@id/noteListFragment"> <fragment android:id="@+id/noteListFragment" android:name="com.larissa.android.note.NoteListFragment" android:label="NoteListFragment" > </fragment> <fragment android:id="@+id/noteFragment" android:name="com.larissa.android.note.NoteFragment" android:label="NoteFragment" /> </navigation>
<navigation>节点下包含两个<fragment>子节点。第一个<fragment>子节点是NoteListFragment,第二个<fragment>子节点是NoteFragment。现在给nav_graph.xml添加一些代码。修改后的nav_graph.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/nav_graph" app:startDestination="@id/noteListFragment"> <fragment android:id="@+id/noteListFragment" android:name="com.larissa.android.note.NoteListFragment" android:label="NoteListFragment" tools:layout="@layout/fragment_note_list"> </fragment> <fragment android:id="@+id/noteFragment" android:name="com.larissa.android.note.NoteFragment" android:label="NoteFragment" tools:layout="@layout/fragment_note"/> </navigation>
至此,导航图已经弄好了。关于导航图更多内容,请查看:https://developer.android.google.cn/guide/navigation/navigation-design-graph?hl=zh-cn
第二步,向Activity添加导航宿主NavHost。我们使用默认的NavHost实现(NavHostFragment)。在activity_main.xml中添加下方代码。
activity_main.xml代码清单:
<?xml version="1.0" encoding="utf-8"?> <androidx.fragment.app.FragmentContainerView 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" android:id="@+id/fragment_container" android:name="androidx.navigation.fragment.NavHostFragment" app:defaultNavHost="true" app:navGraph="@navigation/nav_graph" tools:context=".MainActivity"> </androidx.fragment.app.FragmentContainerView>
然后要在MainActivity.java中把托管Fragment的代码删掉。MainActivity.java代码清单:
public class MainActivity extends AppCompatActivity { private static final String TAG="MainActivity"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Log.d(TAG,"onCreate() called"); setContentView(R.layout.activity_main); if(savedInstanceState==null){ getSupportFragmentManager().beginTransaction() .add(R.id.fragment_container,NoteListFragment.class,null) .commit(); } } }
第三步,使用NavController完成导航。每个NavHost均有自己相应的NavController,获得NavController可以用以下三种方式:
- NavHostFragment.findNavController(Fragment)
- Navigation.findNavController(Activity, @IdRes int viewId)
- Navigation.findNavController(View)
获取到NavController之后,调用navigate()方法,实现导航。navigate()有15种调用方式。可以用int类型的资源Id作为参数调用,也可以用NavDirections类型的操作作为参数调用。我们先来用资源Id调用一下试试,即用下面这个调用方法:
其中,resId是目标Fragment的资源Id,args是附加的参数。在Note中,从NoteListFragment导航到NoteFragment,是需要把所选中的Note的数据一起导航过去的。这些数据就放在args中。
在NoteListFragment.java中的NoteItemHolder类中的构造方法中添加下面代码:
public NoteItemHolder(View view){ super(view); mTitleTextView=(TextView) view.findViewById(R.id.note_title); mLogTextView=(TextView) view.findViewById(R.id.note_log); mImageButton=(ImageButton) view.findViewById(R.id.note_delete); view.setOnClickListener(itemView -> { Bundle bundle=new Bundle(); ArrayList<String> noteDetails=new ArrayList<>(); noteDetails.add(mNote.getTitle()); noteDetails.add(mNote.getContent()); noteDetails.add(mNote.getNoteLog()); bundle.putStringArrayList("NoteDetail",noteDetails); Navigation.findNavController(itemView).navigate(R.id.noteFragment,bundle); }); }
给View添加了点击监听器,使用Navigation.findNavController(view)的方式获取NavController。然后还需要NoteFragment.java中把onCreate()方法改一下,获取bundle的数据。
@Override public void onCreate(Bundle savedInstanceState){ super.onCreate(savedInstanceState); Log.d(TAG,"onCreate() called"); mNote=new Note(); ArrayList<String>noteDetails=getArguments().getStringArrayList("NoteDetail"); mNote.setTitle(noteDetails.get(0)); mNote.setContent(noteDetails.get(1)); mNote.setNoteLog(noteDetails.get(2)); }
Run app,应该已经能够实现本篇开头所示的功能了。另外,这里还给Fragment的ActionBar的title赋值了,现在actionBar的title为各自的名称。分别在NoteListFragment.java和NoteFragment.java中的onCreateView()方法中添加下面的语句:
ActionBar actionBar=((AppCompatActivity) getActivity()).getSupportActionBar();
actionBar.setTitle(TAG);
3.通过SafeArgs传递数据
与第二部分不同的是,现在我们使用SafeArgs在NoteListFragment和NoteFragment之间传递数据。Safe Args是Navigation组件中的一个插件,可以生成简单的object类和builder类,支持以类型安全的方式浏览和访问任何关联数据。Android强烈建议将Safe Args用户导航和传递数据,以确保类型安全。启用Safe Args后,生成的代码会包含已定义的每个操作的类和方法,以及与每个发送目的地和接收目的地相对应的类。看来,还得先看一下操作是什么东西。双击nav_graph.xml文件,并进入Design模式。按照下方动图所示的连接NoteListFragment和NoteFragment。最后点击的那个星星图表是为了规整视图。
切换到Code模式,来看下这波操作生成了什么代码。nav_graph.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/nav_graph" app:startDestination="@id/noteListFragment"> <fragment android:id="@+id/noteListFragment" android:name="com.larissa.android.note.NoteListFragment" android:label="NoteListFragment" tools:layout="@layout/fragment_note_list"> <action android:id="@+id/action_noteListFragment_to_noteFragment2" app:destination="@id/noteFragment" /> </fragment> <fragment android:id="@+id/noteFragment" android:name="com.larissa.android.note.NoteFragment" android:label="NoteFragment" tools:layout="@layout/fragment_note"/> </navigation>
可见,在第一个<fragment>子节点下多了一个<action>子节点。这就是操作。根据它的id,可以看出来这是从NoteListFragment到NoteFragment之间的操作。再返回到Design模式,来给NoteFragment添加Arguments。
再切换到Code模式,看看又添加了哪些代码。nav_graph.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/nav_graph" app:startDestination="@id/noteListFragment"> <fragment android:id="@+id/noteListFragment" android:name="com.larissa.android.note.NoteListFragment" android:label="NoteListFragment" tools:layout="@layout/fragment_note_list"> <action android:id="@+id/show_note" app:destination="@id/noteFragment" /> </fragment> <fragment android:id="@+id/noteFragment" android:name="com.larissa.android.note.NoteFragment" android:label="NoteFragment" tools:layout="@layout/fragment_note"> <argument android:name="noteTitle" app:argType="string" /> <argument android:name="noteContent" app:argType="string" /> <argument android:name="noteLog" app:argType="string" /> </fragment> </navigation>
可见,在第二个<fragment>下面多了三个<argument>子节点。这里把action的id改成了show_note。
操作定义好了之后,Safe Args会为给NoteListFragment生成一个NoteListFragmentDirections类,给NoteFragment生成一个NoteFragmentArgs类。现在可以用这样调用navigate()了。
把NoteListFragment.java中NoteItemHolder类的构造方法改成这样:
public NoteItemHolder(View view){ super(view); mTitleTextView=(TextView) view.findViewById(R.id.note_title); mLogTextView=(TextView) view.findViewById(R.id.note_log); mImageButton=(ImageButton) view.findViewById(R.id.note_delete); view.setOnClickListener(itemView -> { NavDirections action=NoteListFragmentDirections.showNote(mNote.getTitle(),mNote.getContent(),mNote.getNoteLog()); Navigation.findNavController(itemView).navigate(action); Bundle bundle=new Bundle(); ArrayList<String> noteDetails=new ArrayList<>(); noteDetails.add(mNote.getTitle()); noteDetails.add(mNote.getContent()); noteDetails.add(mNote.getNoteLog()); bundle.putStringArrayList("NoteDetail",noteDetails); Navigation.findNavController(itemView).navigate(R.id.noteFragment,bundle); }); }
相应地,NoteFragment.java也需要更改:
@Override public void onCreate(Bundle savedInstanceState){ super.onCreate(savedInstanceState); Log.d(TAG,"onCreate() called"); ActionBar actionBar=((AppCompatActivity) getActivity()).getSupportActionBar(); actionBar.setTitle(TAG); mNote=new Note(); mNote.setTitle(NoteFragmentArgs.fromBundle(getArguments()).getNoteTitle()); mNote.setContent(NoteFragmentArgs.fromBundle(getArguments()).getNoteContent()); mNote.setNoteLog(NoteFragmentArgs.fromBundle(getArguments()).getNoteLog()); mNote.setTitle("NoteDemo"); mNote.setContent("This is a note demo. This is a note demo. This is note demo"); mNote.setId(UUID.randomUUID()); mNote.setNoteLog("Created"); ArrayList<String>noteDetails=getArguments().getStringArrayList("NoteDetail"); mNote.setTitle(noteDetails.get(0)); mNote.setContent(noteDetails.get(1)); mNote.setNoteLog(noteDetails.get(2)); }
Run app,试一下。源代码见:https://gitee.com/larissaLiu/note_-v2