IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Jetpack-Navigation使用第一部分 -> 正文阅读

[移动开发]Jetpack-Navigation使用第一部分

一、使用步骤

1、导入相关库支持库,最新版本请查看安卓官网:https://developer.android.com/jetpack/androidx/versions?hl=zh-cn

官方使用指导请查看:https://developer.android.com/guide/navigation?hl=zh-cn,官方使用讲解的很清楚,大部分第三方资料都是基于官方文档编写,包括本文档。

dependencies {
  def nav_version = "2.3.5"

  // 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"

  // Feature module Support
  implementation "androidx.navigation:navigation-dynamic-features-fragment:$nav_version"

  // Testing Navigation
  androidTestImplementation "androidx.navigation:navigation-testing:$nav_version"

  // Jetpack Compose Integration
  implementation "androidx.navigation:navigation-compose:2.4.0-alpha03"
}

2. 新建navigation xml文件,我们称这个文件问导航图,navigation graph,

1.? 在“Project”窗口中,右键点击?res?目录,然后依次选择?New > Android Resource File。此时系统会显示?New Resource File?对话框。

2.? 在?File name?字段中输入名称,例如“nav_graph”。

3.? 从?Resource type?下拉列表中选择?Navigation,然后点击?OK。

新建导航图后,会有一个直观的画面可以看到fragment的跳转关系,方便用户直观查看。

导航图文件结构如下:

<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/firstFragment">

    <fragment
        android:id="@+id/firstFragment"
        android:name="com.xmcc.androidbasesample.fragment.navigation.FirstFragment"
        android:label="fragment_first"
        tools:layout="@layout/fragment_first" >
        <action
            android:id="@+id/action_firstFragment_to_secondFragment"
            app:destination="@id/secondFragment" />
        <action
            android:id="@+id/action_firstFragment_to_settingsFragment"
            app:destination="@id/settingsFragment" />
    </fragment>
    <fragment
        android:id="@+id/secondFragment"
        android:name="com.xmcc.androidbasesample.fragment.navigation.SecondFragment"
        android:label="fragment_second"
        tools:layout="@layout/fragment_second" >
        <action
            android:id="@+id/action_secondFragment_to_firstFragment"
            app:destination="@id/firstFragment" />
    </fragment>
    <fragment
        android:id="@+id/settingsFragment"
        android:name="com.xmcc.androidbasesample.SettingsFragment"
        android:label="SettingsFragment" />
</navigation>

其中action元素为跳转标记,使用代码跳转时,将会使用到该action元素中的id,如下

Navigation.findNavController(it).navigate(R.id.action_firstFragment_to_secondFragment)

导航图中在根元素navigation中有app:startDestination="@id/firstFragment",表示第一个进入的fragment

3. 完成导航图创建后,我们需要将导航图添加到导航宿主activity中,新建一个空的activity,在xml中添加如下内容,

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    ...>

    <androidx.fragment.app.FragmentContainerView
        ...

        android:name="androidx.navigation.fragment.NavHostFragment"
        app:defaultNavHost = "true"
        app:navGraph = "@navigation/nav_graph"

        ...
        />

</androidx.constraintlayout.widget.ConstraintLayout>

其中有3个属性比较关键,

  • android:name?属性包含?NavHost?实现的类名称。
  • app:navGraph?属性将?NavHostFragment?与导航图相关联。导航图会在此?NavHostFragment?中指定用户可以导航到的所有目的地。
  • app:defaultNavHost="true"?属性确保您的?NavHostFragment?会拦截系统返回按钮。请注意,只能有一个默认?NavHost。如果同一布局(例如,双窗格布局)中有多个宿主,请务必仅指定一个默认?NavHost。

注意:FragmentContainerView必须要有一个id,否则会包如下错误,

Caused by: java.lang.IllegalStateException: FragmentContainerView must have an android:id to add Fragment androidx.navigation.fragment.NavHostFragment

至此,您已经完成了navigation最基本得创建和使用,当然navigation功能还有很多。

二、navigation跳转

navigation跳转方向如下,

1. fragment -> fragment

2. fragment -> activity, activity是导航图的一个端点,相当于调用了,startActivity(xxx)函数,当导航到另一个activity时,当前的导航图将不在处于作用域内,使用如下activity标签,

<activity
    android:id="@+id/testNavigationActivity"          
    android:name="com.xmcc.androidbasesample.fragment.navigation.TestNavigationActivity"
    android:label="TestNavigationActivity" />

3. fragment -> dialogFragment,需要在导航图中新建一个dialog标签的destination,否则将不会出现dialog效果,只会和普通的fragment一样,diaolog标签如下,

<dialog
    android:id="@+id/my_dialog_fragment"
    android:name="com.xmcc.androidbasesample.fragment.navigation.NavigationDialogFragment">
    <action
        android:id="@+id/myDialogAction"
        app:destination="@+id/destinationNameXXX"/>
</dialog>

这里备注下fragment 生命周期:

onAttach -> onCreate -> onCreateView -> onViewCreated -> onActivityCreated -> onStart -> onResume

fragment跳转时:onPause -> onStop -> onDestroyView

fragment结束时:onPause -> onStop -> onDestroyView -> onDestroy -> onDetach

4. 通过导航图跳转到带scheme的activity

androidmanifest.xml中定义如下activity

<activity android:name=".fragment.navigation.TestSchemeActivity">
    <intent-filter tools:ignore="AppLinkUrlError">
    <action android:name="android.intent.action.VIEW" />
        <data android:host="example.com"
              android:scheme="https"/>
                <category android:name="android.intent.category.DEFAULT"/>
    </intent-filter>
</activity>

在导航图中定义如下destination,

<activity
    android:id="@+id/testSchemeActivity"
    android:label="activity_test_scheme"
    app:action="android.intent.action.VIEW"
    app:data="https://example.com"
    app:targetPackage="${applicationId}"
    tools:layout="@layout/activity_test_scheme" />

其中targetPackage用来限定当前应用,不加时,会出现多个应用选择。

5. scheme动态参数

与上一条类似,参数略有不同,导航图中配置如下

<activity
    android:id="@+id/testDynamicSchemeActivity"
    android:label="activity_test_dynamic_scheme"
    app:action="android.intent.action.VIEW"
    app:dataPattern="https://example.com?userId={userId}"
    app:targetPackage="${applicationId}"
    tools:layout="@layout/activity_test_dynamic_scheme" >
    <argument android:name="userId"
        app:argType="string"/>
</activity>

使用时传参如下

findNavController().navigate(
    R.id.action_firstFragment_to_testDynamicSchemeActivity, 
    bundleOf("userId" to "abcd1234")
 )

接收数据的activity中,解析如下

intent.dataString

三、嵌套导航图 nested graph

使用嵌套导航图时,外部不能直接访问嵌套图的内部,嵌套图xml如下,嵌套图可以使用include标签,

<?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/nested_graph"
    app:startDestination="@id/nestedGraphRootFragment">

    <fragment
        android:id="@+id/nestedGraphRootFragment"
        android:name="com.xmcc.androidbasesample.fragment.navigation.nestedgraph.NestedGraphRootFragment"
        android:label="fragment_nested_graph_root_list"
        tools:layout="@layout/fragment_nested_graph_root_list" >
        <action
            android:id="@+id/action_nestedGraphRootFragment_to_orderListFragment"
            app:destination="@id/nav_order" />
        <action
            android:id="@+id/action_nestedGraphRootFragment_to_userHomeFragment"
            app:destination="@id/nav_user" />
    </fragment>
    <include app:graph = "@navigation/nested_order"/>

    <navigation android:id="@+id/nav_user"
        app:startDestination="@id/userHomeFragment">
        <fragment
            android:id="@+id/settingsFragment2"
            android:name="com.xmcc.androidbasesample.fragment.navigation.nestedgraph.usernode.SettingsFragment"
            android:label="fragment_settings"
            tools:layout="@layout/fragment_settings">
        </fragment>
        <fragment
            android:id="@+id/userHomeFragment"
            android:name="com.xmcc.androidbasesample.fragment.navigation.nestedgraph.usernode.UserHomeFragment"
            android:label="fragment_user_home"
            tools:layout="@layout/fragment_user_home">
            <action
                android:id="@+id/action_userHomeFragment_to_settingsFragment2"
                app:destination="@id/settingsFragment2" />
        </fragment>
    </navigation>
    <action android:id="@+id/action_global_nestedGraphRootFragment"
        app:destination="@id/nestedGraphRootFragment"/>
</navigation>

四、导航图返回

可以使用方式返回,如下,

1. 使用popBackStack返回,

findNavController().popBackStack()

2. 使用navigateUp返回,

findNavController().navigateUp()

3. 使用navigate + actionId返回,注意需要在导航图中使用popUpTo,popUpToInclusive标签,其中popUpToInclusive标签为true时,会把destination也清除掉(例子中的destination为orderListFragment)。如有3个destination,A、B、C如下图,从C如果直接navigate到A,没有使用popUpTo的情况下,B、C不会清除,即

没有使用popUpTo,堆栈内会是,ABCABC...

使用popUpTo,堆栈内会是,A

使用popUpTo且popUpToInclusive=true,堆栈内ABC都没有

回退方法

findNavController().navigate(R.id.orderRatingFragmentPop)

nav graph中内容

<fragment
    android:id="@+id/orderRatingFragment"
    android:name="com.xmcc.androidbasesample.fragment.navigation.nestedgraph.ordernode.OrderRatingFragment"
    android:label="fragment_order_rating"
    tools:layout="@layout/fragment_order_rating"
    >
    <action
         android:id="@+id/orderRatingFragmentPop"
         app:popUpTo="@id/orderListFragment"
        app:popUpToInclusive="true"
        />
</fragment>

这里再说明下popBackStack和navigateUp两个方法的区别,

popBackStack,根据官方描述,与系统back键类似,

Attempts to pop the controller's back stack. Analogous to when the user presses the system?Back?button when the associated navigation host has focus.

navigateUp,查看源码可以发现,getDestinationCountOnBackStack() 不为1时,就是调用的popBackStack

public boolean navigateUp() {
    if (getDestinationCountOnBackStack() == 1) {
        // If there's only one entry, then we've deep linked into a specific destination
        // on another task so we need to find the parent and start our task from there
        NavDestination currentDestination = getCurrentDestination();
        int destId = currentDestination.getId();
        NavGraph parent = currentDestination.getParent();
        while (parent != null) {
            if (parent.getStartDestination() != destId) {
               ......
                return true;
            }
            destId = parent.getId();
            parent = parent.getParent();
        }
        // We're already at the startDestination of the graph so there's no 'Up' to go to
        return false;
    } else {
        return popBackStack();
    }
}

五、在目的地(destination)间数据传递

1. 定义目的地参数

<fragment android:id="@+id/myFragment" >
     <argument
         android:name="myArg"
         app:argType="integer"
         android:defaultValue="0" />
 </fragment>

支持的参数类型如下

类型

app:argType 语法

是否支持默认值?

是否支持 null 值?

整数

app:argType="integer"

浮点数

app:argType="float"

长整数

app:argType="long"

是 - 默认值必须始终以“L”后缀结尾(例如“123L”)。

布尔值

app:argType="boolean"

是 -“true”或“false”

字符串

app:argType="string"

资源引用

app:argType="reference"

是 - 默认值必须为“@resourceType/resourceName”格式(例如,“@style/myCustomStyle”)或“0”

自定义 Parcelable

app:argType="<type>",其中 <type> 是?Parcelable?的完全限定类名称

支持默认值“@null”。不支持其他默认值。

自定义 Serializable

app:argType="<type>",其中 <type> 是?Serializable?的完全限定类名称

支持默认值“@null”。不支持其他默认值。

自定义 Enum

app:argType="<type>",其中 <type> 是 Enum 的完全限定名称

是 - 默认值必须与非限定名称匹配(例如,“SUCCESS”匹配 MyEnum.SUCCESS)。

接收的fragment直接使用如下代码,即可获取到传递进来的参数

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    arguments?.let {
        val param1 = it.getInt("orderId")
    }
}

同时以上内容,也表明最终还是通过bundle来传递参数,包括后续要说到的Safe Args插件也是一样,通过bundle来传递参数的,只是外型不一样。

2. Safe Args插件使用

导入包

buildscript {
    repositories {
        google()
    }
    dependencies {
        def nav_version = "2.3.5"
        classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
    }
}

在对应模块中加入插件

plugins {
    id 'androidx.navigation.safeargs'
}

如被墙了,可以使用镜像解决,

buildscript {
    ext.kotlin_version = "1.4.21"
    repositories {
        google()
        maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' }
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:4.1.2'
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
        def nav_version = "2.3.5"
        classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
    }
}
allprojects {
    repositories {
        google()
        maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' }
        jcenter()
    }
}

使用方法如下,

nav graph中定义action

<action
	android:id="@+id/action_orderListFragment_to_orderDetailFragment"
	app:destination="@id/orderDetailFragment" >
	<argument
		android:name="orderId"
		app:argType="integer"
		android:defaultValue="-1" />
</action>

safe args插件会自动生成相应的directions类,大致内容如下,可以看到,最终还是将数据放到bundle中。

public class OrderListFragmentDirections {
  private OrderListFragmentDirections() {
  }

  @NonNull
  public static ActionOrderListFragmentToOrderDetailFragment actionOrderListFragmentToOrderDetailFragment(
      ) {
    return new ActionOrderListFragmentToOrderDetailFragment();
  }

  public static class ActionOrderListFragmentToOrderDetailFragment implements NavDirections {
    private final HashMap arguments = new HashMap();

    private ActionOrderListFragmentToOrderDetailFragment() {
    }

    @NonNull
    @SuppressWarnings("unchecked")
    public ActionOrderListFragmentToOrderDetailFragment setOrderId(int orderId) {
      this.arguments.put("orderId", orderId);
      return this;
    }

    @Override
    @SuppressWarnings("unchecked")
    @NonNull
    public Bundle getArguments() {
      Bundle __result = new Bundle();
      if (arguments.containsKey("orderId")) {
        int orderId = (int) arguments.get("orderId");
        __result.putInt("orderId", orderId);
      } else {
        __result.putInt("orderId", -1);
      }
      return __result;
    }
....
  }
}

safe args也可以用于全局操作,方法同上。

3. 使用bundle传递数据,方法如下

findNavController().navigate(
    R.id.action_orderListFragment_to_orderDetailFragment,
    bundleOf(
        "orderId" to 10,
        "orderCode" to "abcd"))

六、DeepLinkRequest 导航

通过NavDeepLinkRequest导航,需要在当前导航图下定义一个deeplink,如下

<fragment
    android:id="@+id/second2Fragment"
    android:name="com.xmcc.androidbasesample.fragment.navigation.deeplink.Second2Fragment"
    android:label="Second2Fragment" >
    <deepLink
        app:uri="second.abc"
        />
</fragment>

调用方法如下,这里需要注意两点,

1. 可以看到虽然是通过隐式调用的,但作用域是当前这个导航图,我们可以查看源码知道,如下

public void navigate(@NonNull NavDeepLinkRequest request, @Nullable NavOptions navOptions,
            @Nullable Navigator.Extras navigatorExtras) {
        NavDestination.DeepLinkMatch deepLinkMatch =
                mGraph.matchDeepLink(request);
        if (deepLinkMatch != null) {
            ...
        } else {
            //当前导航图获取不到该deeplink时,会抛如下异常
            throw new IllegalArgumentException("Navigation destination that matches request "
                    + request + " cannot be found in the navigation graph " + mGraph);
        }
}

2. 使用uri时,导航图中写的是second.abc,那么我们的程序中调用可以是http://second.abc或者https://second.abc,但是不能在second.abc后加"/",这点和我们后续要说的隐式deeplink不一样,容易搞混。

val request = NavDeepLinkRequest.Builder
        .fromUri("http://second.abc".toUri())
        .build()
findNavController().navigate(request)

七、为目的地创建深层链接

有两种deeplink,explicit and implicit(显式和隐式)

1. 显式deeplink,使用方法如下,

val pendingIntent: PendingIntent = NavDeepLinkBuilder(requireActivity())
    .setGraph(R.navigation.nav_graph)
    .setDestination(R.id.secondFragment)
    .setArguments(bundleOf("orderId" to 11))
    .setComponentName(FragmentNavigationUseActivity::class.java)
    .createPendingIntent()

val builder = NotificationCompat.Builder(requireContext(), "CHANNEL_ID")
    .setSmallIcon(R.drawable.ic_notifications_black_24dp)
    .setContentTitle("开学了")
    .setContentText("小朋友开学了,请注意")
    .setPriority(NotificationCompat.PRIORITY_HIGH)
    .setContentIntent(pendingIntent)
    .setAutoCancel(true)

with(NotificationManagerCompat.from(requireContext())) {
    notify(1, builder.build())
}

使用notification时需要注意API26+的需要增加如下代码,

// Create the NotificationChannel, but only on API 26+ because
// the NotificationChannel class is new and not in the support library
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    val name = "channel-name"
    val descriptionText = "descriptionText"
    val importance = NotificationManager.IMPORTANCE_HIGH
    val channel = NotificationChannel("CHANNEL_ID", name, importance).apply {
        description = descriptionText
    }
    // Register the channel with the system
    val notificationManager: NotificationManager =
        requireContext().getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
    notificationManager.createNotificationChannel(channel)
}

2. 隐式deeplink,使用方法如下,

在导航图中增加deeplink标签,代码如下

<fragment
    android:id="@+id/second2Fragment"
    android:name="com.xmcc.androidbasesample.fragment.navigation.deeplink.Second2Fragment"
    android:label="Second2Fragment" >
    <deepLink
        app:uri="second.abc"
        app:action="android.intent.action.VIEW"
        app:mimeType="type/subtype"
        />
</fragment>

在AndroidManifest.xml文件中增加导航图,代码如下,

<manifest 
...
>
    ...
    <application
        ...
		>
        ...
        <activity
            android:name=".fragment.navigation.FragmentNavigationUseActivity"
            android:theme="@style/Animation" >
            <nav-graph android:value="@navigation/nav_deeplink"/>
        </activity>
        ...
    </application>
</manifest>

构建项目时,Navigation 组件会将 <nav-graph> 元素替换为生成的 <intent-filter> 元素,替换后内容如下,可以看到替换后的代码中有一项 android:path="/",所以在调用时候,也需要在url最后加上"/",这个问题坑了我比较长时间,最后查了转换后代码才发现。

<activity android:name="com.xmcc.androidbasesample.fragment.navigation.FragmentNavigationUseActivity"
 android:theme="@style/Animation">
	<intent-filter>
		<action android:name="android.intent.action.VIEW"/>
		<category android:name="android.intent.category.DEFAULT"/>
		<category android:name="android.intent.category.BROWSABLE"/>
		<data android:scheme="http"/>
		<data android:scheme="https"/>
		<data android:host="second.abc"/>
		<data android:path="/"/>
	</intent-filter>
</activity>

程序中调用方法如下,

val intent = Intent("android.intent.action.VIEW")
intent.data = Uri.parse("https://second.abc/")
intent.`package` = "com.xmcc.androidbasesample.free.debug"
startActivity(intent)

以上为navigation第一部分学习记录(主要是官网导航组件下从概览-为目标创建深层链接的内容 https://developer.android.com/guide/navigation?hl=zh-cn),如有错误,欢迎指出,后续还有第二部分。

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-08-05 17:27:58  更:2021-08-05 17:29:26 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/17 10:01:50-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码