BottomNavigationView 控件去除放大缩小动画

BottomNavigationView 控件去除放大缩小动画

最近项目中有用到底部导航栏,最初的底部导航栏是使用的是‘com.android.support:design’包android.support.design.widget.BottomNavigationView 进行设置;按钮点击后的放大效果可以反射 BottomNavigationMenuView 下的mShiftingMode 属性进行取消。即可做到取消放大缩小动画效果。代码如下:

 @SuppressLint("RestrictedApi")
 public static void disableShiftMode(BottomNavigationView navigationView) {
        BottomNavigationMenuView menuView = (BottomNavigationMenuView) navigationView.getChildAt(0);
        try {
            Field shiftingMode = menuView.getClass().getDeclaredField("mShiftingMode");
            shiftingMode.setAccessible(true);
            shiftingMode.setBoolean(menuView, false);
            shiftingMode.setAccessible(false);
            for (int i = 0; i < menuView.getChildCount(); i++) {
                BottomNavigationItemView itemView = (BottomNavigationItemView) menuView.getChildAt(i);
                itemView.setShiftingMode(false);
                itemView.setChecked(itemView.getItemData().isChecked());
            }
        } catch (NoSuchFieldException | IllegalAccessException e) {
            e.printStackTrace();
        }
  }

但是在新版本中,当你引入design 依赖。当在布局文件中写入Button 后不再有android.support.design.widget.BottomNavigationView的提示。


image

出现的是com.google.android.material.bottomnavigation.BottomNavigationView
既然官方 推荐使用BottomNavigationView 那就抱着试试的心态去使用了 , 属性基本和design 包下的BottomNavigationView 使用一致。

使用如下:

<com.google.android.material.bottomnavigation.BottomNavigationView
            android:id="@+id/bottom_navigation_view"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_alignParentBottom="true"
            android:layout_gravity="bottom"
            app:itemBackground="@null"
            app:itemHorizontalTranslationEnabled="false"
            app:itemIconTint="@drawable/color_state_menu_navi"
            app:itemTextColor="@drawable/color_state_menu_navi"
            app:menu="@menu/bottom_navigation_main" />

运行后的效果却还是带有放大动画效果,这不是产品想要的效果。度娘了一番后部分博客给出了添加属性的方法。

app:labelVisibilityMode="labeled"

运行后动画效果,却还是存在。
运行后效果.gif

查看BottomNavigationView 源码:

image.png
package com.google.android.material.bottomnavigation;

public class BottomNavigationView extends FrameLayout {
    private static final int MENU_PRESENTER_ID = 1;
    private final MenuBuilder menu;
    private final BottomNavigationMenuView menuView;
    private final BottomNavigationPresenter presenter;
    private MenuInflater menuInflater;

在BottomNavigationView 类中搜索,没有找到mShiftingMode 延伸阅读 BottomNavigationMenuView 看看这个菜单类。
希望在 BottomNavigationMenuView源码中或许有 mShiftingMode 属性的调用。
直接搜索。。
还是没有!
无奈,就读下源码。
在BottomNavigationMenuView 中发现以下代码。

private final Pool<BottomNavigationItemView> itemPool;

由对象池管理BottomNavigation 每一个Item 的View。
点击 BottomNavigationItemView 查看源码。

public BottomNavigationItemView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        this.itemPosition = -1;
        Resources res = this.getResources();
        LayoutInflater.from(context).inflate(layout.design_bottom_navigation_item, this, true);
        this.setBackgroundResource(drawable.design_bottom_navigation_item_background);
        this.defaultMargin = res.getDimensionPixelSize(dimen.design_bottom_navigation_margin);
        this.icon = (ImageView)this.findViewById(id.icon);
        this.smallLabel = (TextView)this.findViewById(id.smallLabel);
        this.largeLabel = (TextView)this.findViewById(id.largeLabel);
        ViewCompat.setImportantForAccessibility(this.smallLabel, 2);
        ViewCompat.setImportantForAccessibility(this.largeLabel, 2);
        this.setFocusable(true);
        this.calculateTextScaleFactors(this.smallLabel.getTextSize(), this.largeLabel.getTextSize());
    }

在构造方法中有icon 和 文本标签,这就是每一个Item的实现类了。
开始梳理调用逻辑。--------------->>>>>>>>>
在构造方法中,有两个TextView smallLabel 和 largeLabel 的初始化。一个item 按钮有2个文本的显示,一个小标签,一个大标签,icon 忽略。并且调用了calculateTextScaleFactors 方法。

 private void calculateTextScaleFactors(float smallLabelSize, float largeLabelSize) {
        this.shiftAmount = smallLabelSize - largeLabelSize;
        this.scaleUpFactor = 1.0F * largeLabelSize / smallLabelSize;
        this.scaleDownFactor = 1.0F * smallLabelSize / largeLabelSize;
    }

搜索 shiftAmount 、scaleUpFactor、scaleDownFactor 调用位置。

         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;

在上述代码 else if (checked) 及 else 处有对三个属性进行调用。

private void setViewLayoutParams(@NonNull View view, int topMargin, int gravity) {
        LayoutParams viewParams = (LayoutParams)view.getLayoutParams();
        viewParams.topMargin = topMargin;
        viewParams.gravity = gravity;
        view.setLayoutParams(viewParams);
    }
  private void setViewValues(@NonNull View view, float scaleX, float scaleY, int visibility) {
        view.setScaleX(scaleX);
        view.setScaleY(scaleY);
        view.setVisibility(visibility);
    }

通过上述的两个方法,可以得知
shiftAmount 为icon设置上边距的偏移量
scaleUpFactor 为 largeLabel 的缩放值,默认为1.0F
scaleDownFactor 为smallLabel 的缩放值 ,默认为 1.0F

找到了调用逻辑,接下来就对shiftAmount,scaleUpFactor,scaleDownFactor 属性进行反射处理,让点击和非点击状态的大小一致。
设置 shiftAmount 的偏移量为0,使icon 不上下移动。
设置 scaleUpFactor scaleDownFactor 为默认状态下的值 1。
使BottomNavigationItemView 处于默认状态,不发生位置偏移。

代码如下:

 @SuppressLint("RestrictedApi")
 public void closeAnimation(BottomNavigationView view) {
        BottomNavigationMenuView mMenuView = (BottomNavigationMenuView) view.getChildAt(0);
        for (int i = 0; i < mMenuView.getChildCount(); i++) {
            BottomNavigationItemView button = (BottomNavigationItemView) mMenuView.getChildAt(i);
            TextView mLargeLabel = getField(button.getClass(), button, "largeLabel");
            TextView mSmallLabel = getField(button.getClass(), button, "smallLabel");
            float mSmallLabelSize = mSmallLabel.getTextSize();
            setField(button.getClass(), button, "shiftAmount", 0F);
            setField(button.getClass(), button, "scaleUpFactor", 1F);
            setField(button.getClass(), button, "scaleDownFactor", 1F);
            mLargeLabel.setTextSize(TypedValue.COMPLEX_UNIT_PX, mSmallLabelSize);
        }
        mMenuView.updateMenuView();
    }


 private <T> T getField(Class targetClass, Object instance, String fieldName) {
        try {
            Field field = targetClass.getDeclaredField(fieldName);
            field.setAccessible(true);
            return (T) field.get(instance);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return null;
    }


    private void setField(Class targetClass, Object instance, String fieldName, Object value) {
        try {
            Field field = targetClass.getDeclaredField(fieldName);
            field.setAccessible(true);
            field.set(instance, value);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }

运行后得到想要的效果。


运行后效果.gif

延伸阅读layout布局
layout.design_bottom_navigation_item

<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android">
  <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">
    <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"/>
    <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>

<dimen name="design_bottom_navigation_text_size">12sp</dimen>
<dimen name="design_bottom_navigation_active_text_size">14sp</dimen>

如有理解有误的地方,希望指正。
转载请注明来源,谢谢!