Android BottomNavigationView 底部导航

Android Design Support Library 25 中增加了 BottomNavigationView 这个控件。该控件可以为我们很方便提供底部导航,下面我们介绍一下该控件。


      

   


1、依赖:build.gradle文件中增加依赖

compile 'com.android.support:design:25.3.1'


2、xml文件:

        android:id="@+id/bottom_navigation_view_bottomNavigationView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        app:menu="@menu/navigation_items" />

3、menu文件:



            android:id="@+id/action_phone"
        android:icon="@android:drawable/sym_action_call"
        android:title="phone" />
            android:id="@+id/action_msg"
        android:icon="@android:drawable/sym_action_email"
        android:title="message" />
            android:id="@+id/action_record"
        android:icon="@android:drawable/ic_btn_speak_now"
        android:title="record" />
            android:id="@+id/action_delete"
        android:icon="@android:drawable/ic_menu_delete"
        android:title="delete" />
            android:id="@+id/action_record1"
        android:icon="@android:drawable/ic_btn_speak_now"
        android:title="record" />

通过上面我们完成BottomNavigationView 布局设置。

4、Item中icon和title颜色设置:

app:itemBackground:设置item的背景,对应setItemBackgroundResource(int resId)方法

app:itemIconTint:设置icon的颜色,默认颜色是:@color/colorPrimary。对应setItemIconTintList(ColorStateList tint)方法

app:itemTextColor:设置文字的颜色,默认颜色是:@color/colorPrimary。对应setIteTextColor(ColorStateList textColor)方法

①、自定义颜色

   *选择器:res/color/selector_item_icon_tint



   
   

   * 设置:

app:itemIconTint="@color/selector_item_icon_tint"

5、事件监听:BottomNavigationView 为我们提供两个监听

①、setOnNavigationItemReselectedListener:当MenuItem被选中情况下,再次点击,会调用该方法,可以让我们处理一些选中后再点击刷新的功能。

bottomNavigationView.setOnNavigationItemReselectedListener(new BottomNavigationView.OnNavigationItemReselectedListener() {
            @Override
            public void onNavigationItemReselected(@NonNull MenuItem item) {
              
            }
        });
②、setOnNavigationItemSelectedListener:当点击新的MenuItem情况下,回调该方法。

bottomNavigationView.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
            @Override
            public boolean onNavigationItemSelected(@NonNull MenuItem item) {
               
                return true;
            }
        });
以上两种监听,在实际使用中,如果有一种回调,另一种必然不会有回调。

MenuItem:可以拿到每个item的 itemId、groupId、title、icon等,也可以根据点击事件修改对应item图标、颜色、标题等内容。

6、BottomNavigationView 使用中注意:

①、BottomNavigationView 默认的高是56dp

②、菜单元素只能 1~5(早期版本是3~5),否则会报错。通过源码:BottomNavigationMenu.MAX_ITEM_COUNT = 5

③、底部导航栏背景颜色默认是当前样式的背景色(白色/黑色)

7、解决菜单元素>3效果与<=3效果不同的方案:

①、首先我们看部分源码:

public BottomNavigationView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
 
        ThemeUtils.checkAppCompatTheme(context);
 
        // Create the menu
        mMenu = new BottomNavigationMenu(context);
 
        mMenuView = new BottomNavigationMenuView(context);
        FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
                ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        params.gravity = Gravity.CENTER;
        mMenuView.setLayoutParams(params);
 
        mPresenter.setBottomNavigationMenuView(mMenuView);
        mPresenter.setId(MENU_PRESENTER_ID);
        mMenuView.setPresenter(mPresenter);
        mMenu.addMenuPresenter(mPresenter);
        mPresenter.initForMenu(getContext(), mMenu);
 
        // Custom attributes
        TintTypedArray a = TintTypedArray.obtainStyledAttributes(context, attrs,
                R.styleable.BottomNavigationView, defStyleAttr,
                R.style.Widget_Design_BottomNavigationView);
 
        if (a.hasValue(R.styleable.BottomNavigationView_itemIconTint)) {
            mMenuView.setIconTintList(
                    a.getColorStateList(R.styleable.BottomNavigationView_itemIconTint));
        } else {
            mMenuView.setIconTintList(
                    createDefaultColorStateList(android.R.attr.textColorSecondary));
        }
        if (a.hasValue(R.styleable.BottomNavigationView_itemTextColor)) {
            mMenuView.setItemTextColor(
                    a.getColorStateList(R.styleable.BottomNavigationView_itemTextColor));
        } else {
            mMenuView.setItemTextColor(
                    createDefaultColorStateList(android.R.attr.textColorSecondary));
        }
        if (a.hasValue(R.styleable.BottomNavigationView_elevation)) {
            ViewCompat.setElevation(this, a.getDimensionPixelSize(
                    R.styleable.BottomNavigationView_elevation, 0));
        }
 
        int itemBackground = a.getResourceId(R.styleable.BottomNavigationView_itemBackground, 0);
        mMenuView.setItemBackgroundRes(itemBackground);
 
        if (a.hasValue(R.styleable.BottomNavigationView_menu)) {
            inflateMenu(a.getResourceId(R.styleable.BottomNavigationView_menu, 0));
        }
        a.recycle();
 
        addView(mMenuView, params);
        if (Build.VERSION.SDK_INT < 21) {
            addCompatibilityTopDivider(context);
        }
 
        mMenu.setCallback(new MenuBuilder.Callback() {
            @Override
            public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) {
                if (mReselectedListener != null && item.getItemId() == getSelectedItemId()) {
                    mReselectedListener.onNavigationItemReselected(item);
                    return true; // item is already selected
                }
                return mSelectedListener != null
                        && !mSelectedListener.onNavigationItemSelected(item);
            }
 
            @Override
            public void onMenuModeChange(MenuBuilder menu) {}
        });
    }
通过构造方法,我们发现BottomNavigationView 采用mvp来实现,当我们细看发现ItemView其实就是BotoomNavigationMenuView,那我们继续看BotoomNavigationMenuView部分源码:

public void buildMenuView() {
        removeAllViews();
        if (mButtons != null) {
            for (BottomNavigationItemView item : mButtons) {
                mItemPool.release(item);
            }
        }
        if (mMenu.size() == 0) {
            mSelectedItemId = 0;
            mSelectedItemPosition = 0;
            mButtons = null;
            return;
        }
        mButtons = new BottomNavigationItemView[mMenu.size()];
        mShiftingMode = mMenu.size() > 3;
        for (int i = 0; i < mMenu.size(); i++) {
            mPresenter.setUpdateSuspended(true);
            mMenu.getItem(i).setCheckable(true);
            mPresenter.setUpdateSuspended(false);
            BottomNavigationItemView child = getNewItem();
            mButtons[i] = child;
            child.setIconTintList(mItemIconTint);
            child.setTextColor(mItemTextColor);
            child.setItemBackground(mItemBackgroundRes);
            child.setShiftingMode(mShiftingMode);
            child.initialize((MenuItemImpl) mMenu.getItem(i), 0);
            child.setItemPosition(i);
            child.setOnClickListener(mOnClickListener);
            addView(child);
        }
        mSelectedItemPosition = Math.min(mMenu.size() - 1, mSelectedItemPosition);
        mMenu.getItem(mSelectedItemPosition).setChecked(true);
    }
通过buildMenuVie方法我们发现mShiftingMode = mMenu.size() > 3 (即 mShiftingMode = true or false),由此得知mShiftingMode 的值通过mMenu.size()的值范围来决定的。在遍历过程中,只有child.setShiftingMode(mShiftingMode )不同。

那么我们思路来了,只要把mShiftingMode值修改就可以达到我们需要效果了。

看代码:

BottomNavigationView bottomNavigationView = (BottomNavigationView)findViewById(R.id.bottom_navigation_view_bottomNavigationView);
 BottomNavigationMenuView menuView = (BottomNavigationMenuView) bottomNavigationView .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);  //设置false 让底部导航的动画效果始终保持 <= 3 的效果。
                itemView.setChecked(itemView.getItemData().isChecked());
 
            }
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
这样就是实现菜单元素大于3个时候效果跟小于三个是一样。

8、BottomNavigationItemView:我们通过上面部分源码发现BottomNavigationView 其实就是多个BottomNavigationItemView 。

我们看看源码:

public class BottomNavigationItemView extends FrameLayout implements MenuView.ItemView {
    public static final int INVALID_ITEM_POSITION = -1;
 
    private static final int[] CHECKED_STATE_SET = { android.R.attr.state_checked };
 
    private final int mDefaultMargin;
    private final int mShiftAmount;
    private final float mScaleUpFactor;
    private final float mScaleDownFactor;
 
    private boolean mShiftingMode;
 
    private ImageView mIcon;
    private final TextView mSmallLabel;
    private final TextView mLargeLabel;
    private int mItemPosition = INVALID_ITEM_POSITION;
 
    private MenuItemImpl mItemData;
 
    private ColorStateList mIconTint;
 
    public BottomNavigationItemView(@NonNull Context context) {
        this(context, null);
    }
 
    public BottomNavigationItemView(@NonNull Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }
 
    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 = (ImageView) findViewById(R.id.icon);
        mSmallLabel = (TextView) findViewById(R.id.smallLabel);
        mLargeLabel = (TextView) findViewById(R.id.largeLabel);
    }
 
    @Override
    public void initialize(MenuItemImpl itemData, int menuType) {
        mItemData = itemData;
        setCheckable(itemData.isCheckable());
        setChecked(itemData.isChecked());
        setEnabled(itemData.isEnabled());
        setIcon(itemData.getIcon());
        setTitle(itemData.getTitle());
        setId(itemData.getItemId());
    }
 
    public void setItemPosition(int position) {
        mItemPosition = position;
    }
 
    public int getItemPosition() {
        return mItemPosition;
    }
 
    public void setShiftingMode(boolean enabled) {
        mShiftingMode = enabled;
    }
 
    @Override
    public MenuItemImpl getItemData() {
        return mItemData;
    }
 
    @Override
    public void setTitle(CharSequence title) {
        mSmallLabel.setText(title);
        mLargeLabel.setText(title);
        setContentDescription(title);
    }
 
    @Override
    public void setCheckable(boolean checkable) {
        refreshDrawableState();
    }
 
    @Override
    public void setChecked(boolean checked) {
        ViewCompat.setPivotX(mLargeLabel, mLargeLabel.getWidth() / 2);
        ViewCompat.setPivotY(mLargeLabel, mLargeLabel.getBaseline());
        ViewCompat.setPivotX(mSmallLabel, mSmallLabel.getWidth() / 2);
        ViewCompat.setPivotY(mSmallLabel, mSmallLabel.getBaseline());
        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);
                ViewCompat.setScaleX(mLargeLabel, 1f);
                ViewCompat.setScaleY(mLargeLabel, 1f);
            } else {
                LayoutParams iconParams = (LayoutParams) mIcon.getLayoutParams();
                iconParams.gravity = Gravity.CENTER;
                iconParams.topMargin = mDefaultMargin;
                mIcon.setLayoutParams(iconParams);
                mLargeLabel.setVisibility(INVISIBLE);
                ViewCompat.setScaleX(mLargeLabel, 0.5f);
                ViewCompat.setScaleY(mLargeLabel, 0.5f);
            }
            mSmallLabel.setVisibility(INVISIBLE);
        } else {
            if (checked) {
                LayoutParams iconParams = (LayoutParams) mIcon.getLayoutParams();
                iconParams.gravity = Gravity.CENTER_HORIZONTAL | Gravity.TOP;
                iconParams.topMargin = mDefaultMargin + mShiftAmount;
                mIcon.setLayoutParams(iconParams);
                mLargeLabel.setVisibility(VISIBLE);
                mSmallLabel.setVisibility(INVISIBLE);
 
                ViewCompat.setScaleX(mLargeLabel, 1f);
                ViewCompat.setScaleY(mLargeLabel, 1f);
                ViewCompat.setScaleX(mSmallLabel, mScaleUpFactor);
                ViewCompat.setScaleY(mSmallLabel, 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);
 
                ViewCompat.setScaleX(mLargeLabel, mScaleDownFactor);
                ViewCompat.setScaleY(mLargeLabel, mScaleDownFactor);
                ViewCompat.setScaleX(mSmallLabel, 1f);
                ViewCompat.setScaleY(mSmallLabel, 1f);
            }
        }
 
        refreshDrawableState();
    }
 
    @Override
    public void setEnabled(boolean enabled) {
        super.setEnabled(enabled);
        mSmallLabel.setEnabled(enabled);
        mLargeLabel.setEnabled(enabled);
        mIcon.setEnabled(enabled);
 
        if (enabled) {
            ViewCompat.setPointerIcon(this,
                    PointerIconCompat.getSystemIcon(getContext(), PointerIconCompat.TYPE_HAND));
        } else {
            ViewCompat.setPointerIcon(this, null);
        }
 
    }
 
    @Override
    public int[] onCreateDrawableState(final int extraSpace) {
        final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
        if (mItemData != null && mItemData.isCheckable() && mItemData.isChecked()) {
            mergeDrawableStates(drawableState, CHECKED_STATE_SET);
        }
        return drawableState;
    }
 
    @Override
    public void setShortcut(boolean showShortcut, char shortcutKey) {
    }
 
    @Override
    public void setIcon(Drawable icon) {
        if (icon != null) {
            Drawable.ConstantState state = icon.getConstantState();
            icon = DrawableCompat.wrap(state == null ? icon : state.newDrawable()).mutate();
            DrawableCompat.setTintList(icon, mIconTint);
        }
        mIcon.setImageDrawable(icon);
    }
 
    @Override
    public boolean prefersCondensedTitle() {
        return false;
    }
 
    @Override
    public boolean showsIcon() {
        return true;
    }
 
    public void setIconTintList(ColorStateList tint) {
        mIconTint = tint;
        if (mItemData != null) {
            // Update the icon so that the tint takes effect
            setIcon(mItemData.getIcon());
        }
    }
 
    public void setTextColor(ColorStateList color) {
        mSmallLabel.setTextColor(color);
        mLargeLabel.setTextColor(color);
    }
 
    public void setItemBackground(int background) {
        Drawable backgroundDrawable = background == 0
                ? null : ContextCompat.getDrawable(getContext(), background);
        ViewCompat.setBackground(this, backgroundDrawable);
    }
}
通过源码中BottomNavigationItemView的构造方法我们发现:它其实就是icon、smallTextView、largeTextView。实现很简单BottomNavigationView。


9、总结:

    BottomNavigationView由于提供方法很少,导致我们在实际开发中没有办法达到指定效果(设置icon大小、title大小、其他选中动画等),我们可以通过BottomNavigationView源码,来定制自己BottomNavigationView。

    话不多说,给大家推荐两个比较好点底部导航栏:

①、BottomNavigation:功能比较强大。

②、QQNaviView:高仿QQ底部导航效果

你可能感兴趣的:(View,android)