Jetpack 之 Navigation 小白入手

声明 : https://www.jianshu.com/p/714062a9af75
目录

简介
原理
使用方法:

1,简单使用
2,添加页面动画
3,safe args插件传递参数
4,与BottomNavigationView配合使用
5,深层连接

🍎 简介:

  Android Jetpack提供的一个名为Navigation的UI架构组件,帮助我们更好的管理界面.我这篇文章只写,对 fragment 的管理哈.也是开发中最常见的.有以下优点
one🐹:可视化的页面导航.
tow🐹:通过 destination 和 action 完成页面间的导航.
three🐹:方便添加转场动画.
four🐹:页面之间传参数方便.
five🐹:还可以支持抽屉式导航栏(DrawerLayout)和底部导航(BottomNavigationView)与顶部应用栏(Toolbar、CollapsingToolbarLayout、ActionBar),代码会展示BottomNavigationView的使用,其他雷同哦~~~
six🐹: 支持深层连接 DeepLink.

🍎 工作原理:

了解几个元素:
🤨1.Navigation Graph,这是一种新型的 XML 文件,其中包含所有的Fragment 以及页面之间的关系.
🤨2.NavHostFragment,这是一个特殊的 Fragment,你可以认为他是其他 Fragment 的容器,Navigation Graph 中的 Fragment 正是通过 NAVHostFragment 进行展示的.
🤨3.NavController,这是一个对象,用于在代码中完成 Navigation Graph 中具体的页面切换工作.
**重点来了:**
  当你想切换 Fragment 是,使用 NavController 对象,告诉它,你想要去Navigation Graph 中的哪个 Fragment,NavController 会将你想去的 Fragmen 展示在 NavHostFragment 中.

🍎 使用方法:
1,build.gradle 文件
    //java:
    implementation "androidx.navigation:navigation-fragment:$nav_version"
    implementation "androidx.navigation:navigation-ui:$nav_version"
   //kotlin:
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
2,创建 fragment.

直接截图了哈.是那回事吧🤔🤔🤔


image.png

image.png
3,创建 Navigation Graph

选中 res 文件夹->New->Android Resource File,Resource Type选择(Navigation).


image.png

添加 fragment,


image.png

添加完了,用线连起来
image.png

Look 一下代码
nav_graph_main.xml

<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_main"
    app:startDestination="@id/homeFragment">

    <fragment
        android:id="@+id/homeFragment"
        android:name="com.mcy.test.fragment.HomeFragment"
        android:label="fragment_home"
        tools:layout="@layout/fragment_home" >
        <action
            android:id="@+id/action_homeFragment_to_secondFragment"
            app:destination="@id/secondFragment" />
    </fragment>
    <fragment
        android:id="@+id/secondFragment"
        android:name="com.mcy.test.fragment.SecondFragment"
        android:label="fragment_second"
        tools:layout="@layout/fragment_second" >
        <action
            android:id="@+id/action_secondFragment_to_threeFragment"
            app:destination="@id/threeFragment" />
    </fragment>
    <fragment
        android:id="@+id/threeFragment"
        android:name="com.mcy.test.fragment.ThreeFragment"
        android:label="fragment_three"
        tools:layout="@layout/fragment_three" >
        <action
            android:id="@+id/action_threeFragment_to_myFragment"
            app:destination="@id/myFragment" />
    </fragment>
    <fragment
        android:id="@+id/myFragment"
        android:name="com.mcy.test.fragment.MyFragment"
        android:label="fragment_my"
        tools:layout="@layout/fragment_my" />
</navigation>

app:startDestination="@id/homeFragment"该属性指定起始的 destination 为 homeFragment.

以上体现了文章一开始说的第一个和第二个优点哦~~~

4,Activity中添加NavHost
<fragment
        android:id="@+id/main_nav_fragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:defaultNavHost="true"
        app:navGraph="@navigation/nav_graph_main" />

敲黑板
one🐬:android:name="androidx.navigation.fragment.NavHostFragment".这句代码,告诉我们,这是一个特殊的 Fragment.
otwo🐬:app:defaultNavHost="true",设置为 true,意思是 Fragment 会处理物理返回键.当用户按下返回键的时候,系统将自动将当前所展示的 Fragment 退出.
three🐬:app:navGraph="@navigation/nav_graph_main",设置该 Fragment 对应的导航图.

5,使用 NavController 完成导航

HomeFragment:

        home_tv.setOnClickListener {
            Navigation.findNavController(it).navigate(R.id.action_homeFragment_to_secondFragment)
        }

SecondFragment:

  second_tv.setOnClickListener {
            Navigation.findNavController(it).navigate(R.id.action_secondFragment_to_threeFragment)
        }

ThreeFragment:

  three_tv.setOnClickListener {
            Navigation.findNavController(it).navigate(R.id.action_threeFragment_to_myFragment)
        }

ok,基本的切换完成了.我们看下效果图.


哈哈哈.gif

录屏质量差了点....凑活着看吧😉😉😉
看起来很生硬啊,加个动画效果吧~~

6,添加页面的切换动画

先来几个动画
i_slide_in_right.xml

<set xmlns:android="http://schemas.android.com/apk/res/android">

    <translate
        android:fromXDelta="-100%p"
        android:toXDelta="0"
        android:duration="350"/>
</set>

i_slide_out_left.xml

<set xmlns:android="http://schemas.android.com/apk/res/android">
    <translate
        android:fromXDelta="0"
        android:toXDelta="-100%p"
        android:duration="350"/>
</set>

i_slide_in_left.xml

<set xmlns:android="http://schemas.android.com/apk/res/android">

    <translate
        android:fromXDelta="-100%p"
        android:toXDelta="0"
        android:duration="350"/>
</set>

i_slide_out_right.xml

<set xmlns:android="http://schemas.android.com/apk/res/android">
    <translate
        android:fromXDelta="0"
        android:toXDelta="100%p"
        android:duration="350"/>
</set>

然后给 fragment 设置上:

        <action
            android:id="@+id/action_homeFragment_to_secondFragment"
            app:destination="@id/secondFragment"
            app:enterAnim="@anim/i_slide_in_right"
            app:exitAnim="@anim/i_slide_out_left"
            app:popEnterAnim="@anim/i_slide_in_left"
            app:popExitAnim="@anim/i_slide_out_right" />

呀呀呀

🌸🌸🌸bingo 实现了🌸🌸🌸
ps:🤔
A 界面进入 B 界面
enterAnim B界面的进场动画 --->右进
exitAnim A 界面的退场动画 --->左出
点击Back
popEnterAnim A 界面回退进场的动画 --->左进
popExitAnim B 界面展示后退场动画 --->右出

7,利用safe args插件传递参数

安装插件
操作俩 build.gradle 文件

   //最外层build
   classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.3.0"

   //app 目录下的 build 文件
   apply plugin: 'androidx.navigation.safeargs.kotlin'  //kotlin
   apply plugin:'androidx.navigation.safeargs'   //java

编辑 nav_graph_main.xml 文件:
两种方式:
🌾在Text 中写代码
🌾Design 中面板中添加

......
    <fragment
        android:id="@+id/homeFragment"
        android:name="com.mcy.test.fragment.HomeFragment"
        android:label="fragment_home"
        tools:layout="@layout/fragment_home">
        <action
            android:id="@+id/action_homeFragment_to_secondFragment"
            app:destination="@id/secondFragment"
            app:enterAnim="@anim/i_slide_in_right"
            app:exitAnim="@anim/i_slide_out_left"
            app:popEnterAnim="@anim/i_slide_in_left"
            app:popExitAnim="@anim/i_slide_out_right" />
        <argument
            android:name="name"
            app:argType="string" />
        <argument
            android:name="age"
            app:argType="integer" />
    </fragment>
......

放出来一个看看哈.其他的几个雷同....不占地方了😁😁😁

HomeFragment:

  home_tv.setOnClickListener {
            var toBundle = HomeFragmentArgs("张三", 28).toBundle()
            Navigation.findNavController(it).navigate(R.id.action_homeFragment_to_secondFragment,toBundle)
        }

SecondFragment:

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        var name = arguments?.let { HomeFragmentArgs.fromBundle(it).name }
        var age = arguments?.let { HomeFragmentArgs.fromBundle(it).age }
        second_tv.text = "名字:$name\n年龄:$age"
        second_tv.setOnClickListener {
            var toBundle = SecondFragmentArgs("${second_tv.text}\n吃饭睡觉打豆豆").toBundle()
            Navigation.findNavController(it)
                .navigate(R.id.action_secondFragment_to_threeFragment, toBundle)
        }
    }

好了,基本上是完成了,剩下的就不展示了.都是一样的呀😊😊
展示:


image.png
8,与底部导航的故事(BottomNavigationView)

先看下效果:

展示

看下代码
activity_main.xml

<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/main_nav_fragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        app:defaultNavHost="true"
        app:layout_constraintBottom_toTopOf="@+id/bottomNV"
        app:layout_constraintTop_toTopOf="parent"
        app:navGraph="@navigation/nav_graph_main" />

    <com.google.android.material.bottomnavigation.BottomNavigationView
        android:id="@+id/bottomNV"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="?android:attr/windowBackground"
        app:itemIconTint="@null"
        app:itemRippleColor="@null"
        app:itemTextColor="@color/text_color"
        app:labelVisibilityMode="labeled"
        app:layout_constraintBottom_toBottomOf="parent"
        app:menu="@menu/bttombar" />
</androidx.constraintlayout.widget.ConstraintLayout>

敲黑板😇😇😇
1,如果 tab 超过 3 个,不显示文字,加上app:labelVisibilityMode="labeled"这句代码.
2,去除水波纹,加上app:itemRippleColor="@null" 这句代码.
bttombar.xml

<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:id="@+id/homeFragment"
        android:icon="@drawable/home_d"
        android:title="首页" />
    <item
        android:id="@+id/secondFragment"
        android:icon="@drawable/flower_d"
        android:title="第二" />
    <item
        android:id="@+id/threeFragment"
        android:icon="@drawable/car_d"
        android:title="第三" />
    <item
        android:id="@+id/myFragment"
        android:icon="@drawable/people_d"
        android:title="我的" />
</menu>

MainActivity

class MainActivity : AppCompatActivity() {
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        bottomNV.itemIconTintList = null //自定义底部图标,需要加上这句代码
        var findNavController = Navigation.findNavController(this, R.id.main_nav_fragment)
        NavigationUI.setupWithNavController(bottomNV, findNavController)

    }
}

搞定,只需要上面 3 步,就可以实现效果了.
=======⚠️⚠️⚠️!!!但是!!! 有个问题,就 Fragment 的生命周期问题.⚠️⚠️⚠️========
我们打印一下,我依次点击了四个按钮. 循环一次,看图片

image.png

Fragment 每次生命周期每次都会被创建.....如果需要每次点击的时候,刷新 fragment 数据的话,就不要处理.如果不和BottomNavigationView一起使用的,正常切换,声明周期和 Activity 一样的.但是,如果不需要,就需要处理一下了.
原因呢???🤔🤔🤔
过于具体的原因详情,巴拉 FragmentNavigator 源码看看吧,总结一句话就是,navigation切换 fragment 的时候,调用的 replace() 方法,不是 hide() 和 show().
如何解决?
😨😨😨
很遗憾,我也没有找到更好的方法,网上一堆.......反正我没找到好用的.坐等有人帮我一把~~~

9,深层链接 DeepLink

Navigation 还有一个非常重要和实用的特性 DeepLink,深层链接.通过该特性,我们可以利用 PendingIntent 或者一个真实 URL 链接,直接跳转到应用中的某个界面.
方式一:PendingIntent
HomeFragment:

class HomeFragment : Fragment() {

   override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)
   }

   override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,savedInstanceState: Bundle?): View? {
       // Inflate the layout for this fragment
       return inflater.inflate(R.layout.fragment_home, container, false)
   }

   override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
       super.onViewCreated(view, savedInstanceState)
       home_tv.setOnClickListener {
           sendNotifacaton()   //点击事件,发送通知
       }
   }
   private fun sendNotifacaton() {
       val channelId = "1212"
       if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
           val importanceDefault = NotificationManager.IMPORTANCE_DEFAULT //重要等级
           val notificationManager: NotificationManager = requireActivity().getSystemService(NotificationManager::class.java)
           val channel = NotificationChannel(channelId, "渠道名称", importanceDefault)
           notificationManager.createNotificationChannel(channel)
       }
       val builder: NotificationCompat.Builder = NotificationCompat.Builder(requireActivity(), channelId)
           .setSmallIcon(R.mipmap.ic_launcher)
           .setContentTitle("标题")
           .setContentText("内容内容内容内容内容内容")
           .setContentIntent(getPIntent())
           .setAutoCancel(true)
       NotificationManagerCompat.from(requireActivity()).notify(0, builder.build())
   }

   //重点在这里
   fun getPIntent(): PendingIntent? {
       val bundle = Bundle()
       bundle.putString("aaa", "我是 大写的AAA")
       return Navigation.findNavController(requireActivity(), R.id.main_nav_fragment)
           .createDeepLink()
           .setGraph(R.navigation.nav_graph_main)
           .setDestination(R.id.threeFragment)
           .setArguments(bundle)
           .createPendingIntent()
   }
}

再看看,深度链接到的 Fragment:
ThreeFragment:

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        //接受通知的内容
        var get = arguments?.get("aaa") as String
        three_tv.text = "$get"
    }
}

看效果:

800EB5BF7CF39A75C7FE0DD6DFEB3D58.gif

以上就是通过 PendingIntent 实现的 DeepLink,在 HomeFragment 中点击文字发送通知,然后点击通知,跳转到 ThreeFragement.即便app 处于后台,点击通知,也一样能打开.
方式二:URL
第一步:添加<DeepLink/>标签

    <fragment
        android:id="@+id/secondFragment"
        android:name="com.mcy.test.fragment.SecondFragment"
        android:label="fragment_second"
        tools:layout="@layout/fragment_second">
         <!-- 为destination添加<deepLink/>标签 -->
        <deepLink app:uri="http://www.test.com/{name}" />
    </fragment>

第二步:为 Activity 设置<nav-graph/>标签.

     <activity android:name=".MainActivity">
            <intent-filter>
                <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_main" />
        </activity>

第三步:SecondFragment:接受参数

   override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        var let = arguments?.let { it.getString("name") }
        second_tv.text = let
        
    }

第四步:通过 adb 命令模拟实现

adb shell am start -a android.intent.action.VIEW -d "http://www.test.com/我是name参数"

⚠️ps:浏览器的方式,我没实现,大概是因为我乱写的测试地址吧.....
看图


FFE40344D42F72D6BE1D483AE5629329.gif

END

image.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 162,475评论 4 372
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,744评论 2 307
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 112,101评论 0 254
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,732评论 0 221
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,141评论 3 297
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 41,049评论 1 226
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,188评论 2 320
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,965评论 0 213
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,716评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,867评论 2 254
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,341评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,663评论 3 263
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,376评论 3 244
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,200评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,990评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,179评论 2 285
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,979评论 2 279

推荐阅读更多精彩内容