Android navigation组件

lzyprime 博客 (github)
创建时间:2020.11.24
qq及邮箱:2383518170

kotlin & android 笔记


λ:

navigation 组件Android Jetpack重要组成部分,推出3年左右,2018谷歌I/O大会也曾介绍过。主要用于组织Fragment,通过Fragment来实现不同内容片段的显示。包括同级之间切换,不同级之间跳转(如 列表item跳详情页),代替以往跳转Activity的方式,推出单Activity模式

navigation 官网地址

Activity相比好处:

  1. 拿到同一份Activity ViewModelViewModelActivity为单位共享,同一Activity下的Fragment可以拿到同一份ViewModel,所以如果直接跳转Activity数据共享要自己解决,传递或者全局缓存里取,同时还要考虑副本一致性,也就是我在一个页面修改数据,其他用到此数据的页面也应该同步修改。

flutter Navigator1.0里以Route组织页面,用Provider插件实现状态管理时,除非把Provider包在的MaterialApp的外层,否则,跳转其他Route就无法通过Provider.of(context)获取,数据处理就如同Android Activity, 包在最外层好使是因为MaterialApp生成时会构造一个Navigator来组织所有页面。通过List<_RouteEntry> _historyGlobalKey<OverlayState> _overlayKey保存信息。所以所有页面都是MaterialApp的子节点。

最近的flutter 1.22推出Navigator2.0, 可以用List<Page<dynamic>> pages组织页面,也就类似Android navigation组件,flutter Page好比Android Fragment

  1. 导航图可以靠可视化工具拖框完成。每一个页面称作“目的地”, 通过之间连线、设置参数来实现页面跳转约束,同时可以设置过渡动画等。所有导航资源存在资源文件夹下的navigation文件夹里。也支持Kotlin DSL代码完成导航设置。

  2. Safe Args传递数据。Safe Arges 官网地址。保证安全的传递数据。

有句话: "通常情况下,强烈建议您仅在目的地之间传递最少量的数据。例如,您应该传递键来检索对象而不是传递对象本身,因为在 Android 上用于保存所有状态的总空间是有限的。", 这同样适用于flutter。在flutter里并没有太好的副本一致性方案,所以我在Bean也就是数据解析时做了缓存,同一数据只会构造一次,之后全从缓存中取或者更新。利用factory构造函数将数据缓存、生成、更新、获取等操作隐形,达到简单的Loc的效果。(TODO: 有空再总结)

需要注意:

  1. 系统返回按钮和事件的处理。跳转Fragment时,要拦截并设置好Activity的返回按钮事件,否则整个Activity就关闭了。同时其他组件的状态更新也需要自己维护, 参考:使用 NavigationUI 更新界面组件

这个问题在flutter Navigator2.0里同样存在。

  1. 同级Fragment切换需要重新构建,并不记录状态。通过把当前的FragmentManager交给navigation来实现页面切换时,每切换一次都要重建Fragment

demo: 添加登录页,详情页

从之前demo继续开发。

# android navigation demo
# 仓库地址: https://github.com/lzyprime/android_demos
# branch: navigation

git clone -b navigation https://github.com/lzyprime/android_demos

1. 导入

使用入门 官网地址

如果用Safe Args则要在最顶层引入插件,或者用Bundle代替Safe Args实现传递

// project gradle
buildscript {
    repositories {
        google()
    }
    // Safe Args
    dependencies {
        ...
        def nav_version = "2.3.1"
        classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
    }
}
// module gradle
plugin {
    ...

    id "androidx.navigation.safeargs.kotlin"
}

dependencies {
    ...

    // navigation
    def nav_version = "2.3.1"
    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:1.0.0-alpha01"
}

2. 导航图与目的地

参考 设计导航图
条件导航(官网地址)

添加新的android resource, 类型选择Navigation, 同时会生成navigation目录。

2.png

点击添加新的目的地(Fragment), 如果已经存在,列表里会显示。注意一定要以Fragment为单位

3.png

添加3个目的地。 LoginFragment在登录成功后会由PhotoListFragment取代,因此要设置popUpTo参数。参考导航到目的地

每一条导航规则都有自己的id。 用于NavController.navigate实现跳转。

LoginFragment为起始目的地,名字前会显示“主页”图标。

4.png

2. 为Activity添加NavHost

在layout文件中添加NavHostFragment, 同时会报一个警告⚠,提示用FragmentContainerView作为Fragment容器。点击Fix应用该建议。

5.png
6.png

3. 登录功能及Activity目的地切换

此时app已经可以启动了,会显示起始目的地也就是LoginFragment

demo用到的https://api.unsplash.com/实际只需要access_key, 所以登录页只需要一个输入框和登录按钮。点击登录时会请求列表,若成功则替换为PhotoListFragment页。


// LoginFragment

class LoginFragment : Fragment(R.layout.fragment_login) {
    private val viewModel: ListPhotoViewModel by activityViewModels()

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        login_btn.setOnClickListener {
            val text = access_key_eidt_text.text.toString()
            if (text.isEmpty()) {
                Toast.makeText(context, "不能为空!", Toast.LENGTH_SHORT).show()
            } else {
                Net.ACCESS_KEY = text
                viewModel.refreshListPhotos()
            }
        }
    }
}

参考导航到目的地, 用NavHostNavController来实现目的地跳转。

参考使用 NavigationUI 更新界面组件,设置顶部appBar和系统返回事件相应

// MainActivity


@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
    private val viewModel: ListPhotoViewModel by viewModels()
    private var loginSuccess = false
    private lateinit var appBarConfiguration: AppBarConfiguration
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val navHost = supportFragmentManager.findFragmentById(R.id.mainNavHost) as NavHostFragment
        val navController = navHost.findNavController()

        appBarConfiguration = AppBarConfiguration(setOf(R.id.loginFragment, R.id.photoListFragment))
        // 顶部appBar
        setupActionBarWithNavController(navController, appBarConfiguration)

        viewModel.listPhotos.observe(this) {
            // 列表不为空,登录成功
            if (!loginSuccess && it.isNotEmpty()) {
                loginSuccess = true
                Toast.makeText(this, "登录成功", Toast.LENGTH_SHORT).show()
                navController.navigate(R.id.action_loginFragment_to_photoListFragment)
            }
        }
    }

    override fun onSupportNavigateUp(): Boolean {
        // 是否显示返回按钮
        return mainNavHost.findNavController().navigateUp(appBarConfiguration)
                || super.onSupportNavigateUp()
    }

    override fun onBackPressed() {
        // 系统返回事件
        if (!mainNavHost.findNavController().popBackStack()) finish()
    }
}

4. 点击图片进入详情页,利用Safe Args传递图片链接

参考在目的地之间传递数据, 在导航图编辑页面可视化编辑要传递的参数。

SpecifyAmountFragmentDirections, ConfirmationFragmentArgs为插件自动生成,可在java(generated)目录找到

7.png
//PhotoListFragment 跳转逻辑

class PhotoListFragment : Fragment(R.layout.fragment_photo_list) {
...


                override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
                    val photo = photos[position]

                    with(holder.itemView as ImageView) {
                        Glide.with(this).load(photo.urls.raw).into(this)
                        setOnClickListener {
                            val directions = PhotoListFragmentDirections.actionPhotoListFragmentToDetailFragment(photo.urls.raw)
                            this@PhotoListFragment.findNavController().navigate(directions)
                        }
                    }
                }

...
}

如果使用-ktx版本,可以用by navArgs()来获取传递的参数

// DetailFragment

class DetailFragment : Fragment(R.layout.fragment_detail) {
    private val args by navArgs<DetailFragmentArgs>()

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

推荐阅读更多精彩内容