Kotlin中使用BottomNavigationView实现底部导航

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

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

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

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

最终的效果图

Kotlin中使用BottomNavigationView实现底部导航_第1张图片

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等属性

<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

                return@OnNavigationItemSelectedListener true
            }
            R.id.more -> {

                vp_main.currentItem = 1


                return@OnNavigationItemSelectedListener true
            }
            R.id.me -> {

                vp_main.currentItem = 2


                return@OnNavigationItemSelectedListener true
            }
            R.id.me2 -> {

                vp_main.currentItem = 3


                return@OnNavigationItemSelectedListener true
            }

        }
        false

    }

Kotlin中使用BottomNavigationView实现底部导航_第2张图片

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

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

Kotlin中使用BottomNavigationView实现底部导航_第3张图片
进入到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实现底部导航_第4张图片
这里看到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张图片
哼,这才是我想要的

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实现底部导航_第6张图片
注意这里的 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张图片

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

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


<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实现底部导航_第8张图片

demo下载

你可能感兴趣的:(Android)