AnroidUI 底部导航

BottomNavigationView从入门到强行改造,取消位移动画?和ViewPager绑定?添加Badge? 前言BottomNavigationView 这个官方控件出了几个月了,也有一些介绍该控件的文章,但我发现大部分博文只是做了简单的用法介绍,并未解决一些需求,比如:取消位移动画、和ViewPager一起使用、加入Badge。所以我又写了这么一篇博客。考虑到一些人可能没时间看到最后,我把改造的库地址放在最前面 BottomNavigationViewEx。基本用法1. 添加依赖compile 'com 阅读详情

支持小红点参考地址
BaseFragment参考地址

步骤 1. 在工程根目录的 build.gradle 最后添加如下代码:

allprojects {
    repositories {
        ...
        maven { url "https://jitpack.io" }
    }
}

步骤 2. 添加依赖

compile 'com.github.ittianyu:BottomNavigationViewEx:1.2.1'

步骤三:手动导入代码:

import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.Paint;
import android.graphics.Typeface;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.design.internal.BottomNavigationItemView;
import android.support.design.internal.BottomNavigationMenuView;
import android.support.design.widget.BottomNavigationView;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.util.SparseIntArray;
import android.util.TypedValue;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;

import java.lang.ref.WeakReference;
import java.lang.reflect.Field;

/**
 * Created by yu on 2016/11/10.
 */
public class BottomNavigationViewEx extends BottomNavigationView {
    // used for animation
    private int mShiftAmount;
    private float mScaleUpFactor;
    private float mScaleDownFactor;
    private boolean animationRecord;
    private float mLargeLabelSize;
    private float mSmallLabelSize;
    private boolean visibilityTextSizeRecord;
    private boolean visibilityHeightRecord;
    private int mItemHeight;
    // used for animation end

    // used for setupWithViewPager
    private ViewPager mViewPager;
    private MyOnNavigationItemSelectedListener mMyOnNavigationItemSelectedListener;
    private BottomNavigationViewExOnPageChangeListener mPageChangeListener;
    private BottomNavigationMenuView mMenuView;
    private BottomNavigationItemView[] mButtons;
    // used for setupWithViewPager end

    public BottomNavigationViewEx(Context context) {
        super(context);
    }

    public BottomNavigationViewEx(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public BottomNavigationViewEx(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    /**
     * change the visibility of icon
     *
     * @param visibility
     */
    public void setIconVisibility(boolean visibility) {
        /*
        1. get field in this class
        private final BottomNavigationMenuView mMenuView;

        2. get field in mButtons
        private BottomNavigationItemView[] mButtons;

        3. get mIcon in mButtons
        private ImageView mIcon

        4. set mIcon visibility gone

        5. change mItemHeight to only text size in mMenuView
         */
        // 1. get mMenuView
        final BottomNavigationMenuView mMenuView = getBottomNavigationMenuView();
        // 2. get mButtons
        BottomNavigationItemView[] mButtons = getBottomNavigationItemViews();
        // 3. get mIcon in mButtons
        for (BottomNavigationItemView button : mButtons) {
            ImageView mIcon = getField(button.getClass(), button, "mIcon");
            // 4. set mIcon visibility gone
            mIcon.setVisibility(visibility ? View.VISIBLE : View.INVISIBLE);
        }

        // 5. change mItemHeight to only text size in mMenuView
        if (!visibility) {
            // if not record mItemHeight
            if (!visibilityHeightRecord) {
                visibilityHeightRecord = true;
                mItemHeight = getItemHeight();
            }

            // change mItemHeight
            BottomNavigationItemView button = mButtons[0];
            if (null != button) {
                final ImageView mIcon = getField(button.getClass(), button, "mIcon");
//                System.out.println("mIcon.getMeasuredHeight():" + mIcon.getMeasuredHeight());
                if (null != mIcon) {
                    mIcon.post(new Runnable() {
                        @Override
                        public void run() {
//                            System.out.println("mIcon.getMeasuredHeight():" + mIcon.getMeasuredHeight());
                            setItemHeight(mItemHeight - mIcon.getMeasuredHeight());
                        }
                    });
                }
            }
        } else {
            // if not record the mItemHeight, we need do nothing.
            if (!visibilityHeightRecord)
                return;

            // restore it
            setItemHeight(mItemHeight);
        }

        mMenuView.updateMenuView();
    }

    /**
     * change the visibility of text
     *
     * @param visibility
     */
    public void setTextVisibility(boolean visibility) {
        /*
        1. get field in this class
        private final BottomNavigationMenuView mMenuView;

        2. get field in mButtons
        private BottomNavigationItemView[] mButtons;

        3. set text size in mButtons
        private final TextView mLargeLabel
        private final TextView mSmallLabel

        4. change mItemHeight to only icon size in mMenuView
         */
        // 1. get mMenuView
        BottomNavigationMenuView mMenuView = getBottomNavigationMenuView();
        // 2. get mButtons
        BottomNavigationItemView[] mButtons = getBottomNavigationItemViews();
        // 3. change field mShiftingMode value in mButtons
        for (BottomNavigationItemView button : mButtons) {
            TextView mLargeLabel = getField(button.getClass(), button, "mLargeLabel");
            TextView mSmallLabel = getField(button.getClass(), button, "mSmallLabel");

            if (!visibility) {
                // if not record the font size, record it
                if (!visibilityTextSizeRecord && !animationRecord) {
                    visibilityTextSizeRecord = true;
                    mLargeLabelSize = mLargeLabel.getTextSize();
                    mSmallLabelSize = mSmallLabel.getTextSize();
                }

                // if not visitable, set font size to 0
                mLargeLabel.setTextSize(TypedValue.COMPLEX_UNIT_PX, 0);
                mSmallLabel.setTextSize(TypedValue.COMPLEX_UNIT_PX, 0);

            } else {
                // if not record the font size, we need do nothing.
                if (!visibilityTextSizeRecord)
                    break;

                // restore it
                mLargeLabel.setTextSize(TypedValue.COMPLEX_UNIT_PX, mLargeLabelSize);
                mSmallLabel.setTextSize(TypedValue.COMPLEX_UNIT_PX, mSmallLabelSize);
            }
        }

        // 4 change mItemHeight to only icon size in mMenuView
        if (!visibility) {
            // if not record mItemHeight
            if (!visibilityHeightRecord) {
                visibilityHeightRecord = true;
                mItemHeight = getItemHeight();
            }

            // change mItemHeight to only icon size in mMenuView
            // private final int mItemHeight;

            // change mItemHeight
//            System.out.println("mLargeLabel.getMeasuredHeight():" + getFontHeight(mSmallLabelSize));
            setItemHeight(mItemHeight - getFontHeight(mSmallLabelSize));

        } else {
            // if not record the mItemHeight, we need do nothing.
            if (!visibilityHeightRecord)
                return;
            // restore mItemHeight
            setItemHeight(mItemHeight);
        }

        mMenuView.updateMenuView();
    }

    /**
     * get text height by font size
     *
     * @param fontSize
     * @return
     */
    private static int getFontHeight(float fontSize) {
        Paint paint = new Paint();
        paint.setTextSize(fontSize);
        Paint.FontMetrics fm = paint.getFontMetrics();
        return (int) Math.ceil(fm.descent - fm.top) + 2;
    }

    /**
     * enable or disable click item animation(text scale and icon move animation in no item shifting mode)
     *
     * @param enable It means the text won't scale and icon won't move when active it in no item shifting mode if false.
     */
    public void enableAnimation(boolean enable) {
        /*
        1. get field in this class
        private final BottomNavigationMenuView mMenuView;

        2. get field in mButtons
        private BottomNavigationItemView[] mButtons;

        3. chang mShiftAmount to 0 in mButtons
        private final int mShiftAmount

        change mScaleUpFactor and mScaleDownFactor to 1f in mButtons
        private final float mScaleUpFactor
        private final float mScaleDownFactor

        4. change label font size in mButtons
        private final TextView mLargeLabel
        private final TextView mSmallLabel
         */

        // 1. get mMenuView
        BottomNavigationMenuView mMenuView = getBottomNavigationMenuView();
        // 2. get mButtons
        BottomNavigationItemView[] mButtons = getBottomNavigationItemViews();
        // 3. change field mShiftingMode value in mButtons
        for (BottomNavigationItemView button : mButtons) {
            TextView mLargeLabel = getField(button.getClass(), button, "mLargeLabel");
            TextView mSmallLabel = getField(button.getClass(), button, "mSmallLabel");

            // if disable animation, need animationRecord the source value
            if (!enable) {
                if (!animationRecord) {
                    animationRecord = true;
                    mShiftAmount = getField(button.getClass(), button, "mShiftAmount");
                    mScaleUpFactor = getField(button.getClass(), button, "mScaleUpFactor");
                    mScaleDownFactor = getField(button.getClass(), button, "mScaleDownFactor");

                    mLargeLabelSize = mLargeLabel.getTextSize();
                    mSmallLabelSize = mSmallLabel.getTextSize();

//                    System.out.println("mShiftAmount:" + mShiftAmount + " mScaleUpFactor:"
//                            + mScaleUpFactor + " mScaleDownFactor:" + mScaleDownFactor
//                            + " mLargeLabel:" + mLargeLabelSize + " mSmallLabel:" + mSmallLabelSize);
                }
                // disable
                setField(button.getClass(), button, "mShiftAmount", 0);
                setField(button.getClass(), button, "mScaleUpFactor", 1);
                setField(button.getClass(), button, "mScaleDownFactor", 1);

                // let the mLargeLabel font size equal to mSmallLabel
                mLargeLabel.setTextSize(TypedValue.COMPLEX_UNIT_PX, mSmallLabelSize);

                // debug start
//                mLargeLabelSize = mLargeLabel.getTextSize();
//                System.out.println("mLargeLabel:" + mLargeLabelSize);
                // debug end

            } else {
                // haven't change the value. It means it was the first call this method. So nothing need to do.
                if (!animationRecord)
                    return;
                // enable animation
                setField(button.getClass(), button, "mShiftAmount", mShiftAmount);
                setField(button.getClass(), button, "mScaleUpFactor", mScaleUpFactor);
                setField(button.getClass(), button, "mScaleDownFactor", mScaleDownFactor);
                // restore
                mLargeLabel.setTextSize(TypedValue.COMPLEX_UNIT_PX, mLargeLabelSize);
            }
        }
        mMenuView.updateMenuView();
    }

    /**
     * enable the shifting mode for navigation
     *
     * @param enable It will has a shift animation if true. Otherwise all items are the same width.
     */
    public void enableShiftingMode(boolean enable) {
        /*
        1. get field in this class
        private final BottomNavigationMenuView mMenuView;

        2. change field mShiftingMode value in mMenuView
        private boolean mShiftingMode = true;
         */
        // 1. get mMenuView
        BottomNavigationMenuView mMenuView = getBottomNavigationMenuView();
        // 2. change field mShiftingMode value in mMenuView
        setField(mMenuView.getClass(), mMenuView, "mShiftingMode", enable);

        mMenuView.updateMenuView();
    }

    /**
     * enable the shifting mode for each item
     *
     * @param enable It will has a shift animation for item if true. Otherwise the item text always be shown.
     */
    public void enableItemShiftingMode(boolean enable) {
        /*
        1. get field in this class
        private final BottomNavigationMenuView mMenuView;

        2. get field in this mMenuView
        private BottomNavigationItemView[] mButtons;

        3. change field mShiftingMode value in mButtons
        private boolean mShiftingMode = true;
         */
        // 1. get mMenuView
        BottomNavigationMenuView mMenuView = getBottomNavigationMenuView();
        // 2. get mButtons
        BottomNavigationItemView[] mButtons = getBottomNavigationItemViews();
        // 3. change field mShiftingMode value in mButtons
        for (BottomNavigationItemView button : mButtons) {
            setField(button.getClass(), button, "mShiftingMode", enable);
        }
        mMenuView.updateMenuView();
    }

    /**
     * get the current checked item position
     *
     * @return index of item, start from 0.
     */
    public int getCurrentItem() {
        /*
        1. get field in this class
        private final BottomNavigationMenuView mMenuView;

        2. get field in mMenuView
        private BottomNavigationItemView[] mButtons;

        3. get menu and traverse it to get the checked one
         */

        // 1. get mMenuView
//        BottomNavigationMenuView mMenuView = getBottomNavigationMenuView();
        // 2. get mButtons
        BottomNavigationItemView[] mButtons = getBottomNavigationItemViews();
        // 3. get menu and traverse it to get the checked one
        Menu menu = getMenu();
        for (int i = 0; i < mButtons.length; i++) {
            if (menu.getItem(i).isChecked()) {
                return i;
            }
        }
        return 0;
    }

    /**
     * get menu item position in menu
     *
     * @param item
     * @return position if success, -1 otherwise
     */
    public int getMenuItemPosition(MenuItem item) {
        // get item id
        int itemId = item.getItemId();
        // get meunu
        Menu menu = getMenu();
        int size = menu.size();
        for (int i = 0; i < size; i++) {
            if (menu.getItem(i).getItemId() == itemId) {
                return i;
            }
        }
        return -1;
    }

    /**
     * set the current checked item
     *
     * @param item start from 0.
     */
    public void setCurrentItem(int item) {
        // check bounds
        if (item < 0 || item >= getMaxItemCount()) {
            throw new ArrayIndexOutOfBoundsException("item is out of bounds, we expected 0 - "
                    + (getMaxItemCount() - 1) + ". Actually " + item);
        }

        /*
        1. get field in this class
        private final BottomNavigationMenuView mMenuView;

        2. get field in mMenuView
        private BottomNavigationItemView[] mButtons;
        private final OnClickListener mOnClickListener;

        3. call mOnClickListener.onClick();
         */
        // 1. get mMenuView
        BottomNavigationMenuView mMenuView = getBottomNavigationMenuView();
        // 2. get mButtons
        BottomNavigationItemView[] mButtons = getBottomNavigationItemViews();
        // get mOnClickListener
        View.OnClickListener mOnClickListener = getField(mMenuView.getClass(), mMenuView, "mOnClickListener");

//        System.out.println("mMenuView:" + mMenuView + " mButtons:" + mButtons + " mOnClickListener" + mOnClickListener);
        // 3. call mOnClickListener.onClick();
        mOnClickListener.onClick(mButtons[item]);

    }

    /**
     * get OnNavigationItemSelectedListener
     *
     * @return
     */
    public OnNavigationItemSelectedListener getOnNavigationItemSelectedListener() {
        // private OnNavigationItemSelectedListener mListener;
        OnNavigationItemSelectedListener mListener = getField(BottomNavigationView.class, this, "mSelectedListener");
        return mListener;
    }

    @Override
    public void setOnNavigationItemSelectedListener(@Nullable OnNavigationItemSelectedListener listener) {
        // if not set up with view pager, the same with father
        if (null == mMyOnNavigationItemSelectedListener) {
            super.setOnNavigationItemSelectedListener(listener);
            return;
        }

        mMyOnNavigationItemSelectedListener.setOnNavigationItemSelectedListener(listener);
    }

    /**
     * get private mMenuView
     *
     * @return
     */
    private BottomNavigationMenuView getBottomNavigationMenuView() {
        if (null == mMenuView)
            mMenuView = getField(BottomNavigationView.class, this, "mMenuView");
        return mMenuView;
    }

    /**
     * get private mButtons in mMenuView
     *
     * @return
     */
    public BottomNavigationItemView[] getBottomNavigationItemViews() {
        if (null != mButtons)
            return mButtons;
        /*
         * 1 private final BottomNavigationMenuView mMenuView;
         * 2 private BottomNavigationItemView[] mButtons;
         */
        BottomNavigationMenuView mMenuView = getBottomNavigationMenuView();
        mButtons = getField(mMenuView.getClass(), mMenuView, "mButtons");
        return mButtons;
    }

    /**
     * get private mButton in mMenuView at position
     *
     * @param position
     * @return
     */
    public BottomNavigationItemView getBottomNavigationItemView(int position) {
        return getBottomNavigationItemViews()[position];
    }

    /**
     * get icon at position
     *
     * @param position
     * @return
     */
    public ImageView getIconAt(int position) {
        /*
         * 1 private final BottomNavigationMenuView mMenuView;
         * 2 private BottomNavigationItemView[] mButtons;
         * 3 private ImageView mIcon;
         */
        BottomNavigationItemView mButtons = getBottomNavigationItemView(position);
        ImageView mIcon = getField(BottomNavigationItemView.class, mButtons, "mIcon");
        return mIcon;
    }

    /**
     * get small label at position
     * Each item has tow label, one is large, another is small.
     *
     * @param position
     * @return
     */
    public TextView getSmallLabelAt(int position) {
        /*
         * 1 private final BottomNavigationMenuView mMenuView;
         * 2 private BottomNavigationItemView[] mButtons;
         * 3 private final TextView mSmallLabel;
         */
        BottomNavigationItemView mButtons = getBottomNavigationItemView(position);
        TextView mSmallLabel = getField(BottomNavigationItemView.class, mButtons, "mSmallLabel");
        return mSmallLabel;
    }

    /**
     * get large label at position
     * Each item has tow label, one is large, another is small.
     *
     * @param position
     * @return
     */
    public TextView getLargeLabelAt(int position) {
        /*
         * 1 private final BottomNavigationMenuView mMenuView;
         * 2 private BottomNavigationItemView[] mButtons;
         * 3 private final TextView mLargeLabel;
         */
        BottomNavigationItemView mButtons = getBottomNavigationItemView(position);
        TextView mLargeLabel = getField(BottomNavigationItemView.class, mButtons, "mLargeLabel");
        return mLargeLabel;
    }

    /**
     * return item count
     *
     * @return
     */
    public int getItemCount() {
        BottomNavigationItemView[] bottomNavigationItemViews = getBottomNavigationItemViews();
        if (null == bottomNavigationItemViews)
            return 0;
        return bottomNavigationItemViews.length;
    }

    /**
     * set all item small TextView size
     * Each item has tow label, one is large, another is small.
     * Small one will be shown when item state is normal
     * Large one will be shown when item checked.
     *
     * @param sp
     */
    public void setSmallTextSize(float sp) {
        int count = getItemCount();
        for (int i = 0; i < count; i++) {
            getSmallLabelAt(i).setTextSize(sp);
        }
        mMenuView.updateMenuView();
    }

    /**
     * set all item large TextView size
     * Each item has tow label, one is large, another is small.
     * Small one will be shown when item state is normal.
     * Large one will be shown when item checked.
     *
     * @param sp
     */
    public void setLargeTextSize(float sp) {
        int count = getItemCount();
        for (int i = 0; i < count; i++) {
            getLargeLabelAt(i).setTextSize(sp);
        }
        mMenuView.updateMenuView();
    }

    /**
     * set all item large and small TextView size
     * Each item has tow label, one is large, another is small.
     * Small one will be shown when item state is normal
     * Large one will be shown when item checked.
     *
     * @param sp
     */
    public void setTextSize(float sp) {
        setLargeTextSize(sp);
        setSmallTextSize(sp);
    }

    /**
     * set item ImageView size which at position
     *
     * @param position position start from 0
     * @param width    in dp
     * @param height   in dp
     */
    public void setIconSizeAt(int position, float width, float height) {
        ImageView icon = getIconAt(position);
        // update size
        ViewGroup.LayoutParams layoutParams = icon.getLayoutParams();
        layoutParams.width = dp2px(getContext(), width);
        layoutParams.height = dp2px(getContext(), height);
        icon.setLayoutParams(layoutParams);

        mMenuView.updateMenuView();
    }

    /**
     * set all item ImageView size
     *
     * @param width  in dp
     * @param height in dp
     */
    public void setIconSize(float width, float height) {
        int count = getItemCount();
        for (int i = 0; i < count; i++) {
            setIconSizeAt(i, width, height);
        }
    }

    /**
     * set menu item height
     *
     * @param height in px
     */
    public void setItemHeight(int height) {
        // 1. get mMenuView
        final BottomNavigationMenuView mMenuView = getBottomNavigationMenuView();
        // 2. set private final int mItemHeight in mMenuView
        setField(mMenuView.getClass(), mMenuView, "mItemHeight", height);

        mMenuView.updateMenuView();
    }

    /**
     * get menu item height
     *
     * @return in px
     */
    public int getItemHeight() {
        // 1. get mMenuView
        final BottomNavigationMenuView mMenuView = getBottomNavigationMenuView();
        // 2. get private final int mItemHeight in mMenuView
        return getField(mMenuView.getClass(), mMenuView, "mItemHeight");
    }

    /**
     * dp to px
     *
     * @param context
     * @param dpValue dp
     * @return px
     */
    public static int dp2px(Context context, float dpValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f);
    }

    /**
     * set Typeface for all item TextView
     *
     * @attr ref android.R.styleable#TextView_typeface
     * @attr ref android.R.styleable#TextView_textStyle
     */
    public void setTypeface(Typeface typeface, int style) {
        int count = getItemCount();
        for (int i = 0; i < count; i++) {
            getLargeLabelAt(i).setTypeface(typeface, style);
            getSmallLabelAt(i).setTypeface(typeface, style);
        }
        mMenuView.updateMenuView();
    }

    /**
     * set Typeface for all item TextView
     *
     * @attr ref android.R.styleable#TextView_typeface
     */
    public void setTypeface(Typeface typeface) {
        int count = getItemCount();
        for (int i = 0; i < count; i++) {
            getLargeLabelAt(i).setTypeface(typeface);
            getSmallLabelAt(i).setTypeface(typeface);
        }
        mMenuView.updateMenuView();
    }

    /**
     * get private filed in this specific object
     *
     * @param targetClass
     * @param instance    the filed owner
     * @param fieldName
     * @param <T>
     * @return field if success, null otherwise.
     */
    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;
    }

    /**
     * change the field value
     *
     * @param targetClass
     * @param instance    the filed owner
     * @param fieldName
     * @param value
     */
    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();
        }
    }

    /**
     * This method will link the given ViewPager and this BottomNavigationViewEx together so that
     * changes in one are automatically reflected in the other. This includes scroll state changes
     * and clicks.
     *
     * @param viewPager
     */
    public void setupWithViewPager(@Nullable final ViewPager viewPager) {
        setupWithViewPager(viewPager, false);
    }

    /**
     * This method will link the given ViewPager and this BottomNavigationViewEx together so that
     * changes in one are automatically reflected in the other. This includes scroll state changes
     * and clicks.
     *
     * @param viewPager
     * @param smoothScroll whether ViewPager changed with smooth scroll animation
     */
    public void setupWithViewPager(@Nullable final ViewPager viewPager, boolean smoothScroll) {
        if (mViewPager != null) {
            // If we've already been setup with a ViewPager, remove us from it
            if (mPageChangeListener != null) {
                mViewPager.removeOnPageChangeListener(mPageChangeListener);
            }
        }

        if (null == viewPager) {
            mViewPager = null;
            super.setOnNavigationItemSelectedListener(null);
            return;
        }

        mViewPager = viewPager;

        // Add our custom OnPageChangeListener to the ViewPager
        if (mPageChangeListener == null) {
            mPageChangeListener = new BottomNavigationViewExOnPageChangeListener(this);
        }
        viewPager.addOnPageChangeListener(mPageChangeListener);

        // Now we'll add a navigation item selected listener to set ViewPager's current item
        OnNavigationItemSelectedListener listener = getOnNavigationItemSelectedListener();
        mMyOnNavigationItemSelectedListener = new MyOnNavigationItemSelectedListener(viewPager, this, smoothScroll, listener);
        super.setOnNavigationItemSelectedListener(mMyOnNavigationItemSelectedListener);
    }

    /**
     * A {@link ViewPager.OnPageChangeListener} class which contains the
     * necessary calls back to the provided {@link BottomNavigationViewEx} so that the tab position is
     * kept in sync.
     * <p>
     * <p>This class stores the provided BottomNavigationViewEx weakly, meaning that you can use
     * {@link ViewPager#addOnPageChangeListener(ViewPager.OnPageChangeListener)
     * addOnPageChangeListener(OnPageChangeListener)} without removing the listener and
     * not cause a leak.
     */
    private static class BottomNavigationViewExOnPageChangeListener implements ViewPager.OnPageChangeListener {
        private final WeakReference<BottomNavigationViewEx> mBnveRef;

        public BottomNavigationViewExOnPageChangeListener(BottomNavigationViewEx bnve) {
            mBnveRef = new WeakReference<>(bnve);
        }

        @Override
        public void onPageScrollStateChanged(final int state) {
        }

        @Override
        public void onPageScrolled(final int position, final float positionOffset,
                                   final int positionOffsetPixels) {
        }

        @Override
        public void onPageSelected(final int position) {
            final BottomNavigationViewEx bnve = mBnveRef.get();
            if (null != bnve)
                bnve.setCurrentItem(position);
//            Log.d("onPageSelected", "--------- position " + position + " ------------");
        }
    }

    /**
     * Decorate OnNavigationItemSelectedListener for setupWithViewPager
     */
    private static class MyOnNavigationItemSelectedListener implements OnNavigationItemSelectedListener {
        private OnNavigationItemSelectedListener listener;
        private final WeakReference<ViewPager> viewPagerRef;
        private boolean smoothScroll;
        private SparseIntArray items;// used for change ViewPager selected item
        private int previousPosition = -1;


        MyOnNavigationItemSelectedListener(ViewPager viewPager, BottomNavigationViewEx bnve, boolean smoothScroll, OnNavigationItemSelectedListener listener) {
            this.viewPagerRef = new WeakReference<>(viewPager);
            this.listener = listener;
            this.smoothScroll = smoothScroll;

            // create items
            Menu menu = bnve.getMenu();
            int size = menu.size();
            items = new SparseIntArray(size);
            for (int i = 0; i < size; i++) {
                int itemId = menu.getItem(i).getItemId();
                items.put(itemId, i);
            }
        }

        public void setOnNavigationItemSelectedListener(OnNavigationItemSelectedListener listener) {
            this.listener = listener;
        }

        @Override
        public boolean onNavigationItemSelected(@NonNull MenuItem item) {
            int position = items.get(item.getItemId());
            // only set item when item changed
            if (previousPosition == position) {
                return true;
            }

            // user listener
            if (null != listener) {
                boolean bool = listener.onNavigationItemSelected(item);
                // if the selected is invalid, no need change the view pager
                if (!bool)
                    return false;
            }

            // change view pager
            ViewPager viewPager = viewPagerRef.get();
            if (null == viewPager)
                return false;

            viewPager.setCurrentItem(items.get(item.getItemId()), smoothScroll);

            // update previous position
            previousPosition = position;

            return true;
        }

    }

    public void enableShiftingMode(int position, boolean enable) {
        getBottomNavigationItemView(position).setShiftingMode(enable);
    }

    public void setItemBackground(int position, int background) {
        getBottomNavigationItemView(position).setItemBackground(background);
    }

    public void setIconTintList(int position, ColorStateList tint) {
        getBottomNavigationItemView(position).setIconTintList(tint);
    }
    public void setTextTintList(int position, ColorStateList tint) {
        getBottomNavigationItemView(position).setTextColor(tint);
    }

    /**
     * set margin top for all icons
     * @param marginTop in px
     */
    public void setIconsMarginTop(int marginTop) {
        for (int i = 0; i < getItemCount(); i++) {
            setIconMarginTop(i, marginTop);
        }
    }

    /**
     * set margin top for icon
     * @param position
     * @param marginTop in px
     */
    public void setIconMarginTop(int position, int marginTop) {
        /*
        1. BottomNavigationItemView
        2. private final int mDefaultMargin;
         */
        BottomNavigationItemView itemView = getBottomNavigationItemView(position);
        setField(BottomNavigationItemView.class, itemView, "mDefaultMargin", marginTop);
        mMenuView.updateMenuView();
    }

}

要求Sdk 版本

compileSdkVersion >= 25

开始使用

对应的页面写布局

<?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:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.example.administrator.bottomnavigationview.MainActivity">

 <com.example.administrator.bottomnavigationview.NoSlidingViewPaper
        android:id="@+id/content"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1">
    </com.example.administrator.bottomnavigationview.NoSlidingViewPaper>

    <com.example.administrator.bottomnavigationview.BottomNavigationViewEx
        android:id="@+id/bnve"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="#000000"
        app:itemIconTint="@color/icon_color"
        app:itemTextColor="@color/icon_color"
        app:menu="@menu/bottem_menu" />

</LinearLayout>

需要说明一下:
属性:

  android:background="#000000"  底部导航的背景颜色      
  app:itemIconTint="@color/icon_color"图片的颜色--是个选择器  
  app:itemTextColor="@color/icon_color"文字的颜色--是个选择器
  app:menu="@menu/bottem_menu" 底部导航的图片和文字的设置

颜色选择器创建

在res下创建color文件夹—创建icon_color.xml如下

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:color="#ff0000" android:state_checked="true"/>
    <item android:color="#ff0000" android:state_pressed="true"/>
    <item android:color="#fff000"/>
</selector>

menu的创建 res创建menu文件夹—创建bottem_menu.xml

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:id="@+id/item_news"
        android:icon="@mipmap/ic_launcher"
        android:title="新闻" />
    <item
        android:id="@+id/item_lib"
        android:icon="@mipmap/ic_launcher"
        android:title="图书" />
    <item
        android:id="@+id/item_find"
        android:icon="@mipmap/ic_launcher"
        android:title="发现" />
    <item
        android:id="@+id/item_more"
        android:icon="@mipmap/ic_launcher"
        android:title="更多" />
</menu>

懒加载的ViewPager

public class NoSlidingViewPaper extends ViewPager {
    public NoSlidingViewPaper(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    public NoSlidingViewPaper(Context context) {
        super(context);
    }
    /*
     * 表示把滑动事件传递给下一个view
     */
    @Override
    public boolean onInterceptTouchEvent(MotionEvent arg0) {
        return false;
    }
    /*
     * 可以啥都不做
     */
    public boolean onTouchEvent(MotionEvent arg0) {
        return false;
    }
}

在对应的Activity中初始化控件和设置一些必要的属性

  mViewPager = (NoSlidingViewPaper) findViewById(R.id.content);
        bnve = (BottomNavigationViewEx) findViewById(R.id.bnve);
        //关闭所有动画
        bnve.enableAnimation(false);
        bnve.enableShiftingMode(false);
        bnve.enableItemShiftingMode(false);
        //设置默认显示的Fragment
        bnve.setCurrentItem(1);

        final ArrayList<Fragment> fgLists = new ArrayList<>(4);
        fgLists.add(new OneFragment());
        fgLists.add(new TwoFragment());
        fgLists.add(new ThereFragment());
        fgLists.add(new FourFragment());

        FragmentPagerAdapter mAdapter = new FragmentPagerAdapter(getSupportFragmentManager()) {
            @Override
            public Fragment getItem(int position) {
                return fgLists.get(position);
            }

            @Override
            public int getCount() {
                return fgLists.size();
            }
        };
        mViewPager.setAdapter(mAdapter);
        mViewPager.setOffscreenPageLimit(1); //预加载剩下两页

        // 绑定ViewPager
        bnve.setupWithViewPager(mViewPager);

效果如下:

这里写图片描述

但是,一般都是用作底部导航,如果每次都加载两页,请求三次数据,有点不合理,所以我们要写一个BaseFragment:

/**
 * Created by Administrator on 2017/7/9/009.
 */

public  abstract class BaseFragement extends Fragment {

    protected View mView;
    protected boolean isViewInitiated; //当前页面是否初始化
    protected boolean isVisibleToUser; //当前页面是否显示
    protected boolean isDataRequested; //是否已经请求了数据
    protected Context mContext;

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {

        mContext = getContext();
        mView = inflater.inflate(getLayoutId(), null);
        isViewInitiated = true;
        initView(mView);
        initListener();
        prepareGetData();
        return mView;
    }

    /*初始化页面布局和数据*/
    protected abstract void initView(View view);
    /*布局*/
    public abstract int getLayoutId();
    /*服务器获取数据*/
    protected abstract void getDataFromServer();

    protected abstract void initListener();
    /**
     * 当前页面是否展示
     * @param isVisibleToUser 显示为true, 不显示为false
     */
    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        this.isVisibleToUser = isVisibleToUser;
        prepareGetData();


    }

    /**
     * 如果只想第一次进入该页面请求数据,return prepareGetData(false)
     * 如果想每次进入该页面就请求数据,return prepareGetData(true)
     * @return
     */
    public boolean prepareGetData(){
        return prepareGetData(false);
    }

    /**
     * 判断是否从服务器器获取数据
     * @param isforceUpdate 强制更新的标记
     * @return
     */
    protected boolean prepareGetData(boolean isforceUpdate) {
        if(isVisibleToUser && isViewInitiated && (!isDataRequested || isforceUpdate)){
            /*从服务器获取数据*/
            getDataFromServer();
            isDataRequested = true;
            return true;
        }
        return false;
    }
}

这个时候,页面只有再第一次加载的时候才请求网络,所有的Fragment都继承BaseFragment即可,如果想要当前页面每次出现再用户面前的时候都加载,只需要重写prepareGetData方法即可。

**最后,
需要改变预加载数量,否则,数据会不显示**

小红点直接复制一个方法即可,并且支持拖拽消失

bottomnavigationviewex-android-binding:用于Ittianyu BottomNavigationViewEx的Xamarin.Android绑定库 Xamarin Android绑定库 Xamarin.Android绑定库 Install-Package Naxam.Ittianyu.BottomNavExtension 产品特点 方法 描述 enableAnimation 启用或禁用点击项动画(无项目转换模式下的文本比例和图标移动动画)。 默认为true。 enableItemShiftingMode 为每个项目启用切换模式。 如果为true,则将具有项目的平移动画。 否则,将始终显示项目文本。 当项目数> 3时,默认为true。 enableShiftingMode 启用切换模式进行导航。 如果为true,它将具有平移动画 立即下载

相关推荐

Ubuntu 24.04中文输入法终极指南:从安装到疑难排解

本文是Ubuntu 24.04中文输入法的完整指南,详细介绍了如何通过图形界面或命令行安装和配置Intelligent Pinyin输入法。内容涵盖IBus框架的运作原理、输入法快捷键调校、模糊音设置,并重点解决了Qt应用无法输入中文的疑难问题,最后提供了Fcitx5备选方案及常见故障的排解方法。

weixin_29294975的博客 1245

一个增强 BottomNavigationView 的安卓库。

一个增强 BottomNavigationView 的安卓库。

未来很长,但我会努力的走下去。 1105

Android定位权限全解析:从基础配置到后台持续定位的完整解决方案

本文全面解析Android定位权限体系,从基础配置到后台持续定位的完整解决方案。涵盖LocationManager和FusedLocationProviderClient等主流API的对比选型,详细讲解动态权限申请、精确定位与模糊定位的智能切换,以及Android 10+的后台定位适配策略。帮助开发者构建兼顾精准度与电量友好的定位方案,适用于导航、运动轨迹记录等多种场景。

weixin_29310605的博客 611

【亲测免费】 BottomNavigationViewEx 使用教程

BottomNavigationViewEx 是一个增强版的 BottomNavigationView 库,旨在提供更多的自定义选项和功能,以满足开发者对底部导航栏的更高需求。该库基于 Android 官方的 BottomNavigationView,通过添加额外的方法和属性,使得导航栏的使用更加灵活和强大。 ## 项目快速启动 ### 添加依赖 首先,在你的 `build.gradle` 文...

gitblog_00548的博客 1014

Springboot+Vue 实现鸿蒙商城管理系统开发实践

通过使用 Springboot 和 Vue 技术栈,我们成功实现了鸿蒙商城管理系统的开发。系统具备用户管理、商品管理、订单管理、库存管理和统计分析等功能,支持 Web 端和鸿蒙端的访问和操作。在开发过程中,我们充分利用了 Springboot 的快速开发优势和 Vue 的组件化开发特点,提高了开发效率和系统的可维护性。同时,通过与鸿蒙设备的集成,实现了多设备之间的数据交互和协同工作,提升了用户体验。

ZXJ_SXT的博客 1324

LAMMPS GCMC模拟保姆级教程:从fix gcmc命令到吸附等温线实战

本文提供LAMMPS GCMC模拟的详细教程,从fix gcmc命令设置到吸附等温线实战,涵盖巨正则蒙特卡洛模拟的全流程。通过MOF-5吸附甲烷的案例,指导用户构建模拟体系、转换化学势与压力、定义GCMC区域,并绘制出版级吸附等温线,助力材料科学研究。

weixin_33724046的博客 604

一个增强BottomNavigationView的安卓库。

官方文档 实现: 第一步:添加依赖 implementation 'com.github.ittianyu:BottomNavigationViewEx:2.0.2' implementation "com.android.support:design:28.0.0" 第二步:布局文件: <?xml version="1.0" encoding="utf-8"?&gt...

刘鑫的博客 834

windows10家庭版输入loadrunner注册码时报“License security violation. Operation isnot allowed”的解决办法

有可能出现2个错误第一个可能是第二个可能是“License security violation. Operation isnot allowed”ok,在此我在百度找了很多方法,大部分的方法都是删除注册表,但我根本就找不到这个注册表,而后又是管理中心找本地用户和组,但我依旧找不到这个玩意,然后可能是windows10家庭版本来就没有这个。再然后又以管理员身份去运行填写,好的,依旧报错,依旧未解决...

qq_30990097的博客 2558

Fragment+BottomNavigationViewEx底部导航栏切换页面的实现

在项目中很多时候需要用到底部导航栏(顶部导航栏)来切换页面,其中的具体实现步骤如下。 1.创建MainActivity class MainActivity : BaseActivity() { private val helper = OrderMainHelper(this) private val items by lazy { helper.item...

mlsnatalie的博客 4020

WCK Operation (WCK 操作)

LPDDR6 SDRAM采用双时钟系统(WCK和CK),其中WCK频率是CK的两倍。为确保数据正确传输,需通过WCK2CK同步操作使两种时钟对齐。LPDDR6支持两种同步模式:Auto-sync off模式和WCK Always on模式,可通过MR22寄存器选择。同步操作由特定命令(Read/Write/MRR等)触发,需在满足特定时序参数(如tWCKENL、tWCKPRE等)后完成。同步前必须完成WCK2CK训练,且不同操作(读取/写入/CAS)有各自的时序要求。该机制确保数据在正确时钟相位下传输,是L

李家小院 650

AutoSAR配置与实践(深入篇)5.4 OS原理(下 )- 内存保护(MPU)

ECU可能由内部模块、第三方模块等各模块集成的。需求对每个模块所定义的安全等级可能有差异,需要考虑不同安全等级的模块和代码间减少相互干扰,防止程序错误传播。可以理解为包含Task、Alarm、Counter、Schedule table、ISRs、Event等基本对象的容器,用于隔离Task/Alarm等,进行内存保护。

nihaoljq2010的博客 3536

为什么选择roberta-large-sst2?对比BERT、GPT的情感分析性能评测

在当今人工智能快速发展的时代,情感分析已成为自然语言处理(NLP)领域的重要应用。面对众多预训练模型,如何选择最适合情感分析任务的模型成为开发者面临的关键问题。本文将深入评测roberta-large-sst2模型,并与BERT、GPT等主流模型进行全方位对比,为您提供选择情感分析模型的完整指南。 ## 🔍 情感分析模型选择的重要性 情感分析是文本分类的核心任务之一,广泛应用于社交媒体监控、

gitblog_00014的博客 567

RV1126 ROS2环境交叉编译及部署(基于官方Docker)

我的所有基于`RV1126`的机器人项目都是在`Ubuntu22.04`下进行开发的,在`Ubuntu18.04 Docker`容器中实现`RV1126`的`SDK`编译,详细可参考我之前的博客[《Ubuntu20.04/22.04下Docker方案实现多平台SDK编译》第2章](https://blog.csdn.net/w1820020635/article/details/147342689),`RV1126`的`ROS1`部署没什么难度,可以参考我之前的博客,这里记录了`ROS2`交叉编译及问题解决

w1820020635的博客 1143

Android 底部导航栏结合FloatingActionButton

无图言吊先! 1.依赖 compile 'com.github.ittianyu:BottomNavigationViewEx:1.2.4' // badge 加不加小红点 implementation 'q.rorbin:badgeview:1.1.0' 2.menu menu_navigation_with_view_pager.xml &lt;?x...

FlyPig_Vip的博客 1455

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

Android常用开源库种类大全

## 1.基本控件 ### [](https://www.yundashi168.com/articles/2018/09/12/1536725366888.html#11textview)1.1.TextView * [ScrollNumber](https://github.com/a-voyager/ScrollNumber) * [ReadMoreTextView](https...

weixin_43901866的博客 1万+

锂电池充电器电路设计 里面有电路

锂电池充电器电路设计 里面有电路 3.7V升压到5V电路设计。

BottomNavigationView底部导航

BottomNavigationView很早就在Material Design中出现了,但是知道support library &gt;= 25,官方才增加了此控件。即必须targetSdkVersion &gt;= 25时,才可以引入BottomNavigationView组件兼容包。 此控件基本使用示例如下: gradle文件中添加依赖包 implementation ‘com.and...

sunshine_a70的博客 1010

SAP B1中文培訓教材_SDK

公司内部培训资料,对开发工具 ,DIAPI, UIAPI,Add-ON有详细解说

【安卓小笔记】BottomNavigationView和ViewPager的使用

BottomNavigationView 很早之前就在 Material Design 中出现了,但是直到 Android Support Library 25 中才增加了 BottomNavigationView 控件。 BottomNavigationView和ViewPager一起使用是很常见的。所以简单的一起学习一下。1.在 build.gradle 文件中增加依赖: compile 'c

Paul的博客 1484

Android底部导航BottomNavigationView

1.BottomNavigationVIew,它是AndroidSupport Library 25.0.0 版本中,新增加了一个API,底部导航视图。 支持库:implementation 'com.android.support:design:28.0.0' 2.看布局文件: <android.support.design.widget.BottomNavigationView...

mulanlong的博客 1085

Android 底部导航栏 中间凸起

andrid 中有一种常见底部导航栏中间凸起样式,如图: 中间凸起样式常见的实现方式: 1.借助父布局的clipchildren属性,设置为false,即允许子view超出父view的限制 2.直接在原来view上覆盖view,拦截底层(原来)view的事件。 因项目中中使用了BottomNavigationViewEx 作为底部导航栏,本文主要探讨第二种实现方式。Bot

薛定谔的猫 2万+

BottomNavigationViewEx 项目常见问题解决方案

BottomNavigationViewEx 是一个增强版的 BottomNavigationView 安卓库,旨在提供更多的自定义功能和更好的用户体验。该项目的主要编程语言是 Java,适用于 Android 开发。 ## 新手使用注意事项及解决方案 ### 1. 依赖库导入问题 **问题描述**:新手在导入 BottomNavigationViewEx 库时,可能会遇到依赖库无法正确导入...

gitblog_00805的博客 1153

【亲测免费】 探秘BottomNavigationViewEx:打造灵活多变的底部导航栏

当你正在寻找一个能够满足你个性化需求的底部导航栏组件时,BottomNavigationViewEx无疑是一个值得深入研究的选择。作为一个增强版的Android库,它提供了更多的定制选项和强大的功能,使得你在设计用户界面时能拥有更大的自由度。 ## 项目介绍 BottomNavigationViewEx是基于原生Android Support Library的BottomNavigationV...

gitblog_01048的博客 717

BottomNavigationViewEx 项目推荐

BottomNavigationViewEx 是一个增强型的 Android 库,旨在扩展 Google 的 BottomNavigationView 功能。该项目主要使用 Java 语言编写,适用于 Android 开发。通过该库,开发者可以更灵活地定制 BottomNavigationView,满足各种复杂的 UI 需求。 ## 项目核心功能 BottomNavigationViewEx ...

gitblog_00719的博客 514

关于BottomNavigationViewEx和FloatingActionButton层级问题

在使用BottomNavigationViewEx结合FloatingActionButton做底部导航栏中部突起的时候, 发现FloatingActionButton的elevation属性至少得设置8dp以上才能浮于BottomNavigationViewEx上方,8dp是临界点,无阴影效果。正好处在BottomNavigationViewEx视图上。按照RelativeLayout越靠后的子...

qq_28110303的博客 1756

Android 底部导航栏 开源库,安卓app开发入门书籍推荐

总而言之,Android开发行业变化太快,作为技术人员就要保持终生学习的态度,让学习力成为核心竞争力,所谓“活到老学到老”只有不断的学习,不断的提升自己,才能跟紧行业的步伐,才能不被时代所淘汰。在这里我分享一份自己收录整理上述技术体系图相关的几十套腾讯、头条、阿里、美团等公司20年的面试题,把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分。

2301_77110865的博客 336

LLM Compressor v0.10.0.2使用指南:AMD Llama-3.1-8B-Instruct量化全过程详解

想要在AMD CPU上高效运行大型语言模型吗?LLM Compressor v0.10.0.2为您提供了终极解决方案!本文将详细介绍如何使用这个强大的量化工具,将Llama-3.1-8B-Instruct模型压缩为4位权重,在保持性能的同时大幅降低内存占用。无论是AI开发者还是企业用户,都能通过这份完整指南快速上手。 ## 什么是LLM Compressor?为什么需要量化? LLM Comp

gitblog_00083的博客 712
上一篇: Android数据库GreenDao2.1使用和缺陷
下一篇: Android UI 小红点 可拖拽删除
flysweetegg
博客等级 码龄10年 4粉丝 20原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值