使用新版的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);
  }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章