Kotlin中使用BottomNavigationView实现底部导航
Aandroid Design Support Library中增加了BottonNavigationView控件,实现底部导航切换页面方便了许多,同时它也有不便之处:
1. 底部的条目数超过三个,点击每个条目是会有很大的偏移量
2. 无法添加小红点提示
以前为了实现底部导航切换页面,我通常用以下两种方式
1. TabLayout+ViewPager+Fragment 方式实现
2. RadioGroup+ViewPager+Fragment 方式实现
最终的效果图
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
}
啊,不是我想要的效果,我想要的是4个item平分布局
4. 查看源码解决平分布局
进入到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,这个变量控制是否平分布局
这里看到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)
哼,这才是我想要的
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
}
}
注意这里的 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"/>
这样就不能滑动切换了
在开发的时候,会经常遇到小红点的需求,像微信的聊天信息,新消息的通知
好,最后来实现下小红点
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>
上一篇: Ruby异常处理的基础
下一篇: 软件测试-测试基础知识篇
推荐阅读
-
android中Fragment+RadioButton实现底部导航栏
-
Android 底部导航栏以及导航栏角标显示,使用LinearLayout+Fragment实现
-
Android日常开发(36)BottomNavigationView 实现底部导航
-
Android使用FragmentTabHost实现底部导航栏切换卡
-
Android使用BottomNavigationBar实现底部导航栏
-
在安卓开发中,使用腾讯地图实现定位与导航功能
-
Kotlin用BottomNavigation实现底部导航栏
-
Kotlin中使用BottomNavigationView实现底部导航
-
Android kotlin实现底部导航栏
-
Android开发中如何使用BottomTabBar实现底部导航页