一,基本用法
1,首先需要添加依賴:
implementation 'com.google.android.material:material:1.1.0'
2,佈局文件中引入:
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/nav_view"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="0dp"
android:layout_marginEnd="0dp"
android:background="?android:attr/windowBackground"
app:menu="@menu/bottom_nav_menu" />
3,常用屬性:
-
app:itemTextColor 文字的顏色,可以通過selector來控制選中和未選中的顏色
-
app:itemIconTint 圖標的顏色,可以通過selector來控制選中和未選中的顏色
-
app:itemIconSize 圖標大小,默認24dp
-
app:iteamBackground 背景顏色,默認是主題的顏色
-
app:itemRippleColor 點擊後的水波紋顏色
-
app:itemTextAppearanceActive 設置選中時文字樣式
-
app:itemTextAppearanceInactive 設置默認的文字樣式
-
app:itemHorizontalTranslationEnabled 在label visibility 模式爲
selected
時item水平方向移動 -
app:elevation 控制控件頂部的陰影
-
app:labelVisibilityMode 文字的顯示模式
-
app:menu 指定菜單xml文件(文字和圖片都寫在這個裏面)
4,menu文件:
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/navigation_item1"
android:icon="@drawable/ic_home_black_24dp"
android:title="@string/title_size" />
<item
android:id="@+id/navigation_item2"
android:icon="@drawable/ic_dashboard_black_24dp"
android:title="@string/title_style" />
<item
android:id="@+id/navigation_item3"
android:icon="@drawable/ic_notifications_black_24dp"
android:title="@string/title_in_list" />
</menu>
在每個item中設置對應的icon和title即可。這裏的icon可以是一個drawable,也可以是包含不同狀態對應不同圖片的selector。
設置了menu後一個基本的底部菜單欄就有了。
5,常用事件
主要用兩個事件OnNavigationItemSelectedListener
和OnNavigationItemReselectedListener
nav_view.setOnNavigationItemSelectedListener(
BottomNavigationView.OnNavigationItemSelectedListener {
when (it.itemId) {
R.id.navigation_item1 -> {
Log.e("bottomMenuView:", "home")
return@OnNavigationItemSelectedListener true
}
R.id.navigation_item2 -> {
Log.e("bottomMenuView:", "dashboard")
return@OnNavigationItemSelectedListener true
}
R.id.navigation_item3 -> {
Log.e("bottomMenuView:", "notification")
return@OnNavigationItemSelectedListener true
}
}
false
})
nav_view.setOnNavigationItemReselectedListener(
BottomNavigationView.OnNavigationItemReselectedListener {
Log.e("bottomMenuView:", it.itemId.toString())
})
兩個事件的用法是一樣的,區別在於:OnNavigationItemSelectedListener
在item由未選中到選中狀態時觸發,而OnNavigationItemReselectedListener
在item處於選中狀態再次點擊時觸發。
6,最大Item數量
BottomNavigationView
對顯示的item數量做了顯示,最多5個,超過就會拋出異常,源碼如下:
public final class BottomNavigationMenu extends MenuBuilder {
public static final int MAX_ITEM_COUNT = 5;
// ...
@Override
protected MenuItem addInternal(int group, int id, int categoryOrder, CharSequence title) {
if (size() + 1 > MAX_ITEM_COUNT) {
throw new IllegalArgumentException(
"Maximum number of items supported by BottomNavigationView is "
+ MAX_ITEM_COUNT
+ ". Limit can be checked with BottomNavigationView#getMaxItemCount()");
}
// ...
return item;
}
}
google這麼做的原因大概是一般不會有超過5個的需求,而且超過5個以後就會顯得很擁擠,UI效果比較差。
二,配合fragment
單純使用BottomNavigationView
並沒有什麼卵用,一般都是配合fragment來使用。配合fragment使用時有三種方式:
1,FrameLayout + FragmentTransaction
比較古老的一種方式通過getSupportFragmentManager().beginTransaction()
獲取到FragmentTransaction
,然後通過FragmentTransaction
的add
,show
,hide
等方法來控制fragment的顯示,這種方式比較繁瑣就不贅述了。
2,ViewPager
ViewPager
一種比較流行的方式,當然你也可以用ViewPager2
,用法差不多。需要在佈局文件中添加ViewPager
。
2.1,設置ViewPager的adapter,如下:
mFragments.add(Fragment1.newInstance())
mFragments.add(Fragment2.newInstance())
mFragments.add(Fragment3.newInstance())
val adapter = object :
FragmentStatePagerAdapter(
supportFragmentManager,
BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT
) {
override fun getItem(position: Int): Fragment {
return mFragments[position]
}
override fun getCount(): Int {
return mFragments.size
}
}
viewPager.adapter = adapter
然後需要將ViewPager
和BottomNavigationView
綁定。
2.2,ViewPager綁定BottomNavigationView
就是在ViewPager
切換時更改BottomNavigationView
選中項
// 添加viewpager切換監聽
viewPager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener {
// ...
override fun onPageSelected(position: Int) {
when (position) {
0 -> {
navigation.selectedItemId = R.id.navigation_item1
}
1 -> {
navigation.selectedItemId = R.id.navigation_item2
}
2 -> {
navigation.selectedItemId = R.id.navigation_item3
}
}
}
})
2.3,BottomNavigationView綁定ViewPager
同樣的,需要在BottomNavigationView
選中項改變時更改ViewPager:
navigation.setOnNavigationItemSelectedListener(
BottomNavigationView.OnNavigationItemSelectedListener { item ->
when (item.itemId) {
R.id.navigation_item1 -> {
viewPager.currentItem = 0
return@OnNavigationItemSelectedListener true
}
R.id.navigation_item2 -> {
viewPager.currentItem = 1
return@OnNavigationItemSelectedListener true
}
R.id.navigation_item3 -> {
viewPager.currentItem = 2
return@OnNavigationItemSelectedListener true
}
}
false
})
3,配合navigation
這種方式是Google官方目前主推的方式,需要你對navigation
有所瞭解。
1,佈局文件如下:
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/nav_view"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="0dp"
android:layout_marginEnd="0dp"
android:background="?android:attr/windowBackground"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:menu="@menu/bottom_nav_menu" />
<fragment
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:layout_constraintBottom_toTopOf="@id/nav_view"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="@navigation/mobile_navigation" />
</androidx.constraintlayout.widget.ConstraintLayout>
2,navigation文件
<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/mobile_navigation"
app:startDestination="@+id/navigation_home">
<fragment
android:id="@+id/navigation_item1"
android:name="per.wsj.bottommenu.ui.fragment.Fragment1"
android:label="@string/title_home"
tools:layout="@layout/fragment_home" />
<fragment
android:id="@+id/navigation_item2"
android:name="per.wsj.bottommenu.ui.fragment.Fragment2"
android:label="@string/title_dashboard"
tools:layout="@layout/fragment_dashboard" />
<fragment
android:id="@+id/navigation_item2"
android:name="per.wsj.bottommenu.ui.fragment.Fragment3"
android:label="@string/title_notifications"
tools:layout="@layout/fragment_notifications" />
</navigation>
在navigation中指定了對應的fragment
3,Activity中
接下來的使用就很簡單了,調用Activity的擴展函數findNavController
,根據佈局文件中的fragment
標籤的id獲取NavController
,將NavController
和BottomNavigationView
綁定即可,如下:
val navView: BottomNavigationView = findViewById(R.id.nav_view)
val navController = findNavController(R.id.nav_host_fragment)
navView.setupWithNavController(navController)
效果如下:
三,顯示badge(角標/懸浮徽章)
還在github
上查找怎麼往BottomNavigationView
上添加badge嗎?BottomNavigationView
默認提供了badge。
1,基本使用
在BottomNavigationView
上添加badge很簡單,它提供瞭如下操作badge的方法:
-
getBadge(int menuItemId) 獲取badge
-
getOrCreateBadge(int menuItemId) 獲取或創建badge
-
removeBadge(int menuItemId) 移除badge
因此添加一個badge只需要如下代碼:
val navView: BottomNavigationView = findViewById(R.id.nav_view)
val badge = navView.getOrCreateBadge(R.id.navigation_dashboard)
效果如下:
納尼?怎麼只有一個紅點,胸弟別雞動,還沒設置數量
badge.number = 20
添加數量後效果如下:
2,常用屬性
getBadge
和getOrCreateBadge
方法返回的都是BadgeDrawable
,BadgeDrawable
常用的屬性/方法如下:
-
backgroundColor 設置背景色
-
badgeGravity 設置Badge的顯示位置,有四種可先:
TOP_START
,TOP_END
,BOTTOM_START
,BOTTOM_END
,分別對應左上角,右上角,左下角和右下角。 -
badgeTextColor 設置文字顏色
-
maxCharacterCount 最多顯示幾位數字,比如該項設置了3,number設置爲108,則顯示
99+
,如下圖所示:
3,注意事項
需要你Application的Theme
繼承自Theme.MaterialComponents
,如下所示:
<style name="AppTheme" parent="Theme.MaterialComponents.Light.DarkActionBar">
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
四,常用需求
1,動態顯示/隱藏MenuItem
有些時候需要根據條件來控制menuItem是否顯示,有兩種方式可以實現:
1.1, remove
val navView: BottomNavigationView = findViewById(R.id.nav_view)
navView.menu.removeItem(R.id.navigation_spacing)
這種方式是直接把這個item刪除掉了,是一個不可逆的過程,也就是說刪除後沒法再顯示出來
1.2, setVisible
// 顯示
nav_view.menu.findItem(R.id.navigation_test).isVisible = true
// 隱藏
nav_view.menu.findItem(R.id.navigation_test).isVisible = false
效果如下:
2,更改字體顏色
創建selector:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="#ff0000" android:state_checked="true"/>
<item android:color="#00CC00" android:state_checked="false"/>
</selector>
設置app:itemTextColor
屬性:
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/nav_view"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="?android:attr/windowBackground"
app:itemTextColor="@drawable/selector_menu_text_color"
app:menu="@menu/bottom_nav_menu" />
效果如下:
3,修改字體大小
字體大小分爲選中的大小和未選中的大小,他們的默認值分別是14sp/12sp,可以通過覆蓋原來的字體大小來改變字體大小。
<!--默認字體大小 -->
<dimen name="design_bottom_navigation_text_size">14sp</dimen>
<!--選中字體大小 -->
<dimen name="design_bottom_navigation_active_text_size">14sp</dimen>
修改前後的效果:
4,自定義選中圖標顏色
圖標的顏色是通過着色實現的,如果我們的圖標不是純色就需要特殊處理了。
比如如下圖標:
可以通過selector
來定義選中和未選中的狀態,並設置給menu item
的icon
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/ic_custom_icon" android:state_checked="true"/>
<item android:drawable="@drawable/ic_custom_icon_default" android:state_checked="false"/>
</selector>
然後在activity
中設置itemIconTintList
爲null
nav_view.itemIconTintList = null
效果如下:
5,labelVisibilityMode
在前面基本屬性中已經提到,文字的顯示模式有四種:
-
auto
這種模式就是item數量在三個及以下全部顯示label,三個以上只顯示選中item的label,效果如下:
-
selected
該模式下,不管item數量是多少都只顯示選中的item的label
-
labeled
該模式下,不管item數量是多少item的label都顯示
-
unlabeled
該模式下,item的label始終不顯示
label的顯示模式可以在佈局文件中通過
labelVisibilityMode
設置,也可以在java代碼中通過setLabelVisibilityMode
設置
6,切換時的動畫效果?
很多人說切換時會有動畫效果,如下:
其實這並不是什麼動畫效果,只是因爲選中時文字的字體變大了(實際上是兩個字體大小不一樣的TextView
切換顯示狀態),把圖標撐起來了,再加上點擊時的Ripple
效果,感覺就是一個高大上的動效。要去掉這個效果只需要將字體選中和默認的大小改成一致即可,效果如下:
7,圖標文字間距
7.1 調整圖標到頂部的距離
如果想調整圖標和文字間的距離,改怎麼辦呢?查了一些資料大部分都是通過添加dimen
覆蓋默認的design_bottom_navigation_margin
來實現。
<dimen name="design_bottom_navigation_margin">4dp</dimen>
該值默認是把8dp,把它調小了,發現圖標和文字的距離變大了,這是怎麼回事?其實這個距離並不是圖標和文字的間距,而是圖標距離頂部和底部的Margin
值,調小後到頂部的距離也變小了,就顯得圖標和文字的距離變大了。
如果你有顯示badge的需求,那這種方式就出問題了,因爲badge是依附於圖標的,圖標上移,badge也會跟着上移,可以就顯示不全了
7.2 調整文字到底部的距離
那麼如果我想調整文字到底部的距離呢?這就需要了解一下每個Item的佈局文件design_bottom_navigation_item.xml
,其源代碼(部分代碼省略)如下:
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<ImageView
android:id="@+id/icon"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginTop="@dimen/design_bottom_navigation_margin"
android:layout_marginBottom="@dimen/design_bottom_navigation_margin"
android:layout_gravity="center_horizontal"/>
<com.google.android.material.internal.BaselineLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|center_horizontal"
android:paddingBottom="10dp">
<TextView
android:id="@+id/smallLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="@dimen/design_bottom_navigation_text_size"/>
<TextView
android:id="@+id/largeLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="@dimen/design_bottom_navigation_active_text_size"
android:visibility="invisible"/>
</com.google.android.material.internal.BaselineLayout>
</merge>
可見如果想修改文字到底部的距離可以改動其父容器BaselineLayout
的bottomMargin
屬性,或調用父容器的scrollBy()
使內部的TextView
向下偏移,當然此時就需要用到反射。
public static void hookBnv(BottomNavigationView menu) throws Exception{
Class<BottomNavigationView> bottomNavigationViewClass = BottomNavigationView.class;
Field menuViewField = bottomNavigationViewClass.getDeclaredField("menuView");
menuViewField.setAccessible(true);
Object menuView = menuViewField.get(menu);
Class<BottomNavigationMenuView> bottomNavigationMenuViewClass = BottomNavigationMenuView.class;
Field buttonsField = bottomNavigationMenuViewClass.getDeclaredField("buttons");
buttonsField.setAccessible(true);
Object[] buttons = (Object[]) buttonsField.get(menuView);
for (Object button : buttons) {
Class<BottomNavigationItemView> bottomNavigationItemViewClass = BottomNavigationItemView.class;
Field smallLabelField = bottomNavigationItemViewClass.getDeclaredField("smallLabel");
smallLabelField.setAccessible(true);
TextView smallLabel = (TextView) smallLabelField.get(button);
// 方式一:
// ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) ((ViewGroup) smallLabel.getParent()).getLayoutParams();
// layoutParams.bottomMargin = -15;
// ((ViewGroup) smallLabel.getParent()).setLayoutParams(layoutParams);
// 方式二:
((ViewGroup) smallLabel.getParent()).scrollBy(0,-15);
}
}
效果如下:
8,修改控件高度
BottomNavigationView
的默認高度是56dp,如果遇到操蛋的需求非要改它的話就覆蓋一下design_bottom_navigation_height
吧,如下:
<dimen name="design_bottom_navigation_height">84dp</dimen>
四,結語
關於BottomNavigationView
可能用到的知識點就這些了,如果你還有還有什麼奇葩的需求,那就去**“look the fuck resource code”** 吧