Android JetPack –导航架构

时间:2020-02-23 14:29:00  来源:igfitidea点击:

在本教程中,我们将讨论作为JetPack一部分的导航架构。
JetPack是Google I/O 2016引入的一组组件和库,用于构建更好的应用程序。

Android Jetpack包含以下组件:

Android JetPack导航架构组件

导航架构组件是从Android SDK 28开始引入的新AndroidX程序包的一部分。

该组件由新教程组成,这些新教程用于构建应用程序,尤其是在片段之间导航。

Google建议使用JetPack的单活动架构。
导航体系结构通过提供自己的一组类在片段之间进行导航,从而摆脱了复杂的FragmentTranscation的困扰。
首先,让我们研究一下导航架构教程。

导航原理

  • 该应用程序应具有固定的开始目标。

  • 堆栈用于表示应用程序的导航状态。

  • 向上导航功能永远不会退出您的应用程序。

  • 在所有其他情况下,"上"和"后"功能应相同。

  • 深度链接到目标或者导航到相同的目标应产生相同的堆栈。

入门

创建一个新的Android Studio项目。
在root.build.gradle文件中添加以下类路径:

buildscript {
	...
	repositories {
  		google()
	}
	dependencies {
  		...
  		classpath 'android.arch.navigation:navigation-safe-args-gradle-plugin:1.0.0-alpha04'
	}
}

在应用程序的" build.gradle"内部添加以下内容:

implementation 'android.arch.navigation:navigation-fragment:1.0.0-alpha04'
implementation 'android.arch.navigation:navigation-ui:1.0.0-alpha04'

在应用的build.gradle中添加以下插件:

apply plugin: 'androidx.navigation.safeargs'

项目结构

我们的项目包含一个活动和两个片段。
让我们看看我们是如何实现的。

导航图

导航图是导航体系结构的核心层。
它列出了所有片段/活动并添加了它们之间的所有连接。

在此图中,我们使用一些关键术语来解决不同部分:

导航图XML –这是在res文件夹中创建的XML定义。
右键单击res目录,然后选择New-> Android resource file。
设置文件标题,然后从"资源类型"下拉列表中选择"导航",如下所示:

这将在res内创建具有相关文件名的导航文件夹。

导航主机片段–在活动布局内,我们定义了一个导航主机片段。
在我们的项目中,我们将导航主机片段设置在activity_main.xml布局内,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="https://schemas.android.com/apk/res/android"
  xmlns:app="https://schemas.android.com/apk/res-auto"
  xmlns:tools="https://schemas.android.com/tools"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  tools:context=".MainActivity">

  <fragment
      android:id="@+id/my_nav_host_fragment"
      android:name="androidx.navigation.fragment.NavHostFragment"
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      app:defaultNavHost="true"
      app:layout_constraintBottom_toBottomOf="parent"
      app:layout_constraintLeft_toLeftOf="parent"
      app:layout_constraintRight_toRightOf="parent"
      app:layout_constraintTop_toTopOf="parent"
      app:navGraph="@navigation/navigation_graph" 

</androidx.constraintlayout.widget.ConstraintLayout>
  • app:navGraph`:定义哪个导航图将与导航主机相关联
  • app:defaultNavHost =" true":确保导航主机在按下时拦截系统后退按钮。

目标–目标是用户可以去的任何片段或者活动。

我们可以在"导航图设计"文件中添加以下目标:

  • 在左侧,我们有目的地
  • 在中间,我们有导航图
  • 在右边,我们有属性编辑器。
    在属性编辑器中,我们可以添加操作,传递参数

注意" FirstFragment"上的圆形图标,当它突出显示时。
这些是动作。

动作–定义为从一个片段/活动导航到另一个片段/活动。
我们可以通过拖动或者在XML中定义它们。

创建具有指定ID的操作。
您也可以从内置动画的下拉列表中选择过渡动画,也可以创建自己的动画并进行指定。
XML代码会自动生成:

<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="https://schemas.android.com/apk/res/android"
  xmlns:app="https://schemas.android.com/apk/res-auto"
  xmlns:tools="https://schemas.android.com/tools"
  android:id="@+id/navigation_graph"
  app:startDestination="@id/firstFragment">

  <fragment
      android:id="@+id/firstFragment"
      android:name="com.theitroad.androidjetpacknavigation.FirstFragment"
      android:label="navigation_first_fragment"
      tools:layout="@layout/navigation_first_fragment" >
      <action
          android:id="@+id/action_firstFragment_to_secondFragment"
          app:destination="@id/secondFragment"
          app:enterAnim="@anim/nav_default_enter_anim"  
  </fragment>
  <fragment
      android:id="@+id/secondFragment"
      android:name="com.theitroad.androidjetpacknavigation.SecondFragment"
      android:label="navigation_second_fragment"
      tools:layout="@layout/navigation_second_fragment" 
</navigation>

无需设计编辑器即可直接编写以上代码。
我们在动作上添加了动画。

在Navigation Graph XML标记内,您必须指定startDestination
该应用程序在那里启动。

第一个和第二个片段的布局为:

navigation_first_fragment.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="https://schemas.android.com/apk/res/android"
  xmlns:tools="https://schemas.android.com/tools"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:background="@color/colorPrimary"
  android:gravity="center"
  android:orientation="vertical"
  tools:context=".FirstFragment">

  <TextView
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:layout_margin="16dp"
      android:gravity="center"
      android:textSize="22sp"
      android:textColor="@android:color/white"
      android:text="This is First Fragment" 

  <Button
      android:id="@+id/button_frag1"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:text="Go to next screen" 
</LinearLayout>

navigation_second_fragment.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="https://schemas.android.com/apk/res/android"
  xmlns:tools="https://schemas.android.com/tools"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:background="@color/colorAccent"
  android:gravity="center"
  tools:context=".SecondFragment">

  <TextView
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:layout_centerInParent="true"
      android:gravity="center"
      android:text="This is Second Fragment"
      android:textColor="@android:color/white"
      android:textSize="22sp" 

  <Button
      android:id="@+id/button_frag2"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:layout_alignParentBottom="true"
      android:text="Back" 

</RelativeLayout>

现在已经准备好布局,让我们看看如何通过操作从一个片段导航到另一个片段。
NavController在NavHost中管理应用导航。

FirstFragment.java类的代码为:

package com.theitroad.androidjetpacknavigation;

import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.navigation.NavController;
import androidx.navigation.NavDirections;
import androidx.navigation.Navigation;

public class FirstFragment extends Fragment {

  @Nullable
  @Override
  public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
      return inflater.inflate(R.layout.navigation_first_fragment, container, false);
  }

  @Override
  public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
      super.onViewCreated(view, savedInstanceState);

      View.OnClickListener s = Navigation.createNavigateOnClickListener(R.id.action_firstFragment_to_secondFragment);
      Button button = view.findViewById(R.id.button_frag1);
      button.setOnClickListener(s);
  }

}

我们在createNavigateOnClickListener方法中传递动作ID。
还有几种其他导航方式:

替代方法1我们也可以使用以下代码进行导航:

final NavController navController = Navigation.findNavController(getActivity(), R.id.my_nav_host_fragment);

button.setOnClickListener(new View.OnClickListener() {
          @Override
          public void onClick(View view) {
              navController.navigate(R.id.action_firstFragment_to_secondFragment);
          }
      });

替代方法2

final NavDirections navDirections = FirstFragmentDirections.actionFirstFragmentToSecondFragment();
final NavController navController = Navigation.findNavController(getActivity(), R.id.my_nav_host_fragment);

button.setOnClickListener(new View.OnClickListener() {
          @Override
          public void onClick(View view) {
              navController.navigate(navDirections);
          }
      });

传递参数

导航体系结构内置了一种将类型安全参数从一个Fragment传递到另一个Fragment的内置方法。

您可以在"导航图"设计编辑器中将它们定义为:

和/或者XML:

<fragment
      android:id="@+id/firstFragment"
      android:name="com.theitroad.androidjetpacknavigation.FirstFragment"
      android:label="navigation_first_fragment"
      tools:layout="@layout/navigation_first_fragment" >

      <argument
          android:name="test_string"
          android:defaultValue="hello world"
          app:argType="string" 
  </fragment>

应使用app:argType代替app:type。
app:type已弃用。

现在,当我们导航时,这些参数会自动传递。
我们还可以通过编程方式传递更多参数。

现在,我们的FirstFragment的onViewCreated方法变为:

@Override
  public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
      super.onViewCreated(view, savedInstanceState);

      final Bundle bundle = new Bundle();
      bundle.putBoolean("test_boolean", true);

      final NavController navController = Navigation.findNavController(getActivity(), R.id.my_nav_host_fragment);

      
      Button button = view.findViewById(R.id.button_frag1);

      button.setOnClickListener(new View.OnClickListener() {
          @Override
          public void onClick(View view) {
              navController.navigate(R.id.action_firstFragment_to_secondFragment, bundle);
          }
      });
  }

SecondFragment.java类的代码是:

package com.theitroad.androidjetpacknavigation;

import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.navigation.NavController;
import androidx.navigation.NavDestination;
import androidx.navigation.Navigation;

public class SecondFragment extends Fragment {

  @Nullable
  @Override
  public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
      return inflater.inflate(R.layout.navigation_second_fragment, container, false);
  }

  @Override
  public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
      super.onViewCreated(view, savedInstanceState);
      
      Toast.makeText(getActivity().getApplicationContext(), "Bundle args " + getArguments().getBoolean("test_boolean"), Toast.LENGTH_SHORT).show();
      Toast.makeText(getActivity().getApplicationContext(), "Bundle args " + FirstFragmentArgs.fromBundle(getArguments()).getTestString(), Toast.LENGTH_SHORT).show();

      Button button = view.findViewById(R.id.button_frag2);
      button.setOnClickListener(new View.OnClickListener() {
          @Override
          public void onClick(View view) {
              final NavController navController = Navigation.findNavController(getActivity(), R.id.my_nav_host_fragment);
              navController.navigateUp();

              navController.addOnNavigatedListener(new NavController.OnNavigatedListener() {
                  @Override
                  public void onNavigated(@NonNull NavController controller, @NonNull NavDestination destination) {
                      Log.d("TAG", destination.getLabel() + " ");
                  }
              });
          }
      });
  }
  
}

重建项目时,导航图中传递的参数具有自动生成的getter。

FirstFragmentArgs.fromBundle(getArguments())。
getTestString()用于检索从FirstFragment传递的参数。
navigateUp()用于返回上一个片段。
导航完成时,将调用addOnNavigatedListener。