一、Navigation 简介
1.1 Navigation 诞生背景
采纳单个 Activity 嵌套多个 Fragment 的 UI 架构模式,曾经被大多数的 Android 工程师所承受。然而,对于 Fragment 的治理始终是一件比拟麻烦的事件,工程师须要通过 FragmentManager 和 FragmentTransaction 来治理 Fragment 之间的切换。
在 Android 中,页面的切换和治理包含应用程序 Appbar 的治理、Fragment 的动画切换以及 Fragment 之间的参数传递等内容。并且,纯代码的形式应用起来不是特地敌对,并且 Appbar 在治理和应用的过程中显得很凌乱。因而,Jetpack 提供了一个名为 Navigation 的组件,旨在不便开发者治理 Fragment 页面和 Appbar。
相比之前 Fragment 的治理须要借助 FragmentManager 和 FragmentTransaction,应用 Navigation 组件有如下一些长处:
- 可视化的页面导航图,不便咱们理清页面之间的关系
- 通过 destination 和 action 实现页面间的导航
- 不便增加页面切换动画
- 页面间类型平安的参数传递
- 通过 Navigation UI 类,对菜单 / 底部导航 / 抽屉蓝菜单导航进行对立的治理
- 反对深层链接 DeepLink
1.2 Navigation 元素
在正式学习 Navigation 组件之前,咱们须要对 Navigation 的次要元素有一个简略的理解,Navigation 次要由三局部组成。
- Navigation Graph:一个蕴含所有导航和页面关系相干的 XML 资源。
- NavHostFragment:一种非凡的 Fragment,用于承载导航内容的容器。
- NavController:治理利用导航的对象,实现 Fragment 之间的跳转等操作。
二、Navigation 应用
2.1 增加依赖
首先,新建一个 Android 我的项目,而后在 build.gradle 中引入如下依赖脚本。
dependencies {
def nav_version = "2.3.2"
// Java language implementation
implementation "androidx.navigation:navigation-fragment:$nav_version"
implementation "androidx.navigation:navigation-ui:$nav_version"
// Kotlin
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
}
2.2 创立导航图
在【Project】窗口中,res 目录下右键而后顺次抉择【New】->【Android Resource File】创立 New Resource File 对话框,如下图所示。
在弹出的界面中,File name 可随便输出,Resource type 抉择 Navigation,而后点击确定即可。
点击确定后,就会在 res 目录下创立 navigation 目录,以及导航文件 nav_graph.xml。
2.3 Navigation graph
关上新建的 nav_graph.xml 文件,在 Design 界面能够看到目前还没有内容,能够【顺次点击 New Destination】图标,而后点击【Create new destination】,即可疾速创立新的 Fragment,这里别离新建了 FragmentA、FragmentB、FragmentC 三个 fragment。
而后,可通过手动配置页面之间的跳转关系,点击某个页面,左边会呈现一个小圆点,拖曳小圆点指向跳转的页面,比方设置跳转的关系为 FragmentA -> FragmentB -> FragmentC。
而后,咱们再切换到 Code 面板,能够看到生成的代码如下所示。
<?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/fragmentA">
<fragment
android:id="@+id/fragmentA"
android:name="com.example.testnavigation.FragmentA"
android:label="fragment_a"
tools:layout="@layout/fragment_a" >
<action
android:id="@+id/action_fragmentA_to_fragmentB"
app:destination="@id/fragmentB" />
</fragment>
<fragment
android:id="@+id/fragmentB"
android:name="com.example.testnavigation.FragmentB"
android:label="fragment_b"
tools:layout="@layout/fragment_b" >
<action
android:id="@+id/action_fragmentB_to_fragmentC"
app:destination="@id/fragmentC" />
</fragment>
<fragment
android:id="@+id/fragmentC"
android:name="com.example.testnavigation.FragmentC"
android:label="fragment_c"
tools:layout="@layout/fragment_c" />
</navigation>
下面的生成的代码中用到了几个标签,含意如下。
- navigation:根标签,通过 startDestination 配置指定默认的启动页面。
- fragment:fragment 标签代表一个 fragment 视图。
- action:action 标签定义了页面跳转的行为,destination 标签定义跳转的指标页,跳转时还能够定义跳转动画。
2.4 NavHostFragment
咱们晓得,Fragment 须要有一个 Activity 容器能力失常运行,NavHostFragment 就是承载导航内容的容器,并且它须要和 Activity 绑定。
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
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"
tools:context=".MainActivity">
<fragment
android:id="@+id/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/nav_graph" />
</androidx.constraintlayout.widget.ConstraintLayout>
其中,android:name="androidx.navigation.fragment.NavHostFragment"
这行代码的作用就是通知 Android 零碎这是一个非凡的 Fragment。并且当 app:defaultNavHost="true"
属性为 true 时,该 Fragment 会主动解决零碎返回。
- android:name 指定 NavHostFragment
- app:navGraph 指定导航视图,即建好的 nav_graph.xml
- app:defaultNavHost=true,能够拦挡零碎的返回键,能够了解为默认给 fragment 实现了返回键的性能。
而后咱们运行程序,能够看到默认展现的是 FragmentA 页面,这是因为 MainActivity 的布局文件中配置了 NavHostFragment,并且给 NavHostFragment 指定了默认展现的页面为 FragmentA。
2.5 NavController
NavController 次要用来治理 fragment 之间的跳转,每个 NavHost 均有本人的相应 NavController。能够通过 findNavController 来获取 NavController,而后应用 NavController 的 navigate 或者 navigateUp 办法来进行页面之间的路由操作。
关上 FragmentA.java 文件,而后在 onViewCreated 生命周期办法中增加如下代码。
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {View view = inflater.inflate(R.layout.fragment_a, container, false);
Button btnB = view.findViewById(R.id.btn_b);
btnB.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {Navigation.findNavController(v).navigate(R.id.action_fragmentA_to_fragmentB);
}
});
return view;
}
运行下面的代码,而后点击 FragmentA 页面上的按钮,零碎就会通过 Navigation.findNavController(v).navigate()
办法导航到 FragmentB。
2.6 增加动画
在 Fragment 之间进行跳转时,还能够增加跳转的动画。关上 nav_graph.xml 文件的 Design 选项,而后在 Attributes 面板的 Animations 局部中,点击要增加的动画旁边的下拉箭头,开发者能够从以下类型中进行抉择,如下图所示。
其中,Attributes 面板波及的属性含意如下。
- enterAnim:跳转时的指标页面动画
- exitAnim:跳转时的原页面动画
- popEnterAnim:回退时的指标页面动画
- popExitAnim:回退时的原页面动画
而后,关上 Code 面板,生成的代码如下。
<fragment
android:id="@+id/fragmentA"
android:name="com.xzh.jetpack.FragmentA"
android:label="fragment_a"
tools:layout="@layout/fragment_a" >
<action
android:id="@+id/action_fragmentA_to_fragmentB"
app:destination="@id/fragmentB"
app:enterAnim="@android:anim/slide_in_left"
app:exitAnim="@android:anim/slide_out_right"
app:popEnterAnim="@anim/fragment_fade_enter"
app:popExitAnim="@anim/fragment_fade_exit" />
</fragment>
三、参数传递
在 Android 中,页面之间如果要传递数据,倡议传递最大量的数据,因为在 Android 上用于保留所有状态的总空间是无限的。如果您须要传递大量数据,能够应用 ViewModel。
Fragment 的切换常常随同着参数的传递,为了配合 Navigation 组件在切换 Fragment 时传递参数,Android Studio 为开发者提供了 Safe Args 和 Bundle 两种参数传递形式。
3.1 应用 Bundle 传递数据
应用 Bundle 传递数据时,首先创立 Bundle 对象,而后应用 navigate() 将它传递给目的地,如下所示。
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {super.onViewCreated(view, savedInstanceState)
tv.setOnClickListener {Bundle bundle = new Bundle();
bundle.putString("key", "from fragmentA");
Navigation.findNavController(v).navigate(R.id.action_fragmentA_to_fragmentB,bundle);
}
}
而后,接受方应用 getArguments() 办法来获取 Bundle 传递的数据,如下所示。
String keyStr = getArguments().getString("key");
3.2 应用 Safe Args 传递数据
首先,在我的项目的 build.gradle 中增加 classpath 配置,如下所示。
dependencies {
def nav_version = "2.3.2"
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
}
而后,在 app 的 build.gradle 增加 apply plugin 脚本,如下所示。
apply plugin: 'androidx.navigation.safeargs'
配置实现后记从新 rebuild 下我的项目,会生成{module}/build/generated/source/navigation-args/{debug}/{packaged}/{Fragment}Dircetions
,如下所示。
如果须要往目标页面传递数据,首先请依照以下步骤将参数增加到接管它的目标页面中。Navigation 提供了一个子标签 argument 能够用来传递参数。
首先,在 Navigation Editor 中,点击接管参数的目标页面,在 Attributes 面板中,点击 Add (+)。而后,在显示的 Add Argument Link 窗口中,输出参数名称、参数类型、参数是否可为 null,以及默认值(如果须要)点击【Add】按钮,如下所示。
点击 Text 标签页切换到 XML 视图,会发现生成的代码如下。
<argument
android:name="key"
app:argType="string"
app:nullable="false"
android:defaultValue="navigation 参数传递" />
而后,咱们在 FragmentA.java 中应用如下代码传递数据,如下所示。
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {View view = inflater.inflate(R.layout.fragment_a, container, false);
Button btnB = view.findViewById(R.id.btn_b);
btnB.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {FragmentADirections.ActionFragmentAToFragmentB action=FragmentADirections.actionFragmentAToFragmentB().setKey("通过 safeArgs 进行参数传递");
Navigation.findNavController(v).navigate(action);
}
});
return view;
}
在承受的页面的 onCreate 办法中应用如下的办法进行承受。
public void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);
if (getArguments() != null) {String name= FragmentBArgs.fromBundle(getArguments()).getKey();}
}
四、深层链接 DeepLink
当应用程序承受到某个告诉推送,心愿用户在点击该告诉时,可能间接跳转到展现该告诉内容的页面,这就是深层链接 DeepLink 最常见的场景,Navigation 组件提供了对深层链接(DeepLink)的反对。
DeepLink 有两种利用场景,一种是 PendingIntent,另一种是实在的 URL 链接,利用这两种形式都能够跳转到程序中指定的页面。
4.1 PendingIntent
PendingIntent 形式个别用在音讯告诉中,当应用程序接管到某个告诉时,并且心愿用户在单击该告诉时间接跳转到到指定的页面,那么就能够通过 PendingIntent 来实现。
例如,上面的代码实现性能是,在 MainActivity 中单击按钮弹出告诉栏,点击告诉栏跳转到指定 NotificationActivity 页面中,代码如下。
public class MainActivity extends AppCompatActivity {
NotificationManager manager=null;
@Override
protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
init();}
private void init() {manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
if (Build.VERSION.SDK_INT>=Build.VERSION_CODES.O){
// 留神告诉渠道一旦建设就无权批改
NotificationChannel channel =new NotificationChannel("normal","Normal",NotificationManager.IMPORTANCE_DEFAULT);
manager.createNotificationChannel(channel);
NotificationChannel channel2 =new NotificationChannel("important","Important",NotificationManager.IMPORTANCE_HIGH);
manager.createNotificationChannel(channel2);
}
Button pending=findViewById(R.id.btn_pendingintent);
pending.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {sendNotification();
}
});
}
void sendNotification(){Intent intent =new Intent(this,NotificationActivity.class);
PendingIntent pi = PendingIntent.getActivity(this,0,intent,0);
// 应用 NotificationCompat 兼容 8.0 以前的零碎
// val notification = NotificationCompat.Builder(this,"normal")
Notification notification = new NotificationCompat.Builder(this,"important")
.setContentTitle("This is content title")
.setContentText("This is content text")
.setStyle(new NotificationCompat.BigTextStyle().bigText("Learn how to build notification,send any sync data,and use voice actions.Get the" +
"official Android IDE and developer tools to build apps for Android."))
.setStyle(new NotificationCompat.BigPictureStyle().bigPicture(BitmapFactory.decodeResource(getResources(),R.drawable.ic_launcher_background)))
.setSmallIcon(R.drawable.ic_launcher_foreground)
.setLargeIcon(BitmapFactory.decodeResource(getResources(),R.drawable.ic_launcher_background))
.setContentIntent(pi)
.setAutoCancel(true)
.build();
manager.notify(1,notification);
}
}
而后,须要新建一个告诉的目标页面,为了 MainActivity 做辨别,咱们在 NotificationActivity 页面仅放了一个 TextView 组件,如下所示。
public class NotificationActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);
setContentView(R.layout.activity_notification);
}
}
而后运行下面的代码,最终的成果如下图所示。
4.2 URL
应用 URL 链接形式,当用户通过手机浏览器浏览网站上的某个页面时,能够通过网页浏览器的形式关上对应的利用页面。如果用户的手机装置有咱们得应用程序,那么通过 DeepLink 就能关上相应的页面;如果没有装置,那么网站能够导航到应用程序的下载页面,从而疏导用户装置应用程序。
首先,在导航图中为 destination 增加 <deepLink/>
标签,在 app:uri 属性中填入的是你的网站的相应 web 页面地址,如下所示。
<fragment
android:id="@+id/deepLinkSettingsFragment"
android:name="com.michael.deeplinkdemo.DeepLinkSettingsFragment"
android:label="fragment_deep_link_settings"
tools:layout="@layout/fragment_deep_link_settings">
<!-- 为 destination 增加 <deepLink/> 标签 -->
<deepLink app:uri="www.YourWebsite.com/{params}" />
</fragment>
如上所示,app:uri
外面填的是网站的相应 Web 页面地址,{params}外面的参数会通过 Bundle 对象传递到页面中。
而后,为相应的 Activity 设置 <nav-graph/>
标签,当用户在 Web 中拜访到链接时,你的应用程序便能监听到,如下所示。
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".DeepLinkActivity">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<!-- 为 Activity 设置 <nav-graph/> 标签 -->
<nav-graph android:value="@navigation/nav_graph"/>
</activity>
</application>
通过下面的设置后,接下来就能够测试咱们的性能了。咱们能够在 Google app 中输出相应的 Web 地址,也能够通过 adb 工具,应用命令行来实现测试操作。
adb shell am start -a android.intent.action.VIEW -d "http://www.YourWebsite.com/fromWeb"
执行完命令,手机便能间接关上 deepLinkSettingsFragment。在该 Fragment 中,咱们能够通过 Bundle 对象获取相应的参数(fromWeb),从而实现后续的操作。
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){View view = inflater.inflate(R.layout.fragment_deep_link_settings, container, false);
Bundle bundle = getArguments();
if(bundle != null){String params = bundle.getString("params");
TextView tvDesc = view.findViewById(R.id.tvDesc);
if(!TextUtils.isEmpty(params)){tvDesc.setText(params);
}
}
return view;
}