BottomNavigationView 是 Material Design 提供的一个标准底部导航栏的实现,可以轻松的实现导航栏菜单之间的切换与浏览。底部导航使用户更方便的查看和切换最高层级的导航界面,适用于有三到五个 Tab 的情况。
APP 底部导航栏目中,有新消息提示用户,并在导航栏底部显示具体消息数,这种效果主流 APP 都有应用。本文就介绍 BottomNavigationView + ViewPager + Fragment + BadgeView 可以达到微信消息角标效果和 QQ 消息拖拽效果。
方法 | 介绍 |
---|---|
setSelectedItemId(int itemId) | 设置选择的菜单项 ID |
setElevation(float elevation) | 设置此视图的基本高程(以像素为单位) |
setItemBackground(Drawable background) | 将菜单项的背景设置为给定的可绘制对象 |
setItemBackgroundResource(int resId) | 将菜单项的背景设置为给定资源 |
setItemIconSize(int iconSize) | 设置大小以提供菜单项图标 |
setItemIconTintList(ColorStateList tint) | 设置应用于菜单项图标的色彩 |
setItemRippleColor(ColorStateList itemRippleColor) | 将菜单项的背景设置为具有给定颜色的波纹 |
setItemTextAppearanceActive(int textAppearanceRes) | 设置用于菜单项标签的文本样式 |
setItemTextAppearanceInactive(int textAppearanceRes) | 设置用于非活动菜单项标签的文本样式 |
setItemTextColor(ColorStateList textColor) | 设置颜色以用于菜单项文本的不同状态(正常,选中,聚焦等) |
setLabelVisibilityMode(int labelVisibilityMode) | 设置导航项目的标签可见性模式 |
setItemHorizontalTranslationEnabled(boolean itemHorizontalTranslationEnabled) | 设置当合并的项目宽度填满屏幕时,菜单项是否在选择时水平平移 |
setOnNavigationItemReselectedListener(BottomNavigationView.OnNavigationItemReselectedListener listener) | 设置一个侦听器,当重新选择当前选择的底部导航项时将通知该侦听器 |
getMenu() | 返回 Menu 与此底部导航栏关联的实例 |
getMaxItemCount() | 返回最大 Menu 数量 |
implementation 'com.android.support:design:29.0.0'
在 xml 布局文件中直接引入控件
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottomNavigationView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:menu="@menu/activity_bottom_nav" />
BottomNavigationView 需要添加一个 menu 布局,之前在 DarwerLayout+NavigationView 文章中讲过 menu 创建注意事项,底部导航栏使用至少需要 android:id、android:icon、android:title 三个属性,此处就不做具体讲解,直接附上代码:
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/menu_item_home"
android:icon="@drawable/ic_vector_bottom_home"
android:title="主页" />
<item
android:id="@+id/menu_item_project"
android:icon="@drawable/ic_vector_bottom_book"
android:title="项目" />
<item
android:id="@+id/menu_item_movie"
android:icon="@drawable/ic_vector_bottom_movie"
android:title="电影" />
menu>
经过以上三步,基本上就可以看到 BottomNavigationView 的展示效果,但是发现 3 个以上菜单时会出现文本显示异常现象,动画效果也变了。要想解决这个问题,在最新版的 API 中,已经为大家提供了解决方案,只需要一行代码就可以完美解决。
bottomNavigationView.setLabelVisibilityMode(LABEL_VISIBILITY_LABELED);
//或者在xml文件中使用属性
app:labelVisibilityMode="labeled"
因为本文是根据 androidx 讲解,如果需要旧版本兼容,使用以下方法:
public static void disableShiftMode(BottomNavigationView view) {
BottomNavigationMenuView menuView = (BottomNavigationMenuView) view.getChildAt(0);
try {
Field shiftingMode = menuView.getClass().getDeclaredField("mShiftingMode");
shiftingMode.setAccessible(true);
shiftingMode.setBoolean(menuView, false);
shiftingMode.setAccessible(false);
for (int i = 0; i < menuView.getChildCount(); i++) {
BottomNavigationItemView item = (BottomNavigationItemView) menuView.getChildAt(i);
//去除动画
item.setShiftingMode(false); //api 28之前
item.setChecked(item.getItemData().isChecked());
}
} catch (NoSuchFieldException e) {
LogUtils.e( "Unable to get shift mode field");
} catch (IllegalAccessException e) {
LogUtils.e( "Unable to change value of shift mode");
}
}
1、在 color 文件夹下新建 selector_nav_text_item.xml 文件
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="@color/white" android:state_checked="true" />
<item android:color="@color/white" android:state_pressed="true" />
<item android:color="@color/md_grey_700"/>
selector>
2、设置颜色属性
app:itemIconTint="@color/selector_nav_text_item"
app:itemTextColor="@color/selector_nav_text_item"
// index表示tab索引
bottomNavigationView.getMenu().getItem(index).setChecked(true);
BottomNavigationView 设置监听器,可以根据 Item 的 id 判断点击选中项,这里可以做很多事情,比如 ViewPager 和 Fragment 切换,设置 Item 样式等等。
bottomNavigationView.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem menuItem) {
switch (menuItem.getItemId()) {
case R.id.menu_item_home:
Toast.makeText(BottomNavigationViewActivity.this, "主页", Toast.LENGTH_SHORT).show();
bottomNavigationView.setBackgroundColor(getResources().getColor(R.color.light_yellow));
break;
case R.id.menu_item_project:
Toast.makeText(BottomNavigationViewActivity.this, "项目", Toast.LENGTH_SHORT).show();
bottomNavigationView.setBackgroundColor(getResources().getColor(R.color.brown));
break;
case R.id.menu_item_movie:
Toast.makeText(BottomNavigationViewActivity.this, "电影", Toast.LENGTH_SHORT).show();
bottomNavigationView.setBackgroundColor(getResources().getColor(R.color.txt_link_blue));
break;
case R.id.menu_item_book:
Toast.makeText(BottomNavigationViewActivity.this, "干货", Toast.LENGTH_SHORT).show();
bottomNavigationView.setBackgroundColor(getResources().getColor(R.color.md_lime_700));
break;
case R.id.menu_item_personal:
Toast.makeText(BottomNavigationViewActivity.this, "个人", Toast.LENGTH_SHORT).show();
bottomNavigationView.setBackgroundColor(getResources().getColor(R.color.md_yellow_500));
break;
}
return true;
}
});
BottomNavigationView 往往配合 ViewPager 和 Fragment 一起使用,在 APP 开发中,这是最基本的框架结构。之前文章中讲解过 TabLayout+ViewPager+Fragmemt 的使用,所以本文不再对 ViewPager+Fragment 重复讲解。感兴趣的可以查看 Android Material Design 系列之 TabLayout + ViewPager + Fragment 使用详解
监听 ViewPager 滑动事件,在onPageSelected(int position)
方法中返回当前页面 position,然后通过上面讲到的
bottomNavigationView.getMenu().getItem(position).setChecked(true);
方法设置当前选中项。
viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageSelected(int position) {
bottomNavigationView.getMenu().getItem(position).setChecked(true);
// TODO:更新ToolBar标题栏
setToolBarTitle(position);
}
@Override
public void onPageScrollStateChanged(int state) {
}
});
同理,监听 BottomNavigationView 选中接口,在回调方法onNavigationItemSelected(MenuItem menuItem)
中,根据 ItemID 更新 ViewPager 选项卡,通过调用viewPager.setCurrentItem()
方法实现;
bottomNavigationView.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem menuItem) {
switch (menuItem.getItemId()) {
case R.id.menu_item_home:
Toast.makeText(BottomNavigationViewActivity.this, "主页", Toast.LENGTH_SHORT).show();
viewPager.setCurrentItem(0, false);
break;
case R.id.menu_item_project:
Toast.makeText(BottomNavigationViewActivity.this, "项目", Toast.LENGTH_SHORT).show();
viewPager.setCurrentItem(1, false);
break;
case R.id.menu_item_movie:
Toast.makeText(BottomNavigationViewActivity.this, "电影", Toast.LENGTH_SHORT).show();
viewPager.setCurrentItem(2, false);
break;
case R.id.menu_item_book:
Toast.makeText(BottomNavigationViewActivity.this, "干货", Toast.LENGTH_SHORT).show();
viewPager.setCurrentItem(3, false);
break;
case R.id.menu_item_personal:
Toast.makeText(BottomNavigationViewActivity.this, "个人", Toast.LENGTH_SHORT).show();
viewPager.setCurrentItem(4, false);
break;
}
return true;
}
});
通常情况下,APP 每个界面的标题栏均不相同,为了方便将所有 ToolBar 放在 MainActivity 中管理,根据 ViewPager 监听回调方法中动态设置 Toolbar 的标题栏,调用代码在ViewPager 滑动同步 BottomNavigationView目录中已经贴出,这里附加更新 ToolBar 方法。
/**
* 更新Toolbar标题
* @param currentTitleIndex
*/
private void setToolBarTitle(int currentTitleIndex) {
toolbar.setTitle(bottomNavigationView.getMenu().getItem(currentTitleIndex).getTitle());
}
一个可以自由定制外观、支持拖拽消除的 MaterialDesign 风格 Android BadgeView
随意定制外观,包括 Badge 位置、底色、边框、阴影、文字颜色(支持透明色)、大小、内外边距等
Badge 数字小于 0 时显示 dot,等于 0 时隐藏整个 Badge,在普通模式下超过 99 时显示 99+,精确模式下显示具体值
支持设置文本内容
支持设置图片背景
支持类似 QQ 的拖拽消除效果(默认关闭)
支持以动画的方式隐藏 Badge
方法 | 介绍 |
---|---|
getBadgeNumber | 设置 Badge 数字 |
setBadgeText | 设置 Badge 文本 |
setBadgeTextSize | 设置 Badge 文本字体大小 |
setBadgeTextColor | 设置 Badge 文本字体颜色 |
setBadgeGravity | 设置 Badge 在 TargetView 的位置 |
setExactMode | 设置是否显示精确数字模式 |
setGravityOffset | 设置外边距 |
setBadgePadding | 设置内边距 |
setBadgeBackgroundColor | 设置背景颜色 |
setBadgeBackground | 设置背景图片 |
setShowShadow | 设置是否显示阴影效果 |
stroke | 描边 |
hide | 隐藏 Badge |
setOnDragStateChangedListener | 开启拖拽消除并监听 |
implementation 'q.rorbin:badgeview:1.1.3'
Badge 需要绑定一个 View,所有方法和操作都是以这个 View 为中心的。因为本文讲的是 BottomNavigationView,所以就以 BottomNavigationView 的 MenuItemView 为中心。
查看源码可以看到 BottomNavigationMenuView 是 BottomNavigationView 内部添加的一个子 View,也就是说他是导航栏中添加的所有 Menu 的一个父 View,那么获取 BottomNavigationMenuView 以及子 Menu 就很简单了。
BottomNavigationMenuView itemView = (BottomNavigationMenuView) bottomNav.getChildAt(0);
// 获取导航栏Tab数量
int childCount = itemView.getChildCount();
Badge 是一个接口,创建实现类 QBadgeView 对象,然后设置相关属性。
QBadgeView qBadgeView=new QBadgeView(context);
BadgeView 设置绑定 View、数字、位置
new QBadgeView(getActivity()).bindTarget(itemView.getChildAt(0))
.setBadgeNumber(6)
.setBadgeGravity(Gravity.TOP | Gravity.END);
new QBadgeView(getActivity()).bindTarget(itemView.getChildAt(1))
.setBadgeNumber(27)
.setBadgeGravity(Gravity.TOP | Gravity.END)
.setBadgeTextColor(Color.YELLOW)
.setGravityOffset(10, 0, true);
setExactMode(boolean isExact)
方法设置为 false,当消息数>99,则显示 99+,若设置为 true,当消息数>99,则显示具体的消息数。
// 显示 99+
new QBadgeView(getActivity()).bindTarget(itemView.getChildAt(2))
.setBadgeNumber(999)
.setBadgeGravity(Gravity.TOP | Gravity.END)
.setExactMode(false);
// 显示 1000
new QBadgeView(getActivity()).bindTarget(itemView.getChildAt(3))
.setBadgeNumber(1000)
.setBadgeGravity(Gravity.TOP | Gravity.END)
.setExactMode(true);
setShowShadow(boolean showShadow)
方法设置为 true 时表示有阴影效果,为 false 时取消阴影效果。
new QBadgeView(getActivity()).bindTarget(itemView.getChildAt(4))
.setBadgeNumber(9)
.setBadgeGravity(Gravity.TOP | Gravity.END)
.setBadgeBackgroundColor(Color.BLUE)
.setShowShadow(false);
BadgeView 添加setOnDragStateChangedListener
监听,即可实现仿 QQ 拖拽效果,本文在 BottomNavigationView 导航栏 Item 上使用,也可以在 RecyclerView 的 Item 上实现,使用极其简单。
回调函数onDragStateChanged
其中 dragState 状态有五种:
new QBadgeView(getActivity()).bindTarget(itemView.getChildAt(4))
.setBadgeNumber(9)
.setBadgeGravity(Gravity.TOP | Gravity.END)
.setOnDragStateChangedListener(new Badge.OnDragStateChangedListener() {
@Override
public void onDragStateChanged(int dragState, Badge badge, View targetView) {
}
});
以上只是挑选出来几个比较常用的属性做详细讲解,如果对其他属性感兴趣的可以自己写个案例尝试一下。
源码下载 源码包含 Material Design 系列控件集合,定时更新,敬请期待!
最开始我们实现 APP 首页框架的时候,大多数人应该都沉浸在 Tablayout + viewpager + Fragment 的世界,我相信还有部分人在使用 RadioGroup 和 RadioButton 实现。既然 Google 已经为开发者提供了 BottomNavigationView 控件,还在使用第三方控件以及原始模式的朋友们,可以放心的迁移过来了,Google 出品,必为精品,配合 BadgeView 完美实现底部导航栏。
我的微信:Jaynm888
欢迎点评,诚邀 Android 程序员加入微信交流群
,公众号回复加群或者加我微信邀请入群。