今天分析一个炫酷的底部菜单栏开源项目,先说明下用法,再分析一下源码的实现。
GitHub地址
https://github.com/roughike/BottomBar
先上个效果图
compile 'com.roughike:bottom-bar:1.3.3'
res/menu/bottombar_menu.xml:
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/bottomBarItemOne"
android:icon="@drawable/ic_recents"
android:title="Recents" />
...
</menu>
public class MainActivity extends AppCompatActivity {
private BottomBar mBottomBar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//关联到当前 activity
mBottomBar = BottomBar.attach(this, savedInstanceState);
//从 menu.xml初始化导航栏 并设置监听
mBottomBar.setItemsFromMenu(R.menu.bottombar_menu, new OnMenuTabClickListener() {
@Override
public void onMenuTabSelected(@IdRes int menuItemId) {
if (resId == R.id.bottomBarItemOne) {
//当tab被选中时候触发
}
}
@Override
public void onMenuTabReSelected(@IdRes int menuItemId) {
if (resId == R.id.bottomBarItemOne) {
//当前选中的 tab 再次被点击时候触发
}
}
});
//设置导航栏选中时候的颜色 导航>=3的时候有效
mBottomBar.mapColorForTab(0, ContextCompat.getColor(this, R.color.colorAccent));
mBottomBar.mapColorForTab(1, 0xFF5D4037);
mBottomBar.mapColorForTab(2, "#7B1FA2");
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
//保存底部导航状态 这一步是必须的
mBottomBar.onSaveInstanceState(outState);
}
}
mBottomBar.setItems(
new BottomBarTab(R.drawable.ic_recents, "Recents"),
new BottomBarTab(R.drawable.ic_favorites, "Favorites"),
new BottomBarTab(R.drawable.ic_nearby, "Nearby")
);
mBottomBar.setOnTabClickListener(linstener);
//为第一个tab创建一个背景红色内容为13的右上角提醒
BottomBarBadge unreadMessages = mBottomBar.makeBadgeForTabAt(0, "#FF0000", 13);
// 控制提醒显示和隐藏
unreadMessages.show();
unreadMessages.hide();
// 更改提醒数字
unreadMessages.setCount(4);
// 显示和隐藏的动画持续的时间
unreadMessages.setAnimationDuration(200);
//tab 未选中时也显示
unreadMessages.setAutoShowAfterUnSelection(true);
// 设置平板显示效果和手机显示效果一致
mBottomBar.noTabletGoodness();
// 当超过3个 tab 的时候设置所有 tab 都固定显示
mBottomBar.useFixedMode();
// 暗色主题
mBottomBar.useDarkTheme();
// 设置 tab使用自定义的 textStyle
mBottomBar.setTextAppearance(R.style.MyTextAppearance);
//TAB使用自定义字体 字体需放在 assets 文件夹下
mBottomBar.setTypeFace("MyFont.ttf");
还有很多其他设置就不一一介绍了,有兴趣的请移驾 ==GitHub项目地址==https://github.com/roughike/BottomBar
要分析其原理我们先看下
scrollsweetness下是自定义的CoordinatorLayout.Behavior
BottomNavigationBehavior继承自VerticalScrollingBehavior 用于实现随页面的滚动隐藏与显示 BottomBar ,至于实现原理关于CoordinatorLayout.Behavior,展开来讲的话又是很大一篇,限于本人能力所限这里就不做深入了。
BadgeCircle
这个比较简单 就一个方法用来生成未读提醒提示的的背景
BottomBarBadge
继承自 TextView 是未读提醒显示所用的的自定义 view,设置显示隐藏的动画
BottomBarTab
BottomBarTab继承自BottomBarItemBase 是存储单个tab属性的实体类
其他类
MiscUtils是处理动画、颜色、宽高之类的工具类
剩下的都是一些回掉监听的接口
资源文件
layout 目录下的 xml 文件分别对应着不同状态的下的布局文件
由项目结构可知大部分内容都在BottomBar这个类中
按照使用一步步跟进代码
public static BottomBar attach(Activity activity, Bundle savedInstanceState) {
BottomBar bottomBar = new BottomBar(activity);
bottomBar.onRestoreInstanceState(savedInstanceState);
ViewGroup contentView = (ViewGroup) activity.findViewById(android.R.id.content);
View oldLayout = contentView.getChildAt(0);
contentView.removeView(oldLayout);
bottomBar.setPendingUserContentView(oldLayout);
contentView.addView(bottomBar, 0);
return bottomBar;
}
这个方法其实就做了两件事
第一 实例化bottomBar对象,恢复以前保存的状态
第二 用自定义布局替换掉原本的 contentView 内的内容
activity.setContentView其实就是向系统顶层布局里id为android.R.id.content的Framelayout内添加指定的 view,这里作者做的是将原本用户添加的 oldLayout 从 contentView 内移除,再把这个oldLayout添加到bottomBar的布局中,最后再将bottomBar添加到 contentView 中。
需要注意的是这里是先把oldLayout保存下来
private void setPendingUserContentView(View oldLayout) {
mPendingUserContentView = oldLayout;
}
在initializeViews()中mPendingUserContentView才会真正被添加到bottomBar的布局中,而initializeViews会在updateItems方法内调用,updateItems则会在绑定menu.xml或添加tab的时候调用。
public void setItemsFromMenu(@MenuRes int menuRes, OnMenuTabClickListener listener) {
clearItems();
mItems = MiscUtils.inflateMenuFromResource((Activity) getContext(), menuRes);
mMenuListener = listener;
updateItems(mItems);
if (mItems != null && mItems.length > 0
&& mItems instanceof BottomBarTab[]) {
listener.onMenuTabSelected(((BottomBarTab) mItems[mCurrentTabPosition]).id);
}
}
这个方法的功能也很明确
首先 根据 menu 资源获得 tab 对象列表mItems
然后 绑定监听mMenuListener
最后 设置当前选中的 tab
这个方法中updateItems会被调用 如果mPendingUserContentView未被添加过则会添加到布局中
下面在看看点击事件的执行,由 updateItems 方法可知每个 tab 的 onClick 事件绑定的 listener 即为 BottomBar.this,所以找到 onClick 事件即可,onClick内只是调用了handleClick。
private void handleClick(View v) {
if (v.getTag().equals(TAG_BOTTOM_BAR_VIEW_INACTIVE)) {
View oldTab = findViewWithTag(TAG_BOTTOM_BAR_VIEW_ACTIVE);
unselectTab(oldTab, true);
selectTab(v, true);
shiftingMagic(oldTab, v, true);
}
updateSelectedTab(findItemPosition(v));
}
此方法内部的执行也是非常明确,
第一 根据 tag 找到点击事件前选中的 tab,更新 oldtab 和 newtab 的状态
第二 shiftingMagic 更新tab宽度并执行动画效果
第三 updateSelectedTab 更新未读消息提醒的显示并执行用户绑定的监听
至于其他的有兴趣的请自行下载源码分析。
GitHub下载有困难的同学看这里
BottomBar下载地址
这个库代码不是很复杂但思想很值得借鉴,效果也是非常的赞,以后会多找这样效果很赞又不会很复杂的第三方库与大家分享,我也会第一时间更新到我的微信订阅号,欢迎大家关注!