首页 > 技术文章 > Android - Bottom Navigation View

slyfox 2018-01-23 10:47 原文

Android - Bottom Navigation View

Overview

一直以来,关于Android的底部导航的功能实现的方法一直是各行其道不成规范,使用各种方法的都有

  • RadioButton
  • TextView
  • ...

在Material Design 中推出了这样的一个控件来解决底部导航栏的不统一的问题,但是这个控件有一点点的问题...

问题所在

现在的效果非常棒...

结果一旦Item的数量超过了3个,就会有一个非常鬼畜的动画效果,让人无法接受。

如何使用

在menu目录下新建一个menu菜单,这个菜单将会用于生成BottomNavigationView控件的item

<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:id="@+id/navItem_UI"
        android:icon="@drawable/ui"
        android:title="UI" />
    <item
        android:id="@+id/navItem_data"
        android:icon="@drawable/data"
        android:title="Data" />
    <item
        android:id="@+id/navItem_API"
        android:icon="@drawable/api"
        android:title="API" />
</menu>

xml 布局文件

<android.support.design.widget.BottomNavigationView
    android:id="@+id/nav"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="#f6f6f6"
    app:itemTextColor="@color/colorPrimary"
    app:itemIconTint="@color/colorPrimary"
    app:menu="@menu/nav_menu"/>

主要属性

  • itemTextColor 字体的颜色
  • itemIconTint 图标的颜色
  • menu 绑定的菜单

处理事件

void registerEvent() {
    //设置Item的点击事件
    navView.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
        @Override
        public boolean onNavigationItemSelected(@NonNull MenuItem item) {
            CommonUtil.toast(item.getTitle().toString());
            return true;
        }
    });
}

干掉那个浮夸的动画

效果图

一直以为Google会提供相应的方法,但是找了半天都没找到,但是我们有源码啊,只要溜进去控件的内部,看一看是怎么回事了。

/**
在源码中,返现,这个控件是基于MVP架构的,去MVP架构的View中找
*/

private static final int[] CHECKED_STATE_SET = {android.R.attr.state_checked};
private static final int[] DISABLED_STATE_SET = {-android.R.attr.state_enabled};

private static final int MENU_PRESENTER_ID = 1;

private final MenuBuilder mMenu;

//这个字段是MVP架构中的View,点进去他的源码中寻找
private final BottomNavigationMenuView mMenuView;
private final BottomNavigationPresenter mPresenter = new BottomNavigationPresenter();
private MenuInflater mMenuInflater;

private OnNavigationItemSelectedListener mSelectedListener;
private OnNavigationItemReselectedListener mReselectedListener;

进入BottomNavigationView中继续寻找

@RestrictTo(LIBRARY_GROUP)
public class BottomNavigationMenuView extends ViewGroup implements MenuView {
    private static final long ACTIVE_ANIMATION_DURATION_MS = 115L;

    private final TransitionSet mSet;
    private final int mInactiveItemMaxWidth;
    private final int mInactiveItemMinWidth;
    private final int mActiveItemMaxWidth;
    private final int mItemHeight;
    private final OnClickListener mOnClickListener;
    private final Pools.Pool<BottomNavigationItemView> mItemPool = new Pools.SynchronizedPool<>(5);
	//找到了一个比较可疑的字段, shifting 有道翻译一下-> 位移 关闭动画应该就是这个了
  	//这个字段是关闭的控件的位移动画
    private boolean mShiftingMode = true;
	//这个是BottomNavigationMenuView 中的各个item,进入继续找
    private BottomNavigationItemView[] mButtons;
    private int mSelectedItemId = 0;
    private int mSelectedItemPosition = 0;
    private ColorStateList mItemIconTint;
    private ColorStateList mItemTextColor;
    private int mItemBackgroundRes;
    private int[] mTempChildWidths;

    private BottomNavigationPresenter mPresenter;
    private MenuBuilder mMenu;

进入到BottomNavigationMenuView 中继续寻找

@RestrictTo(LIBRARY_GROUP)
public class BottomNavigationItemView extends FrameLayout implements MenuView.ItemView {
    public static final int INVALID_ITEM_POSITION = -1;

    private static final int[] CHECKED_STATE_SET = { android.R.attr.state_checked };

    private final int mDefaultMargin;
    private final int mShiftAmount;
    private final float mScaleUpFactor;
    private final float mScaleDownFactor;
	//这里还有一个动画效果的设置, 不过这个有方法来供我们设置,不同通过反射来设置了
  	//这个字段标识的是,图标上移的动画,如果不关闭这个动画,那么如果ItemNavigationView的Item超过了3个那么
  	//只有选中了的Item才会显示文字,而其他的不显示文字
    private boolean mShiftingMode;

    private ImageView mIcon;
    private final TextView mSmallLabel;
    private final TextView mLargeLabel;
    private int mItemPosition = INVALID_ITEM_POSITION;

    private MenuItemImpl mItemData;

    private ColorStateList mIconTint;
找到了标志属性,那就该我们的反射出场了了
void initNav() {
    try {
      	//在这里为什么使用getChildAt(0),的线索可以在BottomNavigationMenuView的构造方法中找到线索
        BottomNavigationMenuView menuView = (BottomNavigationMenuView) navView.getChildAt(0);
        Field field = menuView.getClass().getDeclaredField("mShiftingMode");
      	//取消位移动画
        field.setAccessible(true);
        field.setBoolean(menuView, false);
        //遍历所有的Item取消上移动画
        for (int i = 0; i < navView.getChildCount(); i++) {
          	//这里为什么调用的是getChildAt(i); 可以在BottomNavigationMenuView的buildMenuView方法中找到线索
            BottomNavigationItemView itemView = (BottomNavigationItemView) menuView.getChildAt(i);
            itemView.setShiftingMode(false);
        }
      	////更新一下MenuView,如果不加这句代码,初始化的是时候会一个显示样式的小bug出现
      	menuView.updateMenuView();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

在初始化控件后调用此方法就可以达到我们想要的效果

推荐阅读