使用新版的BottomNavigationView当Item大于3时,去除动画很简单

BottomNavigationView 是用来实现底部导航的功能,是在api 26的推出的,是兼容的,而且在android Studio有模板代码,用起来很方便,item可以添加1-5个,但是当item超过3是就会有偏移动画 且选中时文字才会显示,如下效果
在这里插入图片描述

但在新版的BottomNavigationView 两个属性就可以解决这个问题 (网上通过反射的解决方案在这个新版本无效,因为此版本BottomNavigationView底层源码修改了,后面会提到)
1.引入依赖 design包要28.0.0以上版本

implementation 'com.android.support:design:28.0.0'

或者引入这个包implementation 'com.google.android.material:material:1.0.0-beta01',二者选一个即可, google以后应该会推荐这个包。目前还是bata版

2.设置下面这两个属性就可以解决上面问题

     app:itemHorizontalTranslationEnabled="false"  
     app:labelVisibilityMode="labeled"

app:itemHorizontalTranslationEnabled="false" 禁止item水平平移动画效果,对应的的方法
setItemHorizontalTranslationEnabled(false)
app:labelVisibilityMode="labeled" 设置图标下面的文字显示,该属性对应的值有autolabeledselectedunlabeled

auto   当item小于等于3是,显示文字,item大于3个默认不显示,选中显示文字
labeled  始终显示文字
selected  选中时显示
unlabeled 选中时显示

该属性对应的方法是setLabelVisibilityMode(LabelVisibilityMode.LABEL_VISIBILITY_LABELED)

看看源码,这两个属性如何起作用的
看过源码你会发现BottomNavigationView 子父关系如下图所示
在这里插入图片描述

BottomNavigationView 构造方法

 public BottomNavigationView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
     final Resources res = getResources();
        // 未选中的item最大宽度 96dp
        inactiveItemMaxWidth =
            res.getDimensionPixelSize(R.dimen.design_bottom_navigation_item_max_width);
        // 未选中的item最小宽度 56dp
        inactiveItemMinWidth =
                res.getDimensionPixelSize(R.dimen.design_bottom_navigation_item_min_width);
        // 选中的item最大宽度 168dp
        activeItemMaxWidth =
                res.getDimensionPixelSize(R.dimen.design_bottom_navigation_active_item_max_width);
        // 选中的item最小宽度 96dp
        activeItemMinWidth =
                res.getDimensionPixelSize(R.dimen.design_bottom_navigation_active_item_min_width);
//        item 的高度56dp
        itemHeight = res.getDimensionPixelSize(R.dimen.design_bottom_navigation_height);
 …… code略
// 设置底部文字的显示模式,获取labelVisibilityMode属性的值,默认为LABEL_VISIBILITY_AUTO 
	setLabelVisibilityMode( a.getInteger(   R.styleable.BottomNavigationView_labelVisibilityMode,
            LabelVisibilityMode.LABEL_VISIBILITY_AUTO));
    // 设置是否水平平移
	  setItemHorizontalTranslationEnabled(a.getBoolean(
	  R.styleable.BottomNavigationView_itemHorizontalTranslationEnabled, true));
  
	    int itemBackground = a.getResourceId(R.styleable.BottomNavigationView_itemBackground, 0);
	    menuView.setItemBackgroundRes(itemBackground);

	    if (a.hasValue(R.styleable.BottomNavigationView_menu)) {
	    //填充menu
	      inflateMenu(a.getResourceId(R.styleable.BottomNavigationView_menu, 0));
	    }
            …… code略
  }

 public void inflateMenu(int resId) {
    presenter.setUpdateSuspended(true);
    getMenuInflater().inflate(resId, menu);
    presenter.setUpdateSuspended(false);
    //该方法会调用BottomNavigationMenuView 的buildMenuView方法,创建BottomNavigationItemView并添加到BottomNavigationMenuView中。
    presenter.updateMenuView(true);
  }


BottomNavigationMenuView 的测量方法

  @Override
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
   …… code略
   //在老版的中有一个mShiftingMode的Boolean属性来判断,新版的BottomNavigationMenuView 没有这个属性,而是用下面的方式进行判断
    if (isShifting(labelVisibilityMode, visibleCount) && itemHorizontalTranslationEnabled) {
        // 获取选中的item
            final View activeChild = getChildAt(selectedItemPosition);
//
            int activeItemWidth = activeItemMinWidth;
            if (activeChild.getVisibility() != View.GONE) {
                // Do an AT_MOST measure pass on the active child to get its desired width, and resize the
                // active child view based on that width
                activeChild.measure(
                        MeasureSpec.makeMeasureSpec(activeItemMaxWidth, MeasureSpec.AT_MOST), heightSpec);
// 测量宽度和最小宽度比较取最大值做选中的宽度
                activeItemWidth = Math.max(activeItemWidth, activeChild.getMeasuredWidth());
            }
//            未选中的item个数
            final int inactiveCount = visibleCount - (activeChild.getVisibility() != View.GONE ? 1 : 0);
//  选中item的宽度最大值,总宽度减去未选择item宽度之和
            final int activeMaxAvailable = width - inactiveCount * inactiveItemMinWidth;
// 取可以盛放选中的item的最小宽度值
            final int activeWidth =
                    Math.min(activeMaxAvailable, Math.min(activeItemWidth, activeItemMaxWidth));
//            未选择item的最大宽度值,
            final int inactiveMaxAvailable =
                    (width - activeWidth) / (inactiveCount == 0 ? 1 : inactiveCount);
// 取可以盛放所以未选中的item的最小宽度值
            final int inactiveWidth = Math.min(inactiveMaxAvailable, inactiveItemMaxWidth);
// 总宽度减去未选中和选中item 所占宽度之后剩下的额外宽度
            int extra = width - activeWidth - inactiveWidth * inactiveCount;
// 把额外的空间分配给每个临时item宽度
            for (int i = 0; i < totalCount; i++) {
                if (getChildAt(i).getVisibility() != View.GONE) {
                    tempChildWidths[i] = (i == selectedItemPosition) ? activeWidth : inactiveWidth;
                    // Account for integer division which sometimes leaves some extra pixel spaces.
                    // e.g. If the nav was 10px wide, and 3 children were measured to be 3px-3px-3px, there
                    // would be a 1px gap somewhere, which this fills in.
                    if (extra > 0) {
                        tempChildWidths[i]++;
                        extra--;
                    }
                } else {
                    tempChildWidths[i] = 0;
                }
            }
      }
    } else {
   …… code略
    }
   …… code略
  }
 /**
     * @param labelVisibilityMode  label(底部文字)显示的模式
     * @param childCount  BottomNavigationView的Item数
     * @return  改返回值数上面两个参数影响,labelVisibilityMode的默认就是LabelVisibilityMode.LABEL_VISIBILITY_AUTO,所以,
     * 默认情况只有item大于三,该方法返回值就是true
     */
private boolean isShifting(@LabelVisibilityMode int labelVisibilityMode, int childCount) {
    return labelVisibilityMode == LabelVisibilityMode.LABEL_VISIBILITY_AUTO
        ? childCount > 3
        : labelVisibilityMode == LabelVisibilityMode.LABEL_VISIBILITY_SELECTED;
  }

BottomNavigationMenuView 的buildMenuView方法

 public void buildMenuView() {
  …… code略
    //根据菜单个数创建储存BottomNavigationItemView的数组
    buttons = new BottomNavigationItemView[menu.size()];
    boolean shifting = isShifting(labelVisibilityMode, menu.getVisibleItems().size());
    for (int i = 0; i < menu.size(); i++) {
      presenter.setUpdateSuspended(true);
      menu.getItem(i).setCheckable(true);
      presenter.setUpdateSuspended(false);
      BottomNavigationItemView child = getNewItem();
      buttons[i] = child;
      child.setIconTintList(itemIconTint);
      child.setIconSize(itemIconSize);
      // Set the text color the default, then look for another text color in order of precedence.
      child.setTextColor(itemTextColorDefault);
      child.setTextAppearanceInactive(itemTextAppearanceInactive);
      child.setTextAppearanceActive(itemTextAppearanceActive);
      child.setTextColor(itemTextColorFromUser);
      if (itemBackground != null) {
        child.setItemBackground(itemBackground);
      } else {
        child.setItemBackground(itemBackgroundRes);
      }
      //将是否发生位移、文字显示模式等传递给子类
      child.setShifting(shifting);
      child.setLabelVisibilityMode(labelVisibilityMode);
      child.initialize((MenuItemImpl) menu.getItem(i), 0);
      child.setItemPosition(i);
      child.setOnClickListener(onClickListener);
      addView(child);
    }
    selectedItemPosition = Math.min(menu.size() - 1, selectedItemPosition);
    menu.getItem(selectedItemPosition).setChecked(true);
  }

接着看一下BottomNavigationItemView 里面的一些重要方法
BottomNavigationItemView 关联的布局

<merge xmlns:android="http://schemas.android.com/apk/res/android">
   <!-- icon -->
  <ImageView
      android:id="@+id/icon"
      android:layout_width="24dp"
      android:layout_height="24dp"
      android:layout_marginTop="@dimen/design_bottom_navigation_margin"
      android:layout_marginBottom="@dimen/design_bottom_navigation_margin"
      android:layout_gravity="center_horizontal"
      android:contentDescription="@null"
      android:duplicateParentState="true"/>
  <com.google.android.material.internal.BaselineLayout
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_gravity="bottom|center_horizontal"
      android:paddingBottom="10dp"
      android:clipToPadding="false"
      android:duplicateParentState="true">
      <!-- icon 底部默认的文字-->
    <TextView
        android:id="@+id/smallLabel"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:duplicateParentState="true"
        android:maxLines="1"
        android:textSize="@dimen/design_bottom_navigation_text_size"/>
           <!-- icon 选择时显示的文字,默认是隐藏的选中是显示,字号比默认的大-->
    <TextView
        android:id="@+id/largeLabel"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:paddingLeft="8dp"
        android:paddingRight="8dp"
        android:duplicateParentState="true"
        android:maxLines="1"
        android:textSize="@dimen/design_bottom_navigation_active_text_size"
        android:visibility="invisible"/>
  </com.google.android.material.internal.BaselineLayout>
</merge>

BottomNavigationItemView 的setChecked方法

 @Override
  public void setChecked(boolean checked) {
    largeLabel.setPivotX(largeLabel.getWidth() / 2);
    largeLabel.setPivotY(largeLabel.getBaseline());
    smallLabel.setPivotX(smallLabel.getWidth() / 2);
    smallLabel.setPivotY(smallLabel.getBaseline());
// 判断labelVisibilityMode 
    switch (labelVisibilityMode) {
      case LabelVisibilityMode.LABEL_VISIBILITY_AUTO:  //  auto自动模式
        if (isShifting) {  //只有LABEL_VISIBILITY_AUTO模式才会对isShifting进行判断
          if (checked) {
            setViewLayoutParams(icon, defaultMargin, Gravity.CENTER_HORIZONTAL | Gravity.TOP);
            setViewValues(largeLabel, 1f, 1f, VISIBLE);  //largeLabel显示,
          } else {
            setViewLayoutParams(icon, defaultMargin, Gravity.CENTER);
            setViewValues(largeLabel, 0.5f, 0.5f, INVISIBLE); //largeLabel隐藏
          }
  
          //isShifting的值是BottomNavigationMenuView 的isShifting()方法得到的,如果模式是   
         //   LABEL_VISIBILITY_AUTO且item大于3 ,那么改方法返回值就为true,所有默认情况如果item大
         //于3 ,底部的文字是隐藏的,选中的才会显示文字
          smallLabel.setVisibility(INVISIBLE); 
        } else {
          if (checked) {
            setViewLayoutParams(
                icon, (int) (defaultMargin + shiftAmount), Gravity.CENTER_HORIZONTAL | Gravity.TOP);
            setViewValues(largeLabel, 1f, 1f, VISIBLE);
            setViewValues(smallLabel, scaleUpFactor, scaleUpFactor, INVISIBLE);
          } else {
            setViewLayoutParams(icon, defaultMargin, Gravity.CENTER_HORIZONTAL | Gravity.TOP);
            setViewValues(largeLabel, scaleDownFactor, scaleDownFactor, INVISIBLE);
            setViewValues(smallLabel, 1f, 1f, VISIBLE);
          }
        }
        break;

      case LabelVisibilityMode.LABEL_VISIBILITY_SELECTED: //selected 选中模式
        if (checked) {
          setViewLayoutParams(icon, defaultMargin, Gravity.CENTER_HORIZONTAL | Gravity.TOP);
          setViewValues(largeLabel, 1f, 1f, VISIBLE);
        } else {
          setViewLayoutParams(icon, defaultMargin, Gravity.CENTER);
          setViewValues(largeLabel, 0.5f, 0.5f, INVISIBLE);
        }
        //不管item有多少,默认都是隐藏的
        smallLabel.setVisibility(INVISIBLE);
        break;

      case LabelVisibilityMode.LABEL_VISIBILITY_LABELED:// labeled 显示模式
        if (checked) {
          setViewLayoutParams(
              icon, (int) (defaultMargin + shiftAmount), Gravity.CENTER_HORIZONTAL | Gravity.TOP);
          setViewValues(largeLabel, 1f, 1f, VISIBLE);  //显示大文字
          setViewValues(smallLabel, scaleUpFactor, scaleUpFactor, INVISIBLE);//隐藏小文字
        } else {
          setViewLayoutParams(icon, defaultMargin, Gravity.CENTER_HORIZONTAL | Gravity.TOP);
          setViewValues(largeLabel, scaleDownFactor, scaleDownFactor, INVISIBLE);//隐藏大文字
          setViewValues(smallLabel, 1f, 1f, VISIBLE);//显示小文字
        }
        break;

      case LabelVisibilityMode.LABEL_VISIBILITY_UNLABELED: //unlabeled 文字不显示
        setViewLayoutParams(icon, defaultMargin, Gravity.CENTER);
        //大小文字都不显示
        largeLabel.setVisibility(GONE);
        smallLabel.setVisibility(GONE);
        break;

      default:
        break;
    }
    refreshDrawableState();
    // Set the item as selected to send an AccessibilityEvent.TYPE_VIEW_SELECTED from View, so that
    // the item is read out as selected.
    setSelected(checked);
  }


 private void setViewValues(@NonNull View view, float scaleX, float scaleY, int visibility) {
    view.setScaleX(scaleX);
    view.setScaleY(scaleY);
    view.setVisibility(visibility);
  }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章