炫酷的底部菜单栏BottomBar

Posted ASleepyCoder

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了炫酷的底部菜单栏BottomBar相关的知识,希望对你有一定的参考价值。

开源项目分析BottomBar

今天分析一个炫酷的底部菜单栏开源项目,先说明下用法,再分析一下源码的实现。
GitHub地址
https://github.com/roughike/BottomBar
先上个效果图

使用

添加依赖

compile 'com.roughike:bottom-bar:1.3.3'

创建menu资源文件

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>

关联Activity

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);
    

BottomBar其他功能设置

  • 不关联 xml 文件代码创建底部导航选项卡
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这个类中

BottomBar.java

按照使用一步步跟进代码

  • attach(Activity activity, Bundle savedInstanceState)
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的时候调用。

  • setItemsFromMenu
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。

  • handleClick(View v)
    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下载地址

这个库代码不是很复杂但思想很值得借鉴,效果也是非常的赞,以后会多找这样效果很赞又不会很复杂的第三方库与大家分享,我也会第一时间更新到我的微信订阅号,欢迎大家关注!

以上是关于炫酷的底部菜单栏BottomBar的主要内容,如果未能解决你的问题,请参考以下文章

BottomBar之Android底部菜单

如何在底部栏上方显示 Snackbar?

一个炫酷的导航菜单,模仿别人写的

安卓炫酷效果

安卓简单的底部导航栏{特别简单的框架}

使用 BottomBar 和片段容器禁用 Android 片段重新加载