欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Kotlin中使用BottomNavigationView实现底部导航

程序员文章站 2022-06-09 21:24:48
...

Aandroid Design Support Library中增加了BottonNavigationView控件,实现底部导航切换页面方便了许多,同时它也有不便之处:

 1. 底部的条目数超过三个,点击每个条目是会有很大的偏移量
 2. 无法添加小红点提示

以前为了实现底部导航切换页面,我通常用以下两种方式

 1. TabLayout+ViewPager+Fragment 方式实现
 2. RadioGroup+ViewPager+Fragment 方式实现

最终的效果图

Kotlin中使用BottomNavigationView实现底部导航

1. 添加依赖(25以上)

implementation 'com.android.support:design:27.1.1'

2. xml布局使用

<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

<RelativeLayout
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:clipChildren="false"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <android.support.design.widget.BottomNavigationView
        android:id="@+id/bottom_navigation"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:background="?android:attr/windowBackground"
        app:menu="@menu/navigation" />

    <com.zuoge.project.view.NoScrollViewPager
        android:id="@+id/vp_main"
        android:layout_above="@id/bottom_navigation"
         android:layout_width="match_parent"
        android:layout_height="match_parent"/>
</RelativeLayout>
</layout>

其中有几个特有属性

1. app:itemBackground:指定底部导航栏的背景颜色,默认是当前的主题颜色
2. app:itemIconTint:指定底部导航栏元素图标的着色方式,默认元素选中是iocn颜色为@color/colorPrimary
3. app:itemTextColor:指定底部导航栏元素文字的着色方式
4. app:menu:使用Menu的形式为底部导航栏指定元素

使用menu属性定义底部条目:

1. 在res文件下创建menu文件夹
2. 在menu文件下创建条目的xml
3. 设置id、icon、title等属性
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">

    <item
        android:id="@+id/menu"
        android:icon="@drawable/main_menu"
        android:title="@string/first" />

    <item
        android:id="@+id/more"
        android:icon="@drawable/main_more"
        android:title="@string/more" />

    <item
        android:id="@+id/information"
        android:icon="@drawable/main_information"
        android:title="@string/information" />

    <item
        android:id="@+id/me"
        android:icon="@drawable/main_me"
        android:title="@string/me" />
</menu>

3. 代码实现

kotlin可以直接通过id获取到控件,不在使用findViewById获取控件

设置BottonNavigationView底部条目的点击监听

    bottom_navigation.setOnNavigationItemSelectedListener(mBottomNavigationView)
 

mBottomNavigationView

  
       private val mBottomNavigationView = BottomNavigationView.OnNavigationItemSelectedListener { item ->


        when (item.itemId) {
            R.id.menu -> {

                vp_main.currentItem = 0

                aaa@qq.com true
            }
            R.id.more -> {

                vp_main.currentItem = 1


                aaa@qq.com true
            }
            R.id.me -> {

                vp_main.currentItem = 2


                aaa@qq.com true
            }
            R.id.me2 -> {

                vp_main.currentItem = 3


                aaa@qq.com true
            }

        }
        false

    }

Kotlin中使用BottomNavigationView实现底部导航

啊,不是我想要的效果,我想要的是4个item平分布局

4. 查看源码解决平分布局

Kotlin中使用BottomNavigationView实现底部导航
进入到BottomNavigationMeunView

 @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        final int width = MeasureSpec.getSize(widthMeasureSpec);
        final int count = getChildCount();

        final int heightSpec = MeasureSpec.makeMeasureSpec(mItemHeight, MeasureSpec.EXACTLY);

        if (mShiftingMode) {
            final int inactiveCount = count - 1;
            final int activeMaxAvailable = width - inactiveCount * mInactiveItemMinWidth;
            final int activeWidth = Math.min(activeMaxAvailable, mActiveItemMaxWidth);
            final int inactiveMaxAvailable = (width - activeWidth) / inactiveCount;
            final int inactiveWidth = Math.min(inactiveMaxAvailable, mInactiveItemMaxWidth);
            int extra = width - activeWidth - inactiveWidth * inactiveCount;
            for (int i = 0; i < count; i++) {
                mTempChildWidths[i] = (i == mSelectedItemPosition) ? activeWidth : inactiveWidth;
                if (extra > 0) {
                    mTempChildWidths[i]++;
                    extra--;
                }
            }
        } else {
            final int maxAvailable = width / (count == 0 ? 1 : count);
            final int childWidth = Math.min(maxAvailable, mActiveItemMaxWidth);
            int extra = width - childWidth * count;
            for (int i = 0; i < count; i++) {
                mTempChildWidths[i] = childWidth;
                if (extra > 0) {
                    mTempChildWidths[i]++;
                    extra--;
                }
            }
        }

        int totalWidth = 0;
        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if (child.getVisibility() == GONE) {
                continue;
            }
            child.measure(MeasureSpec.makeMeasureSpec(mTempChildWidths[i], MeasureSpec.EXACTLY),
                    heightSpec);
            ViewGroup.LayoutParams params = child.getLayoutParams();
            params.width = child.getMeasuredWidth();
            totalWidth += child.getMeasuredWidth();
        }
        setMeasuredDimension(
                View.resolveSizeAndState(totalWidth,
                        MeasureSpec.makeMeasureSpec(totalWidth, MeasureSpec.EXACTLY), 0),
                View.resolveSizeAndState(mItemHeight, heightSpec, 0));
    }

看到一个判断mShifingMode,这个变量控制是否平分布局
Kotlin中使用BottomNavigationView实现底部导航

Kotlin中使用BottomNavigationView实现底部导航
这里看到menu.size有一个判断,大于3是true,不大于是false

所以当iteam是大于3的时候,获取到mShifingMode设置为false,就可以平分了

由于BottomNavigationView无法通过代码直接来setShiftingMode的属性值(boolean类型),所以我们创建一个NavigationViewHelper并创建一个方法


class BottomNavigationViewHelper {
    companion object {
        @SuppressLint("RestrictedApi")
        open fun disableShiftMode(view: BottomNavigationView) {
            //获取子View BottomNavigationMenuView的对象
            val menuView = view.getChildAt(0) as BottomNavigationMenuView
            try {
                //设置私有成员变量mShiftingMode可以修改
                val shiftingMode = menuView.javaClass.getDeclaredField("mShiftingMode")
                shiftingMode.isAccessible = true
                shiftingMode.setBoolean(menuView, false)
                shiftingMode.isAccessible = false
                for (i in 0 until menuView.childCount) {
                    val item = menuView.getChildAt(i) as BottomNavigationItemView
                    //去除shift效果
                    item.setShiftingMode(false)
                    item.setChecked(item.itemData.isChecked)
                }
            } catch (e: NoSuchFieldException) {
            } catch (e: IllegalAccessException) {
            }

        }
    }


}

在Activity中使用

    bottom_navigation.setOnNavigationItemSelectedListener(mBottomNavigationView)
    //解决3个条目问题
    BottomNavigationViewHelper.disableShiftMode(bottom_navigation)

Kotlin中使用BottomNavigationView实现底部导航
哼,这才是我想要的

5. BottomNavigationView+ViewPager

 private fun initView() {

        menuFragment = MenuFragment()
        moreFragment = MoreFragment()
        informationFragment = InformationFragment()
        meFragment = MeFragment()

        fragmentList.add(menuFragment!!)
        fragmentList.add(moreFragment!!)
        fragmentList.add(informationFragment!!)
        fragmentList.add(meFragment!!)


        normalAdapter = NormalAdapter(supportFragmentManager, fragmentList)

        vp_main.adapter = normalAdapter
        vp_main.offscreenPageLimit = fragmentList.size
   vp_main.addOnPageChangeListener(object : ViewPager.OnPageChangeListener {
            override fun onPageScrollStateChanged(state: Int) {

            }

            override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
            }

            override fun onPageSelected(position: Int) {
                if (menuItem != null) {
                    menuItem!!.isChecked = false
                } else {
                    bottom_navigation.getMenu().getItem(0).isChecked = false
                }
                menuItem = bottom_navigation.getMenu().getItem(position)
                menuItem!!.isChecked=true
            }

        })

    }


    internal inner class NormalAdapter(fm: FragmentManager, private val fragmentList: List<Fragment>) : FragmentPagerAdapter(fm) {

        override fun getItem(position: Int): Fragment {
            return fragmentList[position]
        }

        override fun getCount(): Int {
            return fragmentList.size
        }

    }

Kotlin中使用BottomNavigationView实现底部导航
注意这里的 Fragment 包

import android.support.v4.app.Fragment

可以看到既可以点击item切换页面,也可以滑动切换页面
有时候项目需求不能滑动切换页面,好,我们来做下

6. 禁止ViewPager滑动

public class NoScrollViewPager extends ViewPager {

    private boolean noScroll = true;

    public NoScrollViewPager(Context context, AttributeSet attrs) {
        super(context, attrs);
        // TODO Auto-generated constructor stub
    }

    public NoScrollViewPager(Context context) {
        super(context);
    }

    public void setNoScroll(boolean noScroll) {
        this.noScroll = noScroll;
    }

    @Override
    public void scrollTo(int x, int y) {
        super.scrollTo(x, y);
    }

    @Override
    public boolean onTouchEvent(MotionEvent arg0) {

        if (noScroll)
            return false;
        else
            return super.onTouchEvent(arg0);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent arg0) {
        if (noScroll)
            return false;
        else
            return super.onInterceptTouchEvent(arg0);
    }

    @Override
    public void setCurrentItem(int item, boolean smoothScroll) {
        super.setCurrentItem(item, smoothScroll);
    }

    @Override
    public void setCurrentItem(int item) {
        super.setCurrentItem(item, false);
    }

}

然后修改xml

  <com.zuoge.project.view.NoScrollViewPager
        android:id="@+id/vp_main"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

Kotlin中使用BottomNavigationView实现底部导航

这样就不能滑动切换了
在开发的时候,会经常遇到小红点的需求,像微信的聊天信息,新消息的通知
好,最后来实现下小红点

7. 添加小红点

BottomNavigationMenuView中的每一个Tab就是一个FrameLayout,所以我们可以在上面随意添加View、这样就可以实现角标了

     val menuView = bottom_navigation.getChildAt(0) as BottomNavigationMenuView

//这里就是获取所添加的每一个Tab(或者叫menu),
        val tab = menuView.getChildAt(3)
        val itemView = tab as BottomNavigationItemView

//加载角标View,新创建的一个布局
        val badge = LayoutInflater.from(this).inflate(R.layout.menu_badge, menuView, false)

//添加到Tab上
        itemView.addView(badge)
        val count = badge.findViewById(R.id.tv_msg_count) as TextView
        count.text = 2.toString()
         //如果没有消息,不需要显示的时候那只需要将它隐藏即可
        //count.visibility = View.GONE

menu_badge.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:id="@+id/tv_msg_count"
        android:layout_width="16dp"
        android:layout_height="16dp"
        android:layout_gravity="center"
        android:layout_marginLeft="10dp"
        android:layout_marginTop="3dp"
        android:background="@drawable/bg_red_circle"
        android:gravity="center"
        android:textColor="@color/tool_title"
        android:textSize="12sp"
         />

</LinearLayout>

Kotlin中使用BottomNavigationView实现底部导航

demo下载