前言
BottomNavigationView 是 Material Design 提供的一个标准底部导航栏的实现,可以轻松的实现导航栏菜单之间的切换与浏览。底部导航使用户更方便的查看和切换最高层级的导航界面,适用于有三到五个 Tab 的情况。
APP 底部导航栏目中,有新消息提示用户,并在导航栏底部显示具体消息数,这种效果主流 APP 都有应用。本文就介绍 BottomNavigationView + ViewPager + Fragment + BadgeView 可以达到微信消息角标效果和 QQ 消息拖拽效果。
一、BottomNavigationView 相关属性
方法 | 介绍 |
---|---|
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 数量 |
二、BottomNavigationView 基础使用
1、依赖引入
implementation 'com.android.support:design:29.0.0'
2、XML 布局文件
在 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" />
3、menu 创建
BottomNavigationView 需要添加一个 menu 布局,之前在 DarwerLayout+NavigationView 文章中讲过 menu 创建注意事项,底部导航栏使用至少需要 android:id、android:icon、android:title 三个属性,此处就不做具体讲解,直接附上代码:
<?xml version="1.0" encoding="utf-8"?>
<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>
4、三个以上 Tab 文本与动画效果
经过以上三步,基本上就可以看到 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");
}
}
5、设置选中菜单颜色
1、在 color 文件夹下新建 selector_nav_text_item.xml 文件
<?xml version="1.0" encoding="utf-8"?>
<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"
6、设置 Item 选中
// index表示tab索引
bottomNavigationView.getMenu().getItem(index).setChecked(true);
7、设置选中监听器
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 详解
BottomNavigationView 往往配合 ViewPager 和 Fragment 一起使用,在 APP 开发中,这是最基本的框架结构。之前文章中讲解过 TabLayout+ViewPager+Fragmemt 的使用,所以本文不再对 ViewPager+Fragment 重复讲解。感兴趣的可以查看 Android Material Design 系列之 TabLayout + ViewPager + Fragment 使用详解
1、ViewPager 滑动同步 BottomNavigationView
监听 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) {
}
});
2、BottomNavigationView 切换同步 ViewPager
同理,监听 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;
}
});
3、设置 Toolbar 标题栏
通常情况下,APP 每个界面的标题栏均不相同,为了方便将所有 ToolBar 放在 MainActivity 中管理,根据 ViewPager 监听回调方法中动态设置 Toolbar 的标题栏,调用代码在ViewPager 滑动同步 BottomNavigationView目录中已经贴出,这里附加更新 ToolBar 方法。
private void setToolBarTitle(int currentTitleIndex) {
toolbar.setTitle(bottomNavigationView.getMenu().getItem(currentTitleIndex).getTitle());
}
四、BadgeView 介绍
1、BadgeView 定义
一个可以自由定制外观、支持拖拽消除的 MaterialDesign 风格 Android BadgeView
2、BadgeView 特性
-
随意定制外观,包括 Badge 位置、底色、边框、阴影、文字颜色(支持透明色)、大小、内外边距等
-
Badge 数字小于 0 时显示 dot,等于 0 时隐藏整个 Badge,在普通模式下超过 99 时显示 99+,精确模式下显示具体值
-
支持设置文本内容
-
支持设置图片背景
-
支持类似 QQ 的拖拽消除效果(默认关闭)
-
支持以动画的方式隐藏 Badge
五、BadgeView 方法说明
方法 | 介绍 |
---|---|
getBadgeNumber | 设置 Badge 数字 |
setBadgeText | 设置 Badge 文本 |
setBadgeTextSize | 设置 Badge 文本字体大小 |
setBadgeTextColor | 设置 Badge 文本字体颜色 |
setBadgeGravity | 设置 Badge 在 TargetView 的位置 |
setExactMode | 设置是否显示精确数字模式 |
setGravityOffset | 设置外边距 |
setBadgePadding | 设置内边距 |
setBadgeBackgroundColor | 设置背景颜色 |
setBadgeBackground | 设置背景图片 |
setShowShadow | 设置是否显示阴影效果 |
stroke | 描边 |
hide | 隐藏 Badge |
setOnDragStateChangedListener | 开启拖拽消除并监听 |
六、BottomNavigationView + BadgeView 使用详解
1、依赖包引入
implementation 'q.rorbin:badgeview:1.1.3'
2、获取 BottomNavigationMenuView
Badge 需要绑定一个 View,所有方法和操作都是以这个 View 为中心的。因为本文讲的是 BottomNavigationView,所以就以 BottomNavigationView 的 MenuItemView 为中心。
查看源码可以看到 BottomNavigationMenuView 是 BottomNavigationView 内部添加的一个子 View,也就是说他是导航栏中添加的所有 Menu 的一个父 View,那么获取 BottomNavigationMenuView 以及子 Menu 就很简单了。
BottomNavigationMenuView itemView = (BottomNavigationMenuView) bottomNav.getChildAt(0);
// 获取导航栏Tab数量
int childCount = itemView.getChildCount();
3、BadgeView 初始化
Badge 是一个接口,创建实现类 QBadgeView 对象,然后设置相关属性。
QBadgeView qBadgeView=new QBadgeView(context);
4、BadgeView 基础用法
BadgeView 设置绑定 View、数字、位置
new QBadgeView(getActivity()).bindTarget(itemView.getChildAt(0))
.setBadgeNumber(6)
.setBadgeGravity(Gravity.TOP | Gravity.END);
5、修改文本颜色
new QBadgeView(getActivity()).bindTarget(itemView.getChildAt(1))
.setBadgeNumber(27)
.setBadgeGravity(Gravity.TOP | Gravity.END)
.setBadgeTextColor(Color.YELLOW)
.setGravityOffset(10, 0, true);
6、设置是否为精确模式数
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);
7、BadgeView 阴影效果
setShowShadow(boolean showShadow)
方法设置为 true 时表示有阴影效果,为 false 时取消阴影效果。
new QBadgeView(getActivity()).bindTarget(itemView.getChildAt(4))
.setBadgeNumber(9)
.setBadgeGravity(Gravity.TOP | Gravity.END)
.setBadgeBackgroundColor(Color.BLUE)
.setShowShadow(false);
8、BadgeView 拖拽效果
BadgeView 添加setOnDragStateChangedListener
监听,即可实现仿 QQ 拖拽效果,本文在 BottomNavigationView 导航栏 Item 上使用,也可以在 RecyclerView 的 Item 上实现,使用极其简单。
回调函数onDragStateChanged
其中 dragState 状态有五种:
- int STATE_START = 1;
- int STATE_DRAGGING = 2;
- int STATE_DRAGGING_OUT_OF_RANGE = 3;
- int STATE_CANCELED = 4;
- int STATE_SUCCEED = 5;
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) {
}
});
以上只是挑选出来几个比较常用的属性做详细讲解,如果对其他属性感兴趣的可以自己写个案例尝试一下。
七、BadgeView 使用注意事项
- 请不要在 xml 中创建 Badge
- Badge 和 TargetView 绑定是采用替换 TargetView 的 Parent 方式实现的,同时将 Parent 的 Id 和 TargetView 的 Id 设置成一样来保证不会在 RelativeLayout 中出现位置错乱问题,所以在 bindTarget 后再次使用 findViewById(TargetViewId)得到的会是 Parent 而不是 TargetView,此时建议使用 Badge.getTargetView 方法来获取 TargetView。
源码下载 源码包含 Material Design 系列控件集合,定时更新,敬请期待!
八、总结
最开始我们实现 APP 首页框架的时候,大多数人应该都沉浸在 Tablayout + viewpager + Fragment 的世界,我相信还有部分人在使用 RadioGroup 和 RadioButton 实现。既然 Google 已经为开发者提供了 BottomNavigationView 控件,还在使用第三方控件以及原始模式的朋友们,可以放心的迁移过来了,Google 出品,必为精品,配合 BadgeView 完美实现底部导航栏。
我的微信:Jaynm888
欢迎点评,诚邀 Android 程序员加入微信交流群
,公众号回复加群或者加我微信邀请入群。