com.android.support:design 版本高于 27.0.3 时BottomNavigationView的最新用法


title: BottomNavigationView最新用法
tags:
- BottomNavigationView
- Android Studio V3.2
- Gradle4.6
- Android Design 28.0.2

categories: Android
copyright: true
top: 10

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