Android navigation組件

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模式射亏。

navigation 官網(wǎng)地址

Activity相比好處:

  1. 拿到同一份Activity ViewModel阀溶。 ViewModelActivity為單位共享,同一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> _historyGlobalKey<OverlayState> _overlayKey保存信息钥平。所以所有頁(yè)面都是MaterialApp的子節(jié)點(diǎn)实撒。

最近的flutter 1.22推出Navigator2.0, 可以用List<Page<dynamic>> pages組織頁(yè)面,也就類似Android navigation組件涉瘾,flutter Page好比Android Fragment

  1. 導(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è)置其做。

  2. 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é))

需要注意:

  1. 系統(tǒng)返回按鈕和事件的處理幻工。跳轉(zhuǎn)Fragment時(shí),要攔截并設(shè)置好Activity的返回按鈕事件黎茎,否則整個(gè)Activity就關(guān)閉了囊颅。同時(shí)其他組件的狀態(tài)更新也需要自己維護(hù), 參考:使用 NavigationUI 更新界面組件

這個(gè)問(wèn)題在flutter Navigator2.0里同樣存在。

  1. 同級(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目錄。

2.png

點(diǎn)擊添加新的目的地(Fragment), 如果已經(jīng)存在儿惫,列表里會(huì)顯示澡罚。注意一定要以Fragment為單位

3.png

添加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)。

4.png

2. 為Activity添加NavHost

在layout文件中添加NavHostFragment, 同時(shí)會(huì)報(bào)一個(gè)警告?饵逐,提示用FragmentContainerView作為Fragment容器括眠。點(diǎn)擊Fix應(yīng)用該建議。

5.png
6.png

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)航到目的地, 用NavHostNavController來(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)目錄找到

7.png
//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)
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市表谊,隨后出現(xiàn)的幾起案子钞护,更是在濱河造成了極大的恐慌,老刑警劉巖爆办,帶你破解...
    沈念sama閱讀 219,270評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件难咕,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡押逼,警方通過(guò)查閱死者的電腦和手機(jī)步藕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)挑格,“玉大人咙冗,你說(shuō)我怎么就攤上這事∑” “怎么了雾消?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,630評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)挫望。 經(jīng)常有香客問(wèn)我立润,道長(zhǎng),這世上最難降的妖魔是什么媳板? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,906評(píng)論 1 295
  • 正文 為了忘掉前任桑腮,我火速辦了婚禮,結(jié)果婚禮上蛉幸,老公的妹妹穿的比我還像新娘破讨。我一直安慰自己丛晦,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,928評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布提陶。 她就那樣靜靜地躺著烫沙,像睡著了一般。 火紅的嫁衣襯著肌膚如雪隙笆。 梳的紋絲不亂的頭發(fā)上锌蓄,一...
    開(kāi)封第一講書(shū)人閱讀 51,718評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音撑柔,去河邊找鬼瘸爽。 笑死,一個(gè)胖子當(dāng)著我的面吹牛乏冀,可吹牛的內(nèi)容都是我干的蝶糯。 我是一名探鬼主播,決...
    沈念sama閱讀 40,442評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼辆沦,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼昼捍!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起肢扯,我...
    開(kāi)封第一講書(shū)人閱讀 39,345評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤妒茬,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后蔚晨,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體乍钻,經(jīng)...
    沈念sama閱讀 45,802評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,984評(píng)論 3 337
  • 正文 我和宋清朗相戀三年铭腕,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了银择。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,117評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡累舷,死狀恐怖浩考,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情被盈,我是刑警寧澤析孽,帶...
    沈念sama閱讀 35,810評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站只怎,受9級(jí)特大地震影響袜瞬,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜身堡,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,462評(píng)論 3 331
  • 文/蒙蒙 一邓尤、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦裁赠、人聲如沸殿漠。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,011評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至蕾哟,卻和暖如春一忱,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背谭确。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,139評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工帘营, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人逐哈。 一個(gè)月前我還...
    沈念sama閱讀 48,377評(píng)論 3 373
  • 正文 我出身青樓芬迄,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親昂秃。 傳聞我的和親對(duì)象是個(gè)殘疾皇子禀梳,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,060評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容