前言
即學即用Android Jetpack系列Blog的目的是通過學習Android Jetpack完成一個簡單的Demo,本文是即學即用Android Jetpack系列Blog的第一篇。
記得去年第一次參加谷歌開發者大會的時候,就被Navigation的圖形導航介面給迷住了,一句臥槽就代表了小王的全部心情~,我們可以看一下來自網路的一張圖片:
所以,Android Jetpack學習之旅就開始了。
本人打算每週學習一個元件(上圖的左上區域),最後將所學的元件組成一個簡單的Demo。同時,剛剛過去的2019年穀歌開發者大會宣佈親兒子Kotlin成為開發Android的首選語言,所以本文的Demo也將都會採用Kotlin編寫。
本章結束後登入部分完成效果:
語言:KotlinDemo地址:https://github.com/mCyp/Hoo
目錄
一、簡介
1. 定義
Navigation是什麼呢?谷歌的介紹視訊上說:
Navigation是一個可簡化Android導航的庫和外掛
更確切的來說,Navigation是用來管理Fragment的切換,並且可以通過視覺化的方式,看見App的互動流程。這完美的契合了Jake Wharton大神單Activity的建議。
2. 優點
- 處理Fragment的切換(上文已說過)
- 預設情況下正確處理Fragment的前進和後退
- 為過渡和動畫提供標準化的資源
- 實現和處理深層連線
- 可以繫結Toolbar、BottomNavigationView和ActionBar等
- SafeArgs(Gradle外掛) 資料傳遞時提供型別安全性
- ViewModel支援
3. 準備
如果想要進行下面的學習,你需要 3.2 或者更高的Android studio。
4. 學習方式
最好的學習方式仍然是通過官方文件,下面是官方的學習地址:谷歌官方教程:Navigation Codelab谷歌官方文件:Navigation官方Demo:Demo地址
二、實戰
在實戰之前,我們先來了解一下Navigation中最關鍵的三要素,他們是:
名詞解釋Navigation Graph(New XML resource)如我們的第一張圖所示,這是一個新的資原始檔,使用者在視覺化介面可以看出他能夠到達的Destination(使用者能夠到達的螢幕介面),以及流程關係。NavHostFragment(Layout XML view)當前Fragment的容器NavController(Kotlin/Java object)導航的控制者
可能我這麼解釋還是有點抽象,做一個不是那麼恰當的比喻,我們可以將Navigation Graph看作一個地圖,NavHostFragment看作一個車,以及把NavController看作車中的方向盤,Navigation Graph中可以看出各個地點(Destination)和通往各個地點的路徑,NavHostFragment可以到達地圖中的各個目的地,但是決定到什麼目的地還是方向盤NavController,雖然它取決於開車人(使用者)。
第一步 新增依賴
模組層的build.gradle檔案需要新增:
ext.navigationVersion = "2.0.0"
dependencies {
//...
implementation "androidx.navigation:navigation-fragment-ktx:$rootProject.navigationVersion"
implementation "androidx.navigation:navigation-ui-ktx:$rootProject.navigationVersion"
}
如果你要使用SafeArgs外掛,還要在專案目錄下的build.gradle檔案新增:
buildscript {
ext.navigationVersion = "2.0.0"
dependencies {
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$navigationVersion"
}
}
以及模組下面的build.gradle檔案新增:
apply plugin: 'kotlin-android-extensions'
apply plugin: 'androidx.navigation.safeargs'
第二步 建立navigation導航
- 建立基礎目錄:資原始檔res目錄下建立navigation目錄 -> 右擊navigation目錄New一個Navigation resource file
- 建立一個Destination,如果說navigation是我們的導航工具,Destination是我們的目的地,在此之前,我已經寫好了一個WelcomeFragment、LoginFragment和RegisterFragment,新增Destination的操作完成後如下所示:
除了視覺化介面之外,我們仍然有必要看一下里面的內容組成,login_navigation.xml:
<navigation
...
android:id="@+id/login_navigation"
app:startDestination="@id/welcome">
<fragment
android:id="@+id/login"
android:name="com.joe.jetpackdemo.ui.fragment.login.LoginFragment"
android:label="LoginFragment"
tools:layout="@layout/fragment_login"
/>
<fragment
android:id="@+id/welcome"
android:name="com.joe.jetpackdemo.ui.fragment.login.WelcomeFragment"
android:label="LoginFragment"
tools:layout="@layout/fragment_welcome">
<action
.../>
<action
.../>
</fragment>
<fragment
android:id="@+id/register"
android:name="com.joe.jetpackdemo.ui.fragment.login.RegisterFragment"
android:label="LoginFragment"
tools:layout="@layout/fragment_register"
>
<argument
.../>
</fragment>
</navigation>
我在這裡省略了一些不必要的程式碼。讓我們看一下navigation標籤的屬性:
屬性解釋app:startDestination預設的起始位置
第三步 建立NavHostFragment
我們建立一個新的LoginActivity,在activity_login.xml檔案中:
<androidx.constraintlayout.widget.ConstraintLayout
...>
<fragment
android:id="@+id/my_nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
app:navGraph="@navigation/login_navigation"
app:defaultNavHost="true"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
有幾個屬性需要解釋一下:
屬性解釋android:name值必須是androidx.navigation.fragment.NavHostFragment,宣告這是一個NavHostFragmentapp:navGraph存放的是第二步建好導航的資原始檔,也就是確定了Navigation Graphapp:defaultNavHost="true"與系統的返回按鈕相關聯
第四步 介面跳轉、引數傳遞和動畫
在WelcomeFragment中,點選登入和註冊按鈕可以分別跳轉到LoginFragment和RegisterFragment中。
image
這裡我使用了兩種方式實現:
方式一 利用ID導航
目標:WelcomeFragment攜帶key為name的資料跳轉到LoginFragment,LoginFragment接收後顯示。Have a account ? Login按鈕的點選事件如下:
btnLogin.setOnClickListener {
// 設定動畫引數
val navOption = navOptions {
anim {
enter = R.anim.slide_in_right
exit = R.anim.slide_out_left
popEnter = R.anim.slide_in_left
popExit = R.anim.slide_out_right
}
}
// 引數設定
val bundle = Bundle()
bundle.putString("name","TeaOf")
findNavController().navigate(R.id.login, bundle,navOption)
}
後續LoginFragment的接收程式碼比較簡單,直接獲取Fragment中的Bundle即可,這裡不再出示程式碼。最後的效果:
方式二 利用Safe Args
目標:WelcomeFragment通過Safe Args將資料傳到RegisterFragment,RegisterFragment接收後顯示。再看一下已經展示過的login_navigation.xml:
<navigation
...>
<fragment
...
/>
<fragment
android:id="@+id/welcome"
>
<action
android:id="@+id/action_welcome_to_login"
app:destination="@id/login"/>
<action
android:id="@+id/action_welcome_to_register"
app:enterAnim="@anim/slide_in_right"
app:exitAnim="@anim/slide_out_left"
app:popEnterAnim="@anim/slide_in_left"
app:popExitAnim="@anim/slide_out_right"
app:destination="@id/register"/>
</fragment>
<fragment
android:id="@+id/register"
...
>
<argument
android:name="EMAIL"
android:defaultValue="2005@qq.com"
app:argType="string"/>
</fragment>
</navigation>
細心的同學可能已經觀察到navigation目錄下的login_navigation.xml資原始檔中的action標籤和argument標籤,這裡需要解釋一下:action標籤
屬性作用app:destination跳轉完成到達的fragment的Idapp:popUpTo將fragment從棧中彈出,直到某個Id的fragment
argument標籤
屬性作用android:name標籤名字app:argType標籤的型別android:defaultValue預設值
點選Android studio中的Make Project按鈕,可以發現系統為我們生成了兩個類:
WelcomeFragment中的JOIN US按鈕點選事件:
btnRegister.setOnClickListener {
val action = WelcomeFragmentDirections
.actionWelcomeToRegister()
.setEMAIL("TeaOf1995@Gamil.com")
findNavController().navigate(action)
}
RegisterFragment中的接收:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// ...
val safeArgs:RegisterFragmentArgs by navArgs()
val email = safeArgs.email
mEmailEt.setText(email)
}
以及效果:
需要提及的是,如果不用Safe Args,action可以由Navigation.createNavigateOnClickListener(R.id.next_action, null)方式生成,感興趣的同學可以自行編寫。
三、更多
Navigation可以繫結menus、drawers和bottom navigation,這裡我們以bottom navigation為例,我先在navigation目錄下新建立了main_navigation.xml,接著新建了MainActivity,下面則是activity_main.xml:
<LinearLayout
...>
<fragment
android:id="@+id/my_nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
app:navGraph="@navigation/main_navigation"
app:defaultNavHost="true"
android:layout_height="0dp"
android:layout_weight="1"/>
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/navigation_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/white"
app:itemIconTint="@color/colorAccent"
app:itemTextColor="@color/colorPrimary"
app:menu="@menu/menu_main"/>
</LinearLayout>
MainActivity中的處理也十分簡單:
class MainActivity : AppCompatActivity() {
lateinit var bottomNavigationView: BottomNavigationView
override fun onCreate(savedInstanceState: Bundle?) {
//...
val host: NavHostFragment = supportFragmentManager.findFragmentById(R.id.my_nav_host_fragment) as NavHostFragment
val navController = host.navController
initWidget()
initBottomNavigationView(bottomNavigationView,navController)
}
private fun initBottomNavigationView(bottomNavigationView: BottomNavigationView, navController: NavController) {
bottomNavigationView.setupWithNavController(navController)
}
private fun initWidget() {
bottomNavigationView = findViewById(R.id.navigation_view)
}
}
效果:
四、總結
現在都說網際網路寒冬,其實只要自身技術能力夠強,我們們就不怕!我這邊專門針對Android開發工程師整理了一套【Android進階學習視訊】、【全套Android面試祕籍】、【Android知識點PDF】。如有需要獲取資料文件的朋友,可以[點選我的GitHub]免費獲取!