lzyprime 博客 (github)
創(chuàng)建時(shí)間:2020.11.24
qq及郵箱:2383518170
kotlin & android 筆記
λ:
navigation 組件
是 Android Jetpack
重要組成部分沪斟,推出3年左右速客,2018谷歌I/O大會(huì)也曾介紹過(guò)瞒爬。主要用于組織Fragment
,通過(guò)Fragment
來(lái)實(shí)現(xiàn)不同內(nèi)容片段的顯示。包括同級(jí)之間切換诽嘉,不同級(jí)之間跳轉(zhuǎn)(如 列表item跳詳情頁(yè))进每,代替以往跳轉(zhuǎn)Activity
的方式,推出單Activity模式
射亏。
與Activity
相比好處:
- 拿到同一份
Activity ViewModel
阀溶。ViewModel
以Activity
為單位共享,同一Activity
下的Fragment
可以拿到同一份ViewModel
鸦泳,所以如果直接跳轉(zhuǎn)Activity
數(shù)據(jù)共享要自己解決银锻,傳遞或者全局緩存里取,同時(shí)還要考慮副本一致性做鹰,也就是我在一個(gè)頁(yè)面修改數(shù)據(jù)击纬,其他用到此數(shù)據(jù)的頁(yè)面也應(yīng)該同步修改。
flutter Navigator1.0
里以Route
組織頁(yè)面钾麸,用Provider
插件實(shí)現(xiàn)狀態(tài)管理時(shí)更振,除非把Provider
包在的MaterialApp
的外層,否則饭尝,跳轉(zhuǎn)其他Route
就無(wú)法通過(guò)Provider.of(context)
獲取肯腕,數(shù)據(jù)處理就如同Android Activity
, 包在最外層好使是因?yàn)?code>MaterialApp生成時(shí)會(huì)構(gòu)造一個(gè)Navigator
來(lái)組織所有頁(yè)面。通過(guò)List<_RouteEntry> _history
和GlobalKey<OverlayState> _overlayKey
保存信息钥平。所以所有頁(yè)面都是MaterialApp
的子節(jié)點(diǎn)实撒。最近的
flutter 1.22
推出Navigator2.0
, 可以用List<Page<dynamic>> pages
組織頁(yè)面,也就類似Android navigation
組件涉瘾,flutter Page
好比Android Fragment
導(dǎo)航圖可以靠可視化工具拖框完成知态。每一個(gè)頁(yè)面稱作“目的地”, 通過(guò)之間連線立叛、設(shè)置參數(shù)來(lái)實(shí)現(xiàn)頁(yè)面跳轉(zhuǎn)約束负敏,同時(shí)可以設(shè)置過(guò)渡動(dòng)畫等。所有導(dǎo)航資源存在資源文件夾下的
navigation
文件夾里秘蛇。也支持Kotlin DSL
代碼完成導(dǎo)航設(shè)置其做。Safe Args
傳遞數(shù)據(jù)顶考。Safe Arges 官網(wǎng)地址。保證安全的傳遞數(shù)據(jù)妖泄。
有句話: "通常情況下驹沿,強(qiáng)烈建議您僅在目的地之間傳遞最少量的數(shù)據(jù)。例如浮庐,您應(yīng)該傳遞鍵來(lái)檢索對(duì)象而不是傳遞對(duì)象本身甚负,因?yàn)樵?Android 上用于保存所有狀態(tài)的總空間是有限的。", 這同樣適用于
flutter
审残。在flutter
里并沒(méi)有太好的副本一致性方案梭域,所以我在Bean
也就是數(shù)據(jù)解析時(shí)做了緩存,同一數(shù)據(jù)只會(huì)構(gòu)造一次搅轿,之后全從緩存中取或者更新病涨。利用factory
構(gòu)造函數(shù)將數(shù)據(jù)緩存、生成璧坟、更新既穆、獲取等操作隱形,達(dá)到簡(jiǎn)單的Loc
的效果雀鹃。(TODO: 有空再總結(jié))
需要注意:
- 系統(tǒng)返回按鈕和事件的處理幻工。跳轉(zhuǎn)
Fragment
時(shí),要攔截并設(shè)置好Activity
的返回按鈕事件黎茎,否則整個(gè)Activity
就關(guān)閉了囊颅。同時(shí)其他組件的狀態(tài)更新也需要自己維護(hù), 參考:使用 NavigationUI 更新界面組件
這個(gè)問(wèn)題在
flutter Navigator2.0
里同樣存在。
- 同級(jí)
Fragment
切換需要重新構(gòu)建傅瞻,并不記錄狀態(tài)踢代。通過(guò)把當(dāng)前的FragmentManager
交給navigation
來(lái)實(shí)現(xiàn)頁(yè)面切換時(shí),每切換一次都要重建Fragment
嗅骄。
demo: 添加登錄頁(yè)胳挎,詳情頁(yè)
從之前demo繼續(xù)開(kāi)發(fā)。
# android navigation demo
# 倉(cāng)庫(kù)地址: https://github.com/lzyprime/android_demos
# branch: navigation
git clone -b navigation https://github.com/lzyprime/android_demos
1. 導(dǎo)入
使用入門 官網(wǎng)地址
如果用Safe Args
則要在最頂層引入插件溺森,或者用Bundle
代替Safe Args
實(shí)現(xiàn)傳遞
// 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. 導(dǎo)航圖與目的地
參考 設(shè)計(jì)導(dǎo)航圖
和 條件導(dǎo)航(官網(wǎng)地址)慕爬。
添加新的android resource
, 類型選擇Navigation
, 同時(shí)會(huì)生成navigation目錄。
點(diǎn)擊添加新的目的地(Fragment
), 如果已經(jīng)存在儿惫,列表里會(huì)顯示澡罚。注意一定要以Fragment
為單位
添加3個(gè)目的地。 LoginFragment
在登錄成功后會(huì)由PhotoListFragment
取代肾请,因此要設(shè)置popUpTo
參數(shù)。參考導(dǎo)航到目的地
每一條導(dǎo)航規(guī)則都有自己的id更胖。 用于NavController.navigate
實(shí)現(xiàn)跳轉(zhuǎn)铛铁。
LoginFragment
為起始目的地隔显,名字前會(huì)顯示“主頁(yè)”圖標(biāo)。
2. 為Activity
添加NavHost
在layout文件中添加NavHostFragment
, 同時(shí)會(huì)報(bào)一個(gè)警告?饵逐,提示用FragmentContainerView
作為Fragment
容器括眠。點(diǎn)擊Fix應(yīng)用該建議。
3. 登錄功能及Activity
目的地切換
此時(shí)app已經(jīng)可以啟動(dòng)了倍权,會(huì)顯示起始目的地也就是LoginFragment
掷豺。
demo用到的https://api.unsplash.com/實(shí)際只需要access_key, 所以登錄頁(yè)只需要一個(gè)輸入框和登錄按鈕。點(diǎn)擊登錄時(shí)會(huì)請(qǐng)求列表薄声,若成功則替換為PhotoListFragment
頁(yè)当船。
// 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()
}
}
}
}
參考導(dǎo)航到目的地, 用NavHost
的NavController
來(lái)實(shí)現(xiàn)目的地跳轉(zhuǎn)。
參考使用 NavigationUI 更新界面組件默辨,設(shè)置頂部appBar和系統(tǒng)返回事件相應(yīng)
// 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() {
// 系統(tǒng)返回事件
if (!mainNavHost.findNavController().popBackStack()) finish()
}
}
4. 點(diǎn)擊圖片進(jìn)入詳情頁(yè)德频,利用Safe Args
傳遞圖片鏈接
參考在目的地之間傳遞數(shù)據(jù), 在導(dǎo)航圖編輯頁(yè)面可視化編輯要傳遞的參數(shù)。
SpecifyAmountFragmentDirections
, ConfirmationFragmentArgs
為插件自動(dòng)生成缩幸,可在java(generated)目錄找到
//PhotoListFragment 跳轉(zhuǎn)邏輯
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()
來(lái)獲取傳遞的參數(shù)
// 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)
}
}