關於BottomNavigationView的使用姿勢都在這裏了

一,基本用法

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,常用事件

​ 主要用兩個事件OnNavigationItemSelectedListenerOnNavigationItemReselectedListener

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,然後通過FragmentTransactionaddshowhide等方法來控制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

​ 然後需要將ViewPagerBottomNavigationView綁定。

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,將NavControllerBottomNavigationView綁定即可,如下:

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,常用屬性

getBadgegetOrCreateBadge方法返回的都是BadgeDrawableBadgeDrawable常用的屬性/方法如下:

  • backgroundColor 設置背景色

  • badgeGravity 設置Badge的顯示位置,有四種可先:TOP_STARTTOP_ENDBOTTOM_STARTBOTTOM_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 itemicon

<?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>

​ 可見如果想修改文字到底部的距離可以改動其父容器BaselineLayoutbottomMargin屬性,或調用父容器的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”** 吧

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章