BottomNavigationView最新用法

BottomNavigationView最新用法

  • Android中使用BottomNavigationView早已不稀奇,之前其他博客也都介绍的有用法。
  • 但是,现在网上所出现的使用方法已不符合当今需要

    • 方法一 (com.android.support:design 版本不高于 27.0.3) 网上一堆
    • 方法二 (com.android.support:design 版本高于 27.0.3) 网上没有任何说明
  • 故,在此特别介绍一下方法二 以供参考

方法一:

  • 此方法仅针对 Gradle 文件引用 com.android.support:design 版本不高于 27.0.3

    • ⓵ 对于 Item>3 时的平移及缩放动画的处理
    • ⓶ 对于 Item 缩放动画(选中放大图标/文字)
  • 对于 Item>3 时的平移及缩放动画的处理

    @SuppressLint("RestrictedApi")
    public static void disableShiftMode(BottomNavigationView navigationView) {
    
        try {
            BottomNavigationMenuView menuView = (BottomNavigationMenuView) navigationView.getChildAt(0);
            Field mShiftingMode  = menuView.getClass().getDeclaredField("mShiftingMode");
            mShiftingMode.setAccessible(true);
            mShiftingMode.setBoolean(menuView, false);
            mShiftingMode.setAccessible(false);
    
            for (int i = 0; i < menuView.getChildCount(); i++) {
                BottomNavigationItemView itemView = (BottomNavigationItemView) menuView.getChildAt(i);
                itemView.setShifting(false);
                itemView.setEnabled(true);
                itemView.setChecked(itemView.getItemData().isChecked());
            }
    
        } catch (NoSuchFieldException e) {
            Log.e("BNVHelper", "Unable to get shift mode field", e);
        } catch (IllegalAccessException e) {
            Log.e("BNVHelper", "Unable to change value of shift mode", e);
        }
    }
    
  • 对于 Item 缩放动画(选中放大图标/文字)

    @SuppressLint("RestrictedApi")
    public static void disableItemScale(BottomNavigationView view) {
        try {
            BottomNavigationMenuView menuView = (BottomNavigationMenuView) view.getChildAt(0);
    
            Field mLargeLabelField = menuView.getClass().getDeclaredField("mLargeLabel");
            Field mSmallLabelField = menuView.getClass().getDeclaredField("mSmallLabel");
            Field mShiftAmountField = menuView.getClass().getDeclaredField("mShiftAmount");
            Field mScaleUpFactorField = menuView.getClass().getDeclaredField("mScaleUpFactor");
            Field mScaleDownFactorField = menuView.getClass().getDeclaredField("mScaleDownFactor");
    
            mSmallLabelField.setAccessible(true);
            mLargeLabelField.setAccessible(true);
            mShiftAmountField.setAccessible(true);
            mScaleUpFactorField.setAccessible(true);
            mScaleDownFactorField.setAccessible(true);
    
            final float fontScale = view.getResources().getDisplayMetrics().scaledDensity;
    
            for (int i = 0; i < menuView.getChildCount(); i++) {
                BottomNavigationItemView itemView = (BottomNavigationItemView) menuView.getChildAt(i);
    
                TextView lagerObj = (TextView) mLargeLabelField.get(itemView);
                TextView smallObj = (TextView) mSmallLabelField.get(itemView);
                lagerObj.setTextSize(smallObj.getTextSize() / fontScale + 0.5f);
    
                mShiftAmountField.set(itemView, 0);
                mScaleUpFactorField.set(itemView, 1f);
                mScaleDownFactorField.set(itemView, 1f);
    
                itemView.setChecked(itemView.getItemData().isChecked());
            }
        } catch (NoSuchFieldException | IllegalAccessException e) {
            e.printStackTrace();
        }
    
    }
    
  • 然后初始化后调用

    // 默认count>3时选中效果会影响ViewPager的滑动切换效果,故利用反射去掉
    BottomNavigationViewHelper.disableShiftMode(bottomNavigationView);
    // 本人没有关闭图标缩放动能,该方法不用
    // BottomNavigationViewHelper.disableItemScale(bottomNavigationView);
    
  • 布局文件

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        tools:context=".activity.MainActivity">
    
        <com.xxx.xxxxx.view.NoScrollViewPager
            android:id="@+id/viewpager"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1" />
    
        <android.support.design.widget.BottomNavigationView
            android:id="@+id/bottom_navigation"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_gravity="bottom"
            android:background="@color/colorAppBackground"
            app:itemIconTint="@drawable/main_view"
            app:itemTextColor="@drawable/main_view"
            app:menu="@menu/main_navigation" />
    
    </LinearLayout>
    
  • Menu文件

    <?xml version="1.0" encoding="utf-8"?>
    <menu xmlns:android="http://schemas.android.com/apk/res/android">
        <item
            android:id="@+id/menu_home"
            android:icon="@drawable/icon_main_home"
            android:orderInCategory="1"
            android:title="资讯" />
        <item
            android:id="@+id/menu_product"
            android:icon="@drawable/icon_main_product"
            android:orderInCategory="2"
            android:title="项目" />
        <item
            android:id="@+id/menu_lease"
            android:icon="@drawable/icon_main_lease"
            android:orderInCategory="4"
            android:title="租房" />
        <item
            android:id="@+id/menu_mine"
            android:icon="@drawable/icon_main_mine"
            android:orderInCategory="5"
            android:title="我的" />
    </menu>
    
  • Selector文件

    <?xml version="1.0" encoding="utf-8"?>
    <selector xmlns:android="http://schemas.android.com/apk/res/android">
    
        <item android:color="@color/colorPrimary" android:state_checked="true" />
        <item android:color="@color/colorSecondary" />
    
    </selector>
    
  • 效果如图:


☠ But! 在 com.android.support:design 版本高于 27.0.3 之后,这个方法失效,这也是我接下来着重介绍 方法二 的原因


方法二

  • 此方法仅针对 Gradle 文件引用 com.android.support:design 版本高于 27.0.3

首先看一下源码

  • com.android.support:design <= 27.0.3

    • BottomNavigationMenuView 中源码

      public class BottomNavigationMenuView extends ViewGroup implements MenuView {
      
          // ...
          // 就是这个属性
          private boolean mShiftingMode = true;
          // ...
      
          public void buildMenuView() {
              //...
              mButtons = new BottomNavigationItemView[mMenu.size()];
              // 为什么item大于3的时候,会有缩放
              mShiftingMode = mMenu.size() > 3;
              for (int i = 0; i < mMenu.size(); i++) {
                  BottomNavigationItemView child = getNewItem();
                  child.setShiftingMode(mShiftingMode);
                  //...            
              }
          } 
      }    
      
    • BottomNavigationItemView 中源码

      public class BottomNavigationItemView extends FrameLayout implements MenuView.ItemView {
          private final int mShiftAmount;
          private final float mScaleUpFactor;
          private final float mScaleDownFactor;
      
          private boolean mShiftingMode;
          public BottomNavigationItemView(Context context, AttributeSet attrs, int defStyleAttr) {
              super(context, attrs, defStyleAttr);
              final Resources res = getResources();
              int inactiveLabelSize = res.getDimensionPixelSize(R.dimen.design_bottom_navigation_text_size);
                int activeLabelSize = res.getDimensionPixelSize( R.dimen.design_bottom_navigation_active_text_size);
              mDefaultMargin = res.getDimensionPixelSize(R.dimen.design_bottom_navigation_margin);
              mShiftAmount = inactiveLabelSize - activeLabelSize;
              mScaleUpFactor = 1f * activeLabelSize / inactiveLabelSize;
              mScaleDownFactor = 1f * inactiveLabelSize / activeLabelSize;
      
              LayoutInflater.from(context).inflate(R.layout.design_bottom_navigation_item, this, true);
              setBackgroundResource(R.drawable.design_bottom_navigation_item_background);
              mIcon = findViewById(R.id.icon);
              mSmallLabel = findViewById(R.id.smallLabel);
              mLargeLabel = findViewById(R.id.largeLabel);
          }
      
          @Override
          public void setChecked(boolean checked) {
      
              if (mShiftingMode) {
                  if (checked) {
                      LayoutParams iconParams = (LayoutParams) mIcon.getLayoutParams();
                      iconParams.gravity = Gravity.CENTER_HORIZONTAL | Gravity.TOP;
                      iconParams.topMargin = mDefaultMargin;
                      mIcon.setLayoutParams(iconParams);
                      mLargeLabel.setVisibility(VISIBLE);
                      mLargeLabel.setScaleX(1f);
                      mLargeLabel.setScaleY(1f);
                  } else {
                      LayoutParams iconParams = (LayoutParams) mIcon.getLayoutParams();
                      iconParams.gravity = Gravity.CENTER;
                      iconParams.topMargin = mDefaultMargin;
                      mIcon.setLayoutParams(iconParams);
                      // ShiftingMode模式下,不选中文字隐藏
                      mLargeLabel.setVisibility(INVISIBLE);
                      mLargeLabel.setScaleX(0.5f);
                      mLargeLabel.setScaleY(0.5f);
                  }
                  mSmallLabel.setVisibility(INVISIBLE);
              } else {
      
                  if (checked) {
                      LayoutParams iconParams = (LayoutParams) mIcon.getLayoutParams();
                      iconParams.gravity = Gravity.CENTER_HORIZONTAL | Gravity.TOP;
                      // 通过设置mShiftAmount 的大小 控制图片margin,进而控制图片大小
                      iconParams.topMargin = mDefaultMargin + mShiftAmount;
                      mIcon.setLayoutParams(iconParams);
                      // 通过设置mLargeLabel和mSmallLabel的显示与隐藏控制文本大小
                      // 如果把他俩TextSize设置为一样。则就不会变化
                      mLargeLabel.setVisibility(VISIBLE);
                      mSmallLabel.setVisibility(INVISIBLE);
      
                      mLargeLabel.setScaleX(1f);
                      mLargeLabel.setScaleY(1f);
                      mSmallLabel.setScaleX(mScaleUpFactor);
                      mSmallLabel.setScaleY(mScaleUpFactor);
                  } else {
                      LayoutParams iconParams = (LayoutParams) mIcon.getLayoutParams();
                      iconParams.gravity = Gravity.CENTER_HORIZONTAL | Gravity.TOP;
                      iconParams.topMargin = mDefaultMargin;
                      mIcon.setLayoutParams(iconParams);
                      mLargeLabel.setVisibility(INVISIBLE);
                      mSmallLabel.setVisibility(VISIBLE);
      
                      mLargeLabel.setScaleX(mScaleDownFactor);
                      mLargeLabel.setScaleY(mScaleDownFactor);
                      mSmallLabel.setScaleX(1f);
                      mSmallLabel.setScaleY(1f);
                  }
              }
      
              refreshDrawableState();
          } 
      }
      
  • com.android.support:design > 27.0.3

    • BottomNavigationMenuView 中源码

      public class BottomNavigationMenuView extends ViewGroup implements MenuView {
      
          // ...
          // mShiftingMode属性消失,取而代之的是这两个属性
      
          // itemHorizontalTranslationEnabled:是否启用水平移动动画(如果允许图标会有少许水平移动)
             private boolean itemHorizontalTranslationEnabled;
          // labelVisibilityMode:标签显示模式(labeled:显示标签,auto:根据标签个数判断是否显示,selected:选中的显示,unlabeled:不显示)
          private int labelVisibilityMode;
      
          // ...
          public void buildMenuView() {
              //...
              if (this.menu.size() == 0) {
                  this.selectedItemId = 0;
                  this.selectedItemPosition = 0;
                  this.buttons = null;
              } else {
                  this.buttons = new BottomNavigationItemView[this.menu.size()];
                  boolean shifting = this.isShifting(this.labelVisibilityMode, this.menu.getVisibleItems().size());
      
                  for(i = 0; i < this.menu.size(); ++i) {
                      this.presenter.setUpdateSuspended(true);
                      this.menu.getItem(i).setCheckable(true);
                      this.presenter.setUpdateSuspended(false);
                      BottomNavigationItemView child = this.getNewItem();
                      //...            
                  }
          } 
      
          //...
          private boolean isShifting(int labelVisibilityMode, int childCount) {
              // 这个地方很奇怪,看了BottomNavigationItem中对labelVisibilityMode做了对应处理才明白
              // 默认labelVisibilityMode为"auto",也就是"-1",如果Item>3默认开启水平动画,否则关闭水平动画。
              // 如果labelVisibilityMode用户主动设置了值,且不为"auto",只在"selected"模式下开启动画
              // "labeled"和"unlabeled"两种模式都不开启动画
              return labelVisibilityMode == -1 ? childCount > 3 : labelVisibilityMode == 0;
          }
          //...
      
      }
      
    • 要想彻底关闭水平滑动动画,前提还要同时满足 itemHorizontalTranslationEnabled = false

      //...
      protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
          int width = MeasureSpec.getSize(widthMeasureSpec);
             int visibleCount = this.menu.getVisibleItems().size();
             int totalCount = this.getChildCount();
             int heightSpec = MeasureSpec.makeMeasureSpec(this.itemHeight, 1073741824);
             int totalWidth;
             int i;
             int extra;
             int i;
          // 两个条件都不满足才能禁止水平动画
          if (this.isShifting(this.labelVisibilityMode, visibleCount) && this.itemHorizontalTranslationEnabled) {
              View activeChild = this.getChildAt(this.selectedItemPosition);
                 i = this.activeItemMinWidth;
                 if (activeChild.getVisibility() != 8) {
                     activeChild.measure(MeasureSpec.makeMeasureSpec(this.activeItemMaxWidth, -2147483648), heightSpec);
                     i = Math.max(i, activeChild.getMeasuredWidth());
                 }
              //...                    
          }
      }    
      //...
      
    • BottomNavigationItemView 中对 labelVisibilityMode 也做了对应处理

      //...            
      // labelVisibilityMode="auto"时,值为-1
      // labelVisibilityMode="selected"时,值为0
      // labelVisibilityMode="labeled"时,值为1
      // labelVisibilityMode="unlabeled"时,值为2
      
      public void setChecked(boolean checked) {
          this.largeLabel.setPivotX((float)(this.largeLabel.getWidth() / 2));
          this.largeLabel.setPivotY((float)this.largeLabel.getBaseline());
          this.smallLabel.setPivotX((float)(this.smallLabel.getWidth() / 2));
          this.smallLabel.setPivotY((float)this.smallLabel.getBaseline());
          switch(this.labelVisibilityMode) {
              case -1:
                  if (this.isShifting) {
                      if (checked) {
                          this.setViewLayoutParams(this.icon, this.defaultMargin, 49);
                          this.setViewValues(this.largeLabel, 1.0F, 1.0F, 0);
                      } else {
                          this.setViewLayoutParams(this.icon, this.defaultMargin, 17);
                          this.setViewValues(this.largeLabel, 0.5F, 0.5F, 4);
                      }
      
                      this.smallLabel.setVisibility(4);
                  } else if (checked) {
                      this.setViewLayoutParams(this.icon, (int)((float)this.defaultMargin + this.shiftAmount), 49);
                      this.setViewValues(this.largeLabel, 1.0F, 1.0F, 0);
                      this.setViewValues(this.smallLabel, this.scaleUpFactor, this.scaleUpFactor, 4);
                  } else {
                      this.setViewLayoutParams(this.icon, this.defaultMargin, 49);
                      this.setViewValues(this.largeLabel, this.scaleDownFactor, this.scaleDownFactor, 4);
                      this.setViewValues(this.smallLabel, 1.0F, 1.0F, 0);
                  }
                  break;
              case 0:
                  if (checked) {
                      this.setViewLayoutParams(this.icon, this.defaultMargin, 49);
                      this.setViewValues(this.largeLabel, 1.0F, 1.0F, 0);
                  } else {
                      this.setViewLayoutParams(this.icon, this.defaultMargin, 17);
                      this.setViewValues(this.largeLabel, 0.5F, 0.5F, 4);
                  }
      
                  this.smallLabel.setVisibility(4);
                  break;
              case 1:
                  if (checked) {
                      this.setViewLayoutParams(this.icon, (int)((float)this.defaultMargin + this.shiftAmount), 49);
                      this.setViewValues(this.largeLabel, 1.0F, 1.0F, 0);
                      this.setViewValues(this.smallLabel, this.scaleUpFactor, this.scaleUpFactor, 4);
                  } else {
                      this.setViewLayoutParams(this.icon, this.defaultMargin, 49);
                      this.setViewValues(this.largeLabel, this.scaleDownFactor, this.scaleDownFactor, 4);
                      this.setViewValues(this.smallLabel, 1.0F, 1.0F, 0);
                  }
                  break;
              case 2:
                  this.setViewLayoutParams(this.icon, this.defaultMargin, 17);
                  this.largeLabel.setVisibility(8);
                  this.smallLabel.setVisibility(8);
          }
      
              this.refreshDrawableState();
              this.setSelected(checked);
      }
      //...
      
  • 最终,在新版的 Android Design 中使用 BottomNavigationView 只用在布局文件中添加两个属性就可以达到上图中的效果

    • app:itemHorizontalTranslationEnabled=”false”
    • app:labelVisibilityMode=”labeled”

      <?xml version="1.0" encoding="utf-8"?>
      <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
          xmlns:app="http://schemas.android.com/apk/res-auto"
          xmlns:tools="http://schemas.android.com/tools"
          android:layout_width="match_parent"
          android:layout_height="match_parent"
          android:orientation="vertical"
          tools:context=".activity.MainActivity">
      
          <com.xxx.xxxxx.view.NoScrollViewPager
              android:id="@+id/viewpager"
              android:layout_width="match_parent"
              android:layout_height="0dp"
              android:layout_weight="1" />
      
          <android.support.design.widget.BottomNavigationView
              android:id="@+id/bottom_navigation"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:layout_gravity="bottom"
              android:background="@color/colorAppBackground"
              app:itemHorizontalTranslationEnabled="false"
              app:itemIconTint="@drawable/main_view"
              app:itemTextColor="@drawable/main_view"
              app:labelVisibilityMode="labeled"
              app:menu="@menu/main_navigation" />
      
      </LinearLayout>
      
  • 最后,展示一下各个模式下区别

    • labeled

    • unlabeled

    • select

    • auto

      • 如果item>3效果等同于select
      • 如果item<=3效果等同于labeled
------------本文结束感谢您的阅读------------

本文标题:BottomNavigationView最新用法

文章作者:Cazaea

发布时间:2018年09月27日 - 15:09

最后更新:2018年11月12日 - 11:11

原始链接:https://cazaea.com/2018/09/27/BottomNavigationView最新使用/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

坚持原创技术分享,您的支持将鼓励我继续创作!