Android BottomNavigationView 全部配置

直接给出标准配置

布局中

<com.google.android.material.bottomnavigation.BottomNavigationView
    android:id="@+id/navView"
    style="@style/BottomNavigationItemViewStyle"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:menu="@menu/menu_main_nav" />

styles.xml 已经包含全部属性配置

<style name="BottomNavigationItemViewStyle">
    <!-- 按下时水波纹效果 , 去除水波纹 @null -->
    <item name="itemRippleColor">@null</item>
    <!-- 在“选定”标签可见性模式下,项目是否水平平移 -->
    <item name="itemHorizontalTranslationEnabled">false</item>
    <!-- 显示标签模式,四种: auto selected labeled unlabeled-->
    <item name="labelVisibilityMode">labeled</item>
    <!-- 设置背景: itemBackground 会覆盖 android:background , 任选其一 -->
    <!-- <item name="android:background">?android:attr/windowBackground</item>-->
    <!-- <item name="itemBackground">?android:attr/windowBackground</item>-->
    <item name="itemBackground">@color/white</item>
    <!-- 图标和文字的颜色样式 + 图标大小,用选择器写控制 state_checked 属性即可 -->
    <item name="itemIconSize">@dimen/dp_20</item>
    <item name="itemIconTint">@drawable/sel_main_nav</item>
    <item name="itemTextColor">@drawable/sel_main_nav</item>
    <item name="itemIconPadding">@dimen/dp_20</item>
    <!-- 控制选中和未选中时的字体大小, 系统默认一个12sp一个14sp产生有字体缩放的效果,给一个大小就好了 -->
    <!-- 代码方式需要用到反射,不推荐 http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2017/0428/7888.html -->
    <item name="itemTextAppearanceInactive">@style/BottomNavigationItemViewText</item>
    <item name="itemTextAppearanceActive">@style/BottomNavigationItemViewText</item>
    <!--  阴影效果: https://developer.android.com/training/material/shadows-clipping -->
    <item name="elevation">@dimen/dp_5</item>
    <!-- 如果定义了不同图片进行切换,而不只是简单的改变颜色, 需要代码中设置: BottomNavigationItemView.itemIconTintList = null -->
</style>

<style name="BottomNavigationItemViewText">
    <item name="android:textSize">@dimen/font_12</item>
</style>

sel_main_nav.xml

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:color="@color/color_main_theme" android:state_checked="true" />
    <item android:color="@color/color_font_black" android:state_checked="false" />
</selector>

效果

源码中所有属性声明

<declare-styleable name="BottomNavigationView">
<!-- Background tint for the BottomNavigationView. -->
<attr name="backgroundTint"/>
<!-- The menu resource to inflate and populate items from. Attribute type definition is in
     navigation package. -->
<attr name="menu"/>
<!-- Whether navigation items display with a label, without a label, or with a label during
     selected state. Can also be "auto", which uses the item count to determine whether to show
     or hide the label. -->
<attr name="labelVisibilityMode">
  <!-- Label behaves as "labeled" when there are 3 items or less, or "selected" when there are
       4 items or more. -->
  <enum name="auto" value="-1"/>
  <!-- Label is shown on the selected navigation item. -->
  <enum name="selected" value="0"/>
  <!-- Label is shown on all navigation items. -->
  <enum name="labeled" value="1"/>
  <!-- Label is not shown on any navigation items. -->
  <enum name="unlabeled" value="2"/>
</attr>
<!-- The background for the navigation items. Attribute type definition is in navigation
     package. -->
<attr name="itemBackground"/>
<!-- The ColorStateList to use for a ripple background. This only exists because creating
     ripples in drawable xml based on theme colors is not supported pre-23. This will be ignored
     if itemBackground is set.-->
<attr format="color" name="itemRippleColor"/>
<!-- The size to provide for the navigation item icons. -->
<attr name="itemIconSize"/>
<!-- The tint to apply to the navigation item icons. Attribute type definition is in navigation
     package. -->
<attr name="itemIconTint"/>
<!-- The text appearance to apply to the inactive navigation item labels. Setting
     android:textColor in itemTextAppearanceInactive will take precedence over android:textColor
     in itemTextAppearanceActive. Instead, set itemTextColor with a ColorStateList to make
     the text color stateful. -->
<attr format="reference" name="itemTextAppearanceInactive"/>
<!-- The text appearance to apply to the active navigation item label. You should not set
     android:textColor in itemTextAppearanceActive. Instead, set itemTextColor to a
     ColorStateList to make the text color stateful. -->
<attr format="reference" name="itemTextAppearanceActive"/>
<!-- The color to apply to the navigation items' text. Setting itemTextColor will take
     precedence over android:textColor in itemTextAppearanceInactive or
     itemTextAppearanceActive. Attribute type definition is in navigation package. -->
<attr name="itemTextColor"/>
<!-- Whether the items translate horizontally when in "selected" label visibility mode. -->
<attr format="boolean" name="itemHorizontalTranslationEnabled"/>
<attr name="elevation"/>
</declare-styleable>

取消长按吐司

(BottomNavigationView.getChildAt(0) as? ViewGroup)?
.children?.forEach { it.setOnLongClickListener { true } }

即把每一个BottomNavigationItemView的长按监听吃掉。但是vivo手机会有长按震动的问题,这个无法屏蔽掉...-_-||

处理双击事件

需要从 BottomNavigationView.setOnNavigationItemSelectedListener 入手

//快速点击事件
val fastClick=object :NoShakeClickListener2(){
    override fun onFastClick(item: Any?) {
        super.onFastClick(item)
        Log.e("123", "onFastClick Click")
        //处理双击刷新逻辑...
        if(XXXFragment.isNotRefreshing){
        	balabal...
        }
    }
}
BottomNavigationView.setOnNavigationItemSelectedListener {
    switchPage(it.itemId)
    fastClick.proceedClick()
    true
}

NoShakeClickListener2源码

/**
 * 事件防抖
 * 注: 不仅适用于 View , 其他控件如: MenuItem 同样适用
 *
 * 1.既适用于单个`View`事件防抖, 也适用于`Adapter`中`ItemView`事件防抖
 * 2.如果事件为跳转到新的`Activity`, 该`Activity`启动模型应为`android:launchMode="singleTop"`
 */
open class NoShakeClickListener2 @JvmOverloads constructor(interval: Long = 500L) {

    private var mTimeInterval = 500L
    private var mLastClickTime: Long = 0   //最近一次点击的时间
    private var mLastClick: Any? = null    //最近一次点击的控件 View or MenuItem ...

    init {
        mTimeInterval = interval
    }

    fun proceedClick() {
        if (isFastClick(null, mTimeInterval)) onFastClick(null) else onSingleClick(null)
    }

    fun <T> proceedClick(item: T?) {
        if (isFastClick(item, mTimeInterval)) onFastClick(item) else onSingleClick(item)
    }

    /**
     * 是否是快速点击
     *
     * @param item      点击的控件 View or MenuItem ...
     * @param interval 时间间期(毫秒)
     * @return true:是,false:不是
     */
    private fun <T> isFastClick(item: T?, interval: Long): Boolean {
        val nowTime = System.currentTimeMillis()
        val timeInterval = abs(nowTime - mLastClickTime)
        return if (timeInterval < interval && item == mLastClick) {
            // 快速点击事件
            true
        } else {
            // 单次点击事件
            mLastClickTime = nowTime
            mLastClick = item
            false
        }
    }

    protected open fun onFastClick(item: Any?) {}
    protected open fun onSingleClick(item: Any?) {}
}

此处参考我的另外一篇日志 : Android View 事件防抖


辅助工具类BottomNavController

BottomNavController 既适用于 BottomNavigationView的底部导航方案, 也适用于RadioGroup+RadioButton 的方案。

class BottomNavController(private val containerId: Int, size: Int = 5) {

    private val fragmentArray = SparseArray<Fragment>(size)
    private lateinit var navView: BottomNavigationView
    private lateinit var fragmentManager: FragmentManager

    fun attach(navView: BottomNavigationView?, fragmentManager: FragmentManager?) {
        if (navView == null) {
            throw RuntimeException("BottomNavigationView can`t be null")
        }
        if (fragmentManager == null) {
            throw RuntimeException("FragmentManager can`t be null")
        }
        this.navView = navView
        this.fragmentManager = fragmentManager
    }

    fun putFragments(vararg arrayOfPairs: Pair<Int, Fragment>?) {
        if (arrayOfPairs.isNullOrEmpty()) return
        arrayOfPairs.forEach { p ->
            p?.apply {
                fragmentArray.put(first, second)
            }
        }
    }

    fun switchDefaultPage(pageId: Int) {
        fragmentManager
            .beginTransaction()
            .apply {
                fragmentArray.forEach { key, value ->
                    add(containerId, value, key.toString())
                }
            }
            .commitAllowingStateLoss()
        switchPage(pageId)
    }

    fun switchPage(id: Int) {
        fragmentManager
            .beginTransaction()
            .apply {
                fragmentArray.forEach { key, value ->
                    if (key == id) show(value) else hide(value)
                }
            }.commitAllowingStateLoss()
    }

    //Copy from androidx.core.util.SparseArrayKt.forEach
    private inline fun <T> SparseArray<T>.forEach(action: (key: Int, value: T) -> Unit) {
        for (index in 0 until size()) {
            action(keyAt(index), valueAt(index))
        }
    }

}

🌴 MainActivity中使用 :

@AndroidEntryPoint
class MainActivity : BaseMvvmActivity<ActivityMainBinding>() {

    private lateinit var navView: BottomNavigationView
    private lateinit var navViewController: BottomNavController
    
    override fun initView(savedInstanceState: Bundle?) {
        prepareLogin()
     
        navView = findViewById(R.id.bottomNavView)
        navViewController = BottomNavController(containerId = R.id.main_container, size = 3)
        navViewController.attach(navView, supportFragmentManager)
        navViewController.putFragments(
            R.id.nav_home to HomeFragment(),
            R.id.nav_repo to RepoFragment(),
            R.id.nav_mine to MineFragment()
        )

        //屏蔽长按吐司
        (navView.getChildAt(0) as? ViewGroup)?.children?.forEach { it.setOnLongClickListener { true } }

        //快速点击事件
        val fastClick = object : NoShakeClickListener2() {
            override fun onFastClick(item: Any?) {
                super.onFastClick(item)
                (item as? MenuItem?)?.apply {
                    //val fg = fragmentArray.get(itemId)
                    //if (fg.isAdded) fg.refreshData()
                }
            }
        }

        navView.setOnNavigationItemSelectedListener {
            navViewController.switchPage(it.itemId)
            fastClick.proceedClick(it)
            true
        }
    }
    
    private fun prepareLogin() {
        repo.loginByToken()?.observe(this) { r ->
            if (r?.isSuccessful == true) {
                r.body?.apply {
            		...
                    isLogin = true
                    //登录成功后选中 HomeFragment 页面
                    navViewController.switchDefaultPage(R.id.nav_home)
                }
            } else {
                AppRouter.toLogin(this)//未登录 跳转至 注册/登录页
            }
        }
    }
}

activit_main.xml :

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    android:orientation="vertical">
    
    <FrameLayout
        android:id="@id/main_container"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />
        
    <com.google.android.material.bottomnavigation.BottomNavigationView
        android:id="@+id/bottomNavView"
        style="@style/BottomNavigationItemViewStyle"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:menu="@menu/menu_nav_main" />
</LinearLayout>

本文作者:风之旅人

本文链接:https://www.cnblogs.com/jooy/p/17302052.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   javakam  阅读(0)  评论(0编辑  收藏  举报  
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起