準備實現 iOS 上 UITabBarController + UINavigationController 的功能,這中搭配經常出現在各種類型的 App 上。
- 三個 Tab 可以切換到不同的 Activity
- HomeActivity 提供一個按鈕可以跳轉到 Home2Activity
- Home2Activity 提供一個按鈕可以跳轉到 Home3Activity
- Home3Activity 提供一個按鈕可以跳回 MainActivity
- Toolbar 上有返回鍵,提供 Home3 -> Home2 -> Main 的返回功能。
Menu
我們建立檔案 res/menu/navigation.xml,定義三個可以點選的 Item。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
<?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="@+id/navigation_home" android:icon="@drawable/ic_home_black_24dp" android:title="@string/title_home" /> <item android:id="@+id/navigation_dashboard" android:icon="@drawable/ic_dashboard_black_24dp" android:title="@string/title_dashboard" /> <item android:id="@+id/navigation_notifications" android:icon="@drawable/ic_notifications_black_24dp" android:title="@string/title_notifications" /> </menu> |
為 Navigation 被 Click 的情況提供一個 Listener
1 2 |
// set navigation Listener navigation.setOnNavigationItemSelectedListener(onNavigationItemSelectedListener) |
在 Listener 中通過被 click 的 itemID 來判斷要切換到哪一個 Fragment
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
private val onNavigationItemSelectedListener = BottomNavigationView.OnNavigationItemSelectedListener { item -> when (item.itemId) { R.id.navigation_home -> { changeFragmentTo(FragmentType.home) return@OnNavigationItemSelectedListener true } R.id.navigation_dashboard -> { title = "Dashboard" changeFragmentTo(FragmentType.dashboard) return@OnNavigationItemSelectedListener true } R.id.navigation_notifications -> { title = "Notification" changeFragmentTo(FragmentType.notification) return@OnNavigationItemSelectedListener true } } false } |
Fragment
通過 supportFragmentManager 來管理 Fragment
1 |
val manager = supportFragmentManager |
要切換 Fragment 需要幾個步驟(來自官方文件 in Java)
1 2 3 4 5 6 7 8 9 10 11 |
<span class="com">// Create new fragment and transaction</span> <span class="typ">Fragment</span><span class="pln"> newFragment </span><span class="pun">=</span> <span class="kwd">new</span> <span class="typ">ExampleFragment</span><span class="pun">();</span> <span class="typ">FragmentTransaction</span><span class="pln"> transaction </span><span class="pun">=</span><span class="pln"> getFragmentManager</span><span class="pun">().</span><span class="pln">beginTransaction</span><span class="pun">();</span> <span class="com">// Replace whatever is in the fragment_container view with this fragment,</span> <span class="com">// and add the transaction to the back stack</span><span class="pln"> transaction</span><span class="pun">.</span><span class="pln">replace</span><span class="pun">(</span><span class="pln">R</span><span class="pun">.</span><span class="pln">id</span><span class="pun">.</span><span class="pln">fragment_container</span><span class="pun">,</span><span class="pln"> newFragment</span><span class="pun">);</span><span class="pln"> transaction</span><span class="pun">.</span><span class="pln">addToBackStack</span><span class="pun">(</span><span class="kwd">null</span><span class="pun">);</span> <span class="com">// Commit the transaction</span><span class="pln"> transaction</span><span class="pun">.</span><span class="pln">commit</span><span class="pun">();</span> |
Kotlin 的例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
private fun changeFragmentTo(type: FragmentType) { val transaction = manager.beginTransaction() when(type) { FragmentType.home -> { title = "Home" val homeFragment = HomeFragment() transaction.replace(R.id.baseFragment, homeFragment) } FragmentType.dashboard -> { val dashboardFragment = DashboardFragment() transaction.replace(R.id.baseFragment, dashboardFragment) } FragmentType.notification -> { val notificationFragment = NotificationDashboard() transaction.replace(R.id.baseFragment, notificationFragment) } } transaction.addToBackStack(null) transaction.commit() } |
activity_main.xml
在 layout_main.xml 中引入 BottomNavigationView
1 2 3 4 5 6 7 8 9 10 11 |
<android.support.design.widget.BottomNavigationView android:id="@+id/navigation" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginEnd="0dp" android:layout_marginStart="0dp" android:background="?android:attr/windowBackground" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:menu="@menu/navigation" /> |
其中我們通過下面的方法引入剛才定義好的 menu(我們在 menu 下,有建立一個 navigation 的文件,並且定義好了幾個 item)
1 |
app:menu+@menu/navigaion" |
幾個需要注意的地方
- 默認高度是 54dp
- icon 選中的顏色默認是 @color/colorPrimary
- 文字默認顏色也是 @color/colorPrimary
- 背景顏色默認是當前的樣式
顏色的部分可以直接在 <android.support.design.widget.BottomNavigationView /> 下修改
- app:itemBackground=“@android:color/red”
- app:itemIconTint=“@android:color/yellow”
- app:itemTextColor=“@android:color/green”
Navigation 返回鍵
通過在 Activity 中加入
1 |
supportActionBar?.setDisplayHomeAsUpEnabled(true) |
然後通過 override 方法來接收點擊情況
1 2 3 4 5 6 7 8 9 10 |
override fun onOptionsItemSelected(item: MenuItem): Boolean { when (item.getItemId()) { // Respond to the action bar's Up/Home button android.R.id.home -> { onBackPressed() return true } } return super.onOptionsItemSelected(item) } |
回到最初的 Activity
這裡和 iOS 的 UINavigationController 不太一樣。
通過給 Intent 加 Flag 的方式可以清除其他 Activity 跳到下一個 Activity 上。
1 2 3 |
val intentHomeActivity = Intent(this, MainActivity::class.java) intentHomeActivity.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or IntentCompat.FLAG_ACTIVITY_CLEAR_TASK) startActivity(intentHomeActivity) |
Build.gradle
iOS 通過 CocoaPods 等工具進行依賴包管理而 Android 是通過 Gradle 而 Android 世界裡看起來連官方提供的一些 UI 控件 都會通過 Gradle 加載。
如果不是通過 IDE 來建立 BottomNavigationView,需要手動在 Build.gradle 中的 dependencies 增加依賴
1 |
implementation 'com.android.support:design:26.1.0' |
Drawable 資料夾
如果是通過 new project 選擇 bottomNavigationView 的方式,會看到 IDE 幫我們在 Drawable 底下建立了幾個 icon。
比如 ic_dashboard_black_24dp.xml 文件通過 pathData 的方式把 icon 給畫了出來。
1 2 3 4 5 6 7 8 9 |
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportHeight="24.0" android:viewportWidth="24.0"> <path android:fillColor="#FF000000" android:pathData="M3,13h8L11,3L3,3v10zM3,21h8v-6L3,15v6zM13,21h8L21,11h-8v10zM13,3v6h8L21,3h-8z" /> </vector> |
筆記
- 問題:為什麼一定要在 res 下建立 menu 資料夾,而不能取其他名字,感覺是 Android 規定好的名稱?
- TODO:TabLayout + Fragment / FragmentTabHost + Fragment / RadioGroup + RadioButton + Fragment / CustomTabView + Fragment
- 問題:當我的 Activity 取名為 HomeDetailActivity 而 Layout 為 Activity_home_deail.xml 的時候,發現版面不見了…
參考
- Android 底部导航栏 (底部 Tab) 最佳实践
- 官方文件 – Fragment
- 官方文件 – 工作和返回堆疊
- 可以到 Github 上看對應的 SourceCode