Android学习笔记七(JAVA):初识Navigation组件

上篇笔记中,实现了RecyclerView显示的笔记列表,本篇笔记实现下方动图所示的功能,即点击笔记列表中的某一项,能够跳转到笔记详情页面。实现该功能,使用Navigation组件,即导航组件。

 

1.导航组件的简介以及配置

2.导航组件的使用

3.通过SafeArgs传递数据

 

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

posted @ 2022-09-02 19:24  南风小斯  阅读(1158)  评论(0编辑  收藏  举报