一、 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),从而实现后续的操作。
@Overridepublic 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;}