在使用BottomNavigationView时item数量为3时还正常啊,求教如何取消数量大于4的时候就会出现的那个奇葩的动画效果,实现像微信下方导航栏的样子,谢谢。
新建一个CustomBottomNavigationView继承BottomNavigationView,下面这三个方法是你要的,自己设置看下效果
mMenuView.enableAnimation(false);
mMenuView.enableShiftingMode(false);
mMenuView.enableItemShiftingMode(false);
public class CustomBottomNavigationView 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 setupWithViewPager
private ViewPager mViewPager;
private MyOnNavigationItemSelectedListener mMyOnNavigationItemSelectedListener;
private BottomNavigationViewExOnPageChangeListener mPageChangeListener;
private BottomNavigationMenuView mMenuView;
private BottomNavigationItemView[] mButtons;
// used for setupWithViewPager end
public CustomBottomNavigationView(Context context) {
super(context);
}
public CustomBottomNavigationView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomBottomNavigationView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
/**
* change the visibility of icon
*
* @param visibility
*/
public void setIconVisibility(boolean visibility) {
// 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");
if (null != mIcon) {
mIcon.post(new Runnable() {
@Override
public void run() {
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 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
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 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();
}
// 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);
} 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 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 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 mMenuView
// 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 mMenuView
BottomNavigationMenuView mMenuView = getBottomNavigationMenuView();
// 2. get mButtons
BottomNavigationItemView[] mButtons = getBottomNavigationItemViews();
// get mOnClickListener
View.OnClickListener mOnClickListener = getField(mMenuView.getClass(), mMenuView, "mOnClickListener");
// 3. call mOnClickListener.onClick();
mOnClickListener.onClick(mButtons[item]);
}
/**
* get OnNavigationItemSelectedListener
*
* @return
*/
public OnNavigationItemSelectedListener getOnNavigationItemSelectedListener() {
OnNavigationItemSelectedListener mListener = getField(getClass().getSuperclass(), this, "mListener");
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(getClass().getSuperclass(), this, "mMenuView");
return mMenuView;
}
/**
* get private mButtons in mMenuView
*
* @return
*/
public BottomNavigationItemView[] getBottomNavigationItemViews() {
if (null != mButtons)
return 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) {
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) {
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) {
BottomNavigationItemView mButtons = getBottomNavigationItemView(position);
TextView mLargeLabel = getField(BottomNavigationItemView.class, mButtons, "mLargeLabel");
return mLargeLabel;
}
/**
* return item count
*
* @return
*/
public int getItemCount() {
return getBottomNavigationItemViews().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 CustomBottomNavigationView} 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<CustomBottomNavigationView> mBnveRef;
public BottomNavigationViewExOnPageChangeListener(CustomBottomNavigationView 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 CustomBottomNavigationView bnve = mBnveRef.get();
if (null != bnve)
bnve.setCurrentItem(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, CustomBottomNavigationView 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;
}
}
}