由于Android的開源性哟沫,在開始的幾年呈現(xiàn)出了百家齊放的盛況匕得,層出不窮的API和以及官方的API各自大放異彩掠河,在豐富了android生態(tài)的同時(shí)也帶來了一個(gè)很嚴(yán)重的問題,即android 的碎片化和規(guī)范化的問題灯节。碎片化主要集中于國(guó)內(nèi)的各大手機(jī)廠商的多種屏幕尺寸循头、多種手機(jī)分辨率、多種機(jī)型的多樣性上炎疆,而規(guī)范化則是集中于開發(fā)生態(tài)上面贷岸,由于Google官方的弱約束,很多時(shí)候大家都隨心所欲以業(yè)務(wù)驅(qū)動(dòng)技術(shù)去進(jìn)行開發(fā)磷雇,導(dǎo)致沒有一個(gè)很好的開發(fā)整體框架偿警。基于此唯笙,Google官方在2018年發(fā)布了一系列輔助開發(fā)Android開發(fā)者的實(shí)用工具螟蒸,合成JetPack。
一崩掘、什么是JetPack?
根據(jù)官方介紹七嫌,JetPack是一個(gè)由多個(gè)庫組成的套件,可幫助開發(fā)者遵循最佳做法苞慢,減少樣板代碼并編寫可在各種Android版本和設(shè)備中一致運(yùn)行的代碼诵原,讓開發(fā)者可將精力集中于真正重要的編碼工作。下面是官方關(guān)于Jetpack的描述圖
1. 基礎(chǔ)組件
(1) AppCompat:使得支持較低的 Android 版本挽放。從以前繼承 Activity 到現(xiàn)在繼承AppCompatActivity 就是屬于這一部分
(2) Android KTX:Kotlin 的擴(kuò)展支持庫
(3) Multidex:多 dex 文件支持
(4) Test:測(cè)試支持庫
2. 架構(gòu)組件
(1) Data Binding:MVVM 的一種實(shí)踐
(2) Lifecycles:管理你的 Activity 和 Fragment 生命周期
(3) LiveData:通過觀察者模式感知數(shù)據(jù)變化绍赛,類比 RxJava
(4) Navigation:處理 Fragment 導(dǎo)航相關(guān)邏輯
(5) Paging:分頁數(shù)據(jù)加載方案
(6) Room:官方 ORM 庫
(7) ViewModel:通過數(shù)據(jù)驅(qū)動(dòng) V 視圖發(fā)生改變
(8) WorkManager:管理后臺(tái)任務(wù)
3. 行為組件
(1) DownloadManager:管理下載任務(wù)
(2) Media App:多媒體播放和一些向后兼容的API。主要包含 MediaPlayer 和 ExoPlayer
(3) Notifications:提供向后兼容的通知 API辑畦,支持 Wear 和 Auto
(4) Permissions:權(quán)限管理吗蚌,這個(gè)應(yīng)該都接觸過。用于檢查和請(qǐng)求應(yīng)用權(quán)限
(5) Settings:Preference 相關(guān) API纯出◎歉荆基本每個(gè)應(yīng)用都會(huì)用到
(6) Share Action:提供分享操作敷燎。這塊在國(guó)內(nèi)使用的不多,都是自己封裝或者采用第三方方案
(7) Slices:可以讓應(yīng)用通過外部(其他 APP)顯示 APP 界面(通過設(shè)備自帶的搜索箩言,語音助手等)
4. 界面組件
(1) Animations and Transitions:動(dòng)畫硬贯,界面轉(zhuǎn)場(chǎng)等
(2) Auto:針對(duì)車輛的標(biāo)準(zhǔn)化界面和模式
(3) Emoji:表情符號(hào)相關(guān)
(4) Fragment:基礎(chǔ)概念
(5) Layout:基礎(chǔ)概念
(6) Palette-Colors:調(diào)色板
(7) TV:Android TV 開發(fā)相關(guān)
(8) Wear:可穿戴設(shè)備(目前主要是手表)開發(fā)相關(guān)
二、JetPack到底值不值得我們?nèi)?yīng)用?
說實(shí)話陨收,這個(gè)問題仁者見仁智者見智澄成,因?yàn)闆]有什么東西是絕對(duì)的好或者不好,看你自己的需求而定畏吓,就像MVC、MVP卫漫、MVVM框架一樣菲饼,沒有說哪種模式一定最好,很多時(shí)候是要根據(jù)實(shí)際情況來定列赎。所以宏悦,同等道理,Jetpack到底要不要用包吝,怎么用看開發(fā)需求饼煞,但是對(duì)于我們開發(fā)者來說,掌握它卻是必不可少的诗越,只有完全的掌握它砖瞧,知道了它的優(yōu)點(diǎn)和缺點(diǎn)才能更好的做出理性的判斷在實(shí)際的開發(fā)者到底要不要用它。
三嚷狞、進(jìn)入正題——Jetpack組件之一Navigation
1.什么是Navigation
Navigation是一個(gè)可簡(jiǎn)化的Android導(dǎo)航的庫和插件块促,換句話說,Navigation是用來管理Fragment的切換的床未,并且是通過可視化的方式來進(jìn)行管理的竭翠。
2.Navigation的優(yōu)缺點(diǎn)
優(yōu)點(diǎn)
- 處理Fragment的切換
- 默認(rèn)情況下正確處理Fragment的前進(jìn)和后退
- 為過渡和動(dòng)畫提供標(biāo)準(zhǔn)化的資源
- 可以綁定Toolbar/BottomNavigationView/ActionBar等
- 數(shù)據(jù)傳遞時(shí)提供類型安全性(使用SafeArgs)
- ViewModel支持
缺點(diǎn)
- fragment切換后底層會(huì)調(diào)用replace方法導(dǎo)致會(huì)被不斷銷毀,無法保存上一次的狀態(tài)
3.Navigation的使用
Navigation的使用相對(duì)來說比較簡(jiǎn)答薇搁,分為以下幾步:
(1)引入依賴
(2)創(chuàng)建多個(gè)要調(diào)配的Fragment
(3)在res下面創(chuàng)建navigation文件夾斋扰,并創(chuàng)建navigation文件
(4)在主Activity里面的XML文件里面引入指定的Fragment
基本上大體步驟就那么幾步,現(xiàn)在我們就一個(gè)一個(gè)來看啃洋。
dependencies {
// Java引入
implementation "androidx.navigation:navigation-fragment:2.2.2"
implementation "androidx.navigation:navigation-ui:2.2.2"
// kotlin引入
implementation 'androidx.navigation:navigation-fragment-ktx:2.2.2'
implementation 'androidx.navigation:navigation-ui-ktx:2.2.2'
}
步驟二:創(chuàng)建多個(gè)要調(diào)配的Fragment
這里我創(chuàng)建了3個(gè)Fragment传货,第一個(gè)是歡迎頁面的WelcomeFragment,第二個(gè)是注冊(cè)的RegisterFragment宏娄,第三個(gè)是登錄頁面的LoginFragment损离。分別如下:
歡迎頁面的WelcomeFragment
class WelcomeFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_welcome, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
view.findViewById<Button>(R.id.register_jump).setOnClickListener {
val navOption = navOptions {
anim {
enter = R.anim.common_slide_in_right
exit = R.anim.common_slide_out_left
popEnter = R.anim.common_slide_in_left
popExit = R.anim.common_slide_out_right
}
}
findNavController().navigate(R.id.registerFragment, null, navOption)
}
view.findViewById<Button>(R.id.login_jump).setOnClickListener {
val navOption = navOptions {
anim {
enter = R.anim.common_slide_in_right
exit = R.anim.common_slide_out_left
popEnter = R.anim.common_slide_in_left
popExit = R.anim.common_slide_out_right
}
}
val bundle = LoginFragmentArgs.Builder().setLoginInfo("jack").build().toBundle()
findNavController().navigate(R.id.loginFragment,bundle,navOption)
}
}
}
注冊(cè)的RegisterFragment
class RegisterFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_register, container, false)
}
}
登錄頁面的LoginFragment
class LoginFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_login, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val bundle = arguments
if (bundle!=null){
val loginTag = LoginFragmentArgs.fromBundle(bundle).loginInfo
view.findViewById<TextView>(R.id.login_info).text = loginTag
}
}
}
步驟三:在res下面創(chuàng)建navigation文件夾,并創(chuàng)建navigation文件
需要注意的是navigation文件夾必須在res文件夾下面并且名稱為固定寫法绝编,這里我新建了一個(gè)splash_navigation.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/splash_navigation"
app:startDestination="@id/welcomeFragment">
<fragment
android:id="@+id/welcomeFragment"
android:name="com.jack.androidjetpack.login.WelcomeFragment"
android:label="fragment_welcome"
tools:layout="@layout/fragment_welcome">
<action
android:id="@+id/action_welcomeFragment_to_registerFragment"
app:destination="@id/registerFragment" />
<action
android:id="@+id/action_welcomeFragment_to_loginFragment"
app:destination="@id/loginFragment" />
</fragment>
<fragment
android:id="@+id/registerFragment"
android:name="com.jack.androidjetpack.login.RegisterFragment"
android:label="fragment_register"
tools:layout="@layout/fragment_register" />
<fragment
android:id="@+id/loginFragment"
android:name="com.jack.androidjetpack.login.LoginFragment"
android:label="fragment_login"
tools:layout="@layout/fragment_login">
<argument
android:name="loginInfo"
android:defaultValue="defaultValue"
app:argType="string" />
</fragment>
</navigation>
這個(gè)XML文件是Navigation里面一個(gè)比較重要的文件貌踏,下面我們慢慢來分析一下里面包含了哪些東西:
名稱 | 含義 |
---|---|
<navigation>/</navigation> | 文件必須以<navigation>開頭,以</navigation>結(jié)尾窟勃,標(biāo)識(shí)這是一個(gè)Navigation文件 |
app:startDestination | 默認(rèn)的起始的fragment所對(duì)應(yīng)的id祖乳,此處意味著WelcomeFragment作為起始的Fragment |
<fragment> | Navigation所操作的都是Fragment |
<fragment android:id | 對(duì)應(yīng)的Fragment的id |
<fragment android:name | 對(duì)應(yīng)的Fragment,注意這里的值必須是完整的Fragment的路徑 |
<fragment android:layout | 對(duì)應(yīng)的Fragment的布局文件 |
<fragment <action | Fragment對(duì)應(yīng)的跳轉(zhuǎn)行為 |
<fragment <argument | Fragment傳參 |
<argument | Fragment傳參 |
<argument android:name | Fragment傳參的key |
<argument android:defaultValue | Fragment傳參的參數(shù)默認(rèn)值 |
<argument app:argType | Fragment傳參的參數(shù)對(duì)應(yīng)的類型 |
步驟四:在主Activity里面的XML文件里面引入指定的Fragment
主Activity很簡(jiǎn)單秉氧,沒有寫一行代碼
/**
* 歡迎頁面
*/
class WelcomeActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_welcome)
}
}
對(duì)應(yīng)的activity_welcome文件
<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=".login.WelcomeActivity">
<fragment
android:id="@+id/welcome_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:navGraph="@navigation/splash_navigation" />
</androidx.constraintlayout.widget.ConstraintLayout>
不知道大家發(fā)現(xiàn)沒有眷昆,在WelcomeActivity對(duì)應(yīng)的XML文件里面我們并沒有引用我們自己創(chuàng)建的Fragment,而是引用了一個(gè)NavHostFragment汁咏,那么NavHostFragment到底是什么呢亚斋?這里就要介紹幾個(gè)概念了:
(1) NavHostFragment:NavHostFragment是我們引入的Navigation組件提供的一個(gè)Fragment,其實(shí)現(xiàn)了NavHost接口攘滩,可以將它理解為系統(tǒng)默認(rèn)的一個(gè)Fragment模板帅刊,或者是當(dāng)前Fragment的容器。
(2)app:navGraph:NavHostFragment 與導(dǎo)航圖相關(guān)聯(lián)工作由它完成漂问,在navigation中完成到目的視圖導(dǎo)航
(3)app:defaultNavHost:是否被系統(tǒng)返回鍵攔截
需要強(qiáng)調(diào)的是赖瞒,在fragment中android:name="androidx.navigation.fragment.NavHostFragment"
是固定寫法。
基礎(chǔ)工作已經(jīng)結(jié)束了蚤假,接下來我們解決兩個(gè)問題:
- (1)Fragment的切換
- (2)Fragment的傳參
先說明的就是栏饮,fragment的切換其實(shí)就是通過replace的方法來實(shí)現(xiàn)的,相信這個(gè)做法很多人都不陌生磷仰,只不過以前是我們自己手動(dòng)實(shí)現(xiàn)(包括回退棧)袍嬉,但是現(xiàn)在借助Navigation我們不必去手動(dòng)實(shí)現(xiàn),Navigation在底部幫我們實(shí)現(xiàn)了灶平,我們只需要按照Navigation的模板去實(shí)現(xiàn)就好了冬竟。
Fragment的切換
findNavController().navigate(R.id.registerFragment, null, navOption)
Fragment的切換很簡(jiǎn)單,一行代碼就可以搞定民逼,不過我這是kotlin的寫法泵殴,Java的寫法稍微復(fù)雜一點(diǎn)。
NavigationController navigationController = Navigation.findNavController(it)
navigationController.navigate(R.id.registerFragment, null, navOption)
但是不管是Java寫法還是kotlin寫法底層都是一樣的拼苍,這里需要用到一個(gè)很重要的東西NavigationController笑诅,顧名思義NavigationController是用來控制Navigation的操作的,最終我們切換Fragment就是依靠它來實(shí)現(xiàn)的疮鲫。NavigationController提供了一個(gè)方法navigate吆你,navigate的重載方法有很多,這里簡(jiǎn)單介紹幾個(gè):
// 通過傳遞我們要跳轉(zhuǎn)的Fragment來實(shí)現(xiàn)切換
// resId:目標(biāo)Fragment的id
public void navigate(@IdRes int resId) {
navigate(resId, null);
}
// 通過傳遞我們要跳轉(zhuǎn)的Fragment來實(shí)現(xiàn)切換
// resId:目標(biāo)Fragment的id
// Bundle args:傳遞給目標(biāo)Fragment的參數(shù)
public void navigate(@IdRes int resId, @Nullable Bundle args) {
navigate(resId, args, null);
}
// 通過傳遞我們要跳轉(zhuǎn)的Fragment來實(shí)現(xiàn)切換
// resId:目標(biāo)Fragment的id
// Bundle args:傳遞給目標(biāo)Fragment的參數(shù)
// NavOptions navOptions:Fragment切換時(shí)候的過渡動(dòng)畫
public void navigate(@IdRes int resId, @Nullable Bundle args,
@Nullable NavOptions navOptions) {
navigate(resId, args, navOptions, null);
}
這個(gè)比較簡(jiǎn)單俊犯,一看就懂了妇多,關(guān)于怎么傳參放到下面去講,我們?cè)谶@里先看看怎么實(shí)現(xiàn)動(dòng)畫
val navOption = navOptions {
anim {
enter = R.anim.common_slide_in_right
exit = R.anim.common_slide_out_left
popEnter = R.anim.common_slide_in_left
popExit = R.anim.common_slide_out_right
}
}
實(shí)現(xiàn)動(dòng)畫也很簡(jiǎn)單燕侠,創(chuàng)建一個(gè)NavOptions在里面設(shè)置好我們想要的動(dòng)畫效果
common_slide_in_right
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="200"
android:fromXDelta="100%"
android:fromYDelta="0%"
android:toXDelta="0%"
android:toYDelta="0%" />
</set>
common_slide_out_left
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="200"
android:fromXDelta="0%"
android:fromYDelta="0%"
android:toXDelta="-100%"
android:toYDelta="0%" />
</set>
common_slide_in_left
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="200"
android:fromXDelta="-100%"
android:fromYDelta="0%"
android:toXDelta="0%"
android:toYDelta="0%" />
</set>
common_slide_out_right
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="200"
android:fromXDelta="0%"
android:fromYDelta="0%"
android:toXDelta="100%"
android:toYDelta="0%" />
</set>
Fragment的傳參
Fragment的傳參方式也有兩種:
- 方式一:常規(guī)傳參
// 傳遞參數(shù)
val bundle = Bundle()
bundle.putString("loginInfo","steven")
findNavController().navigate(R.id.loginFragment,bundle,navOption)
// 獲取參數(shù)
val bundle = arguments
if (bundle!=null){
val loginTag = bundle.getString("loginInfo")
view.findViewById<TextView>(R.id.login_info).text = loginTag
}
- 方式二:使用系統(tǒng)推薦的插件Safe Args
Safe Args使用步驟:
(1)添加依賴
在項(xiàng)目的builder.gradle下面添加
dependencies {
...
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.3.0"
}
(2)在主module的builder.gradle下面添加
plugins {
...
id 'androidx.navigation.safeargs'
}
(3)在navigation文件夾下面我們創(chuàng)建的文件下面要跳轉(zhuǎn)的目標(biāo)Fragment下面設(shè)置argument。
<fragment
android:id="@+id/loginFragment"
android:name="com.jack.androidjetpack.login.LoginFragment"
android:label="fragment_login"
tools:layout="@layout/fragment_login">
<argument
// 傳遞的參數(shù)key
android:name="loginInfo"
// 默認(rèn)值
android:defaultValue="defaultValue"
// 參數(shù)對(duì)應(yīng)的類型
app:argType="string" />
</fragment>
(4)Build-> Make Project
完成后會(huì)發(fā)現(xiàn)在我們的app->build->generated->source->navigation-args->debug->com.jack.androidjetpack.login文件夾下面有一個(gè)LoginFragmentArgs文件,而這個(gè)文件正是用來傳遞參數(shù)使用的唤锉。
(5)創(chuàng)建我們的bundle并進(jìn)行傳遞
val bundle = LoginFragmentArgs.Builder().setLoginInfo("jack").build().toBundle()
(6)獲取我們傳遞的值
val bundle = arguments
if (bundle != null) {
val loginTag = LoginFragmentArgs.fromBundle(bundle).loginInfo
view.findViewById<TextView>(R.id.login_info).text = loginTag
}
這樣通過插件傳值就結(jié)束了骗露。
可以看到遭居,可能會(huì)有人會(huì)疑惑,用Bundle不是更簡(jiǎn)單些嗎,Safe Args 傳遞方式不僅編碼復(fù)雜,還要安裝它的插件刹淌,不會(huì)太麻煩嗎?既然是Google推薦讥耗,那當(dāng)然是有道理的這里不再啰嗦有勾,直接貼上官方說明
感覺其實(shí)官方就說了一句話:使用Safe Args可以確保數(shù)據(jù)安全。但是說實(shí)話我還沒有太明白和我們以往直接通過bundle有什么區(qū)別古程。知道的同學(xué)可以在下面留言大家探討探討蔼卡。