BottomNavigationView 配合ViewPager、Fragment当做底部(顶部)菜单栏的使用想必大家应该不会陌生。
先看一下效果图:
底部按钮切换的动画是不是有点烦人。。
不要怕,我们接下来就进击版一下。去各种动画,各种定制,随心所欲,为所欲为。
先给猴急的童鞋们贴一波代码,一键傻瓜式配置。
/**
*
* BottomNavigationView的加强版
*
* 禁止所有动画
* xxx.enableAnimation(false); 去掉切换动画
* xxx.enableShiftingMode(false); 去掉位移动画
* xxx.enableItemShiftingMode(false); 去掉放大动画
* xxx.setIconVisibility(false); 隐藏图标
* xxx.setTextVisibility(false); 隐藏文字
* 绑定ViewPager
* xxx.setupWithViewPager(ViewPage)
*/
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() {
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
* @return field if success, null otherwise.
*/
private 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.
*
*
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 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 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();
}
}
贴完代码,我们再来看看具体需求应对
看到这,有的童鞋肯定又嘀咕了,妈卖批哟,看的劳资眼窝窝疼。
其实上面这么多效果,总结下来无非就几行代码。
mBottomNavigationViewEx.enableAnimation(false); //去掉切换动画
mBottomNavigationViewEx xxx.enableShiftingMode(false); //去掉位移动画
mBottomNavigationViewEx xxx.enableItemShiftingMode(false); //去掉放大动画
mBottomNavigationViewEx xxx.setIconVisibility(false); //隐藏图标
mBottomNavigationViewEx xxx.setTextVisibility(false); //隐藏文字
//绑定ViewPager
mBottomNavigationViewEx xxx.setupWithViewPager(mViewPage)
//底部菜单栏图标字体点击颜色变化在这里修改
int[][] states = new int[][]{
new int[]{-android.R.attr.state_checked},
new int[]{android.R.attr.state_checked}
};
int[] colors = new int[]{getResources().getColor(R.color.colorPrimary),
getResources().getColor(R.color.colorPrimary)
};
ColorStateList csl = new ColorStateList(states, colors);
mBottomNavigationView.setItemTextColor(csl);
mBottomNavigationView.setItemIconTintList(csl);
切换图标变换样子的效果,只需要在Menu的item中的选择icon时使用图片选择器
//图标选择器
如果要实现中间加号,或者独立的图标效果(注意:这种效果不建议搭配ViewPager切换)
//然后在布局文件中加入单独的图标,覆盖到BottomNavigationViewEx
添加消息红点的效果。
首先,在库中加入Badgeview的依赖
compile 'q.rorbin:badgeview:1.1.0'
// add badge
addBadgeAt(2, 1);
private Badge addBadgeAt(int position, int number) {
// add badge
return new QBadgeView(this)
.setBadgeNumber(number)
.setGravityOffset(12, 2, true)
.bindTarget(mBottomNavigationItemView.getBottomNavigationItemView(position))
.setOnDragStateChangedListener(new Badge.OnDragStateChangedListener() {
@Override
public void onDragStateChanged(int dragState, Badge badge, View targetView) {
if (Badge.OnDragStateChangedListener.STATE_SUCCEED == dragState){
}
}
});
}
methods | description |
---|---|
enableAnimation | 开启或关闭点击动画(文字放大效果和图片移动效果)。 默认为 true. |
enableItemShiftingMode | 开始或关闭子菜单位移模式。 如果为 true,除了当前选中项,其他项的文本将会隐藏。 当菜单数大于3时,默认为 true。 |
getBottomNavigationItemView | 获取位于 position 的私有成员变量 mButton。 |
getBottomNavigationItemViews | 获取私有成员变量 mButtons。 |
getCurrentItem | 获取当前选中项的索引。 |
getIconAt | 获取位于 position 的图片。 |
getItemCount | 获取子项个数。 |
getItemHeight | 获取菜单高度。 |
getLargeLabelAt | 获取位于 position 的大标签. 每个子项包含两个标签,一个大的,一个小的。 |
getSmallLabelAt | 获取位于 position 的小标签. 每个子项包含两个标签,一个大的,一个小的。 |
getMenuItemPosition | 获取子菜单的索引。如果找不到,返回 -1。 |
getOnNavigationItemSelectedListener | 获取 OnNavigationItemSelectedListener。 |
setCurrentItem | 设置当前选中项。 |
setIconMarginTop | 设置 icon 的 MarginTop,用于调节图标垂直位置。 |
setIconSize | 设置所有的子项图标大小。 |
setIconSizeAt | 设置位于 position 的图标的大小。 |
setIconsMarginTop | 设置所有 icon 的 MarginTop,用于调节图标垂直位置。 |
setIconTintList | 设置图片的渲染颜色列表(Selector) |
setIconVisibility | 设置图片可见性。 |
setItemBackground | 设置子项的背景。 |
setItemHeight | 设置子项高度。 |
setLargeTextSize | 设置所有子项的大标签文本大小。每个子项有两个标签,一个大的,一个小的。当子项未选中时,显示小标签;选中时,显示大标签。 |
setSmallTextSize | 设置所有子项的小标签文本大小。每个子项有两个标签,一个大的,一个小的。当子项未选中时,显示小标签;选中时,显示大标签。 |
setTextSize | 设置所有子项的大和小标签文本大小。 |
setTextTintList | 设置子项 TextView 的颜色。 |
setTextVisibility | 设置文本可见性。 |
setTypeface | 设置所有子项的 TextView 字体 |
setupWithViewPager | 和 ViewPager 绑定,当 任何一个选中项改变时,都会自动改变另一项。 |
生命不止,进击不息。
接下来我们看一下在CoordinatorLayout布局下的滑动隐藏效果
import android.content.Context;
import android.support.design.widget.AppBarLayout;
import android.support.design.widget.CoordinatorLayout;
import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.view.View;
/**
*
* 自定义BottomNavigationView底部菜单栏上滑隐藏效果(需要配合ToolBar使用)
*
*/
public class BottomNavigationViewBehavior extends CoordinatorLayout.Behavior {
public BottomNavigationViewBehavior() {
}
public BottomNavigationViewBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onLayoutChild(CoordinatorLayout parent, View child, int layoutDirection) {
((CoordinatorLayout.LayoutParams) child.getLayoutParams()).topMargin = parent.getMeasuredHeight() - child.getMeasuredHeight();
return super.onLayoutChild(parent, child, layoutDirection);
}
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
//因为Behavior只对CoordinatorLayout的直接子View生效,因此将依赖关系转移到AppBarLayout
return dependency instanceof AppBarLayout;
}
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
//得到依赖View的滑动距离
int top = ((AppBarLayout.Behavior)((CoordinatorLayout.LayoutParams)dependency.getLayoutParams()).getBehavior()).getTopAndBottomOffset();
//因为BottomNavigation的滑动与ToolBar是反向的,所以取-top值
ViewCompat.setTranslationY(child, -top);
return false;
}
}
注意,这个Behavior是要搭配ToolBar使用的,他要像拷贝忍者卡卡西一样,拷贝ToolBar的滑动隐藏效果。
还有一个加强版的。不需要搭配ToolBar,可以独立使用。
import android.content.Context;
import android.os.Parcelable;
import android.support.annotation.IntDef;
import android.support.design.widget.CoordinatorLayout;
import android.support.v4.view.WindowInsetsCompat;
import android.util.AttributeSet;
import android.view.View;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
public abstract class BottomNavigationViewVerticalScrollingBehavior extends CoordinatorLayout.Behavior {
private int mTotalDyUnconsumed = 0;
private int mTotalDy = 0;
@ScrollDirection
private int mOverScrollDirection = ScrollDirection.SCROLL_NONE;
@ScrollDirection
private int mScrollDirection = ScrollDirection.SCROLL_NONE;
public BottomNavigationViewVerticalScrollingBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
public BottomNavigationViewVerticalScrollingBehavior() {
super();
}
@Retention(RetentionPolicy.SOURCE)
@IntDef({ScrollDirection.SCROLL_DIRECTION_UP, ScrollDirection.SCROLL_DIRECTION_DOWN})
public @interface ScrollDirection {
int SCROLL_DIRECTION_UP = 1;
int SCROLL_DIRECTION_DOWN = -1;
int SCROLL_NONE = 0;
}
/*
@return Overscroll direction: SCROLL_DIRECTION_UP, CROLL_DIRECTION_DOWN, SCROLL_NONE
*/
@ScrollDirection
public int getOverScrollDirection() {
return mOverScrollDirection;
}
/**
* @return Scroll direction: SCROLL_DIRECTION_UP, SCROLL_DIRECTION_DOWN, SCROLL_NONE
*/
@ScrollDirection
public int getScrollDirection() {
return mScrollDirection;
}
/**
* @param coordinatorLayout
* @param child
* @param direction Direction of the overscroll: SCROLL_DIRECTION_UP, SCROLL_DIRECTION_DOWN
* @param currentOverScroll Unconsumed value, negative or positive based on the direction;
* @param totalOverScroll Cumulative value for current direction
*/
public abstract void onNestedVerticalOverScroll(CoordinatorLayout coordinatorLayout, V child, @ScrollDirection int direction, int currentOverScroll, int totalOverScroll);
/**
* @param scrollDirection Direction of the overscroll: SCROLL_DIRECTION_UP, SCROLL_DIRECTION_DOWN
*/
public abstract void onDirectionNestedPreScroll(CoordinatorLayout coordinatorLayout, V child, View target, int dx, int dy, int[] consumed, @ScrollDirection int scrollDirection);
@Override
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, V child, View directTargetChild, View target, int nestedScrollAxes) {
return (nestedScrollAxes & View.SCROLL_AXIS_VERTICAL) != 0;
}
@Override
public void onNestedScrollAccepted(CoordinatorLayout coordinatorLayout, V child, View directTargetChild, View target, int nestedScrollAxes) {
super.onNestedScrollAccepted(coordinatorLayout, child, directTargetChild, target, nestedScrollAxes);
}
@Override
public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, V child, View target) {
super.onStopNestedScroll(coordinatorLayout, child, target);
}
@Override
public void onNestedScroll(CoordinatorLayout coordinatorLayout, V child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);
if (dyUnconsumed > 0 && mTotalDyUnconsumed < 0) {
mTotalDyUnconsumed = 0;
mOverScrollDirection = ScrollDirection.SCROLL_DIRECTION_UP;
} else if (dyUnconsumed < 0 && mTotalDyUnconsumed > 0) {
mTotalDyUnconsumed = 0;
mOverScrollDirection = ScrollDirection.SCROLL_DIRECTION_DOWN;
}
mTotalDyUnconsumed += dyUnconsumed;
onNestedVerticalOverScroll(coordinatorLayout, child, mOverScrollDirection, dyConsumed, mTotalDyUnconsumed);
}
@Override
public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, V child, View target, int dx, int dy, int[] consumed) {
super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed);
if (dy > 0 && mTotalDy < 0) {
mTotalDy = 0;
mScrollDirection = ScrollDirection.SCROLL_DIRECTION_UP;
} else if (dy < 0 && mTotalDy > 0) {
mTotalDy = 0;
mScrollDirection = ScrollDirection.SCROLL_DIRECTION_DOWN;
}
mTotalDy += dy;
onDirectionNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed, mScrollDirection);
}
@Override
public boolean onNestedFling(CoordinatorLayout coordinatorLayout, V child, View target, float velocityX, float velocityY, boolean consumed) {
super.onNestedFling(coordinatorLayout, child, target, velocityX, velocityY, consumed);
mScrollDirection = velocityY > 0 ? ScrollDirection.SCROLL_DIRECTION_UP : ScrollDirection.SCROLL_DIRECTION_DOWN;
return onNestedDirectionFling(coordinatorLayout, child, target, velocityX, velocityY, mScrollDirection);
}
protected abstract boolean onNestedDirectionFling(CoordinatorLayout coordinatorLayout, V child, View target, float velocityX, float velocityY, @ScrollDirection int scrollDirection);
@Override
public boolean onNestedPreFling(CoordinatorLayout coordinatorLayout, V child, View target, float velocityX, float velocityY) {
return super.onNestedPreFling(coordinatorLayout, child, target, velocityX, velocityY);
}
@Override
public WindowInsetsCompat onApplyWindowInsets(CoordinatorLayout coordinatorLayout, V child, WindowInsetsCompat insets) {
return super.onApplyWindowInsets(coordinatorLayout, child, insets);
}
@Override
public Parcelable onSaveInstanceState(CoordinatorLayout parent, V child) {
return super.onSaveInstanceState(parent, child);
}
}
import android.content.Context;
import android.os.Build;
import android.support.annotation.NonNull;
import android.support.design.widget.CoordinatorLayout;
import android.support.design.widget.Snackbar;
import android.support.v4.view.ViewCompat;
import android.support.v4.view.ViewPropertyAnimatorCompat;
import android.support.v4.view.animation.LinearOutSlowInInterpolator;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Interpolator;
/**
*
* 自定义BottomNavigationView底部菜单栏上滑隐藏效果(可单独使用)
*
*/
public final class BottomNavigationViewBehaviorEx extends BottomNavigationViewVerticalScrollingBehavior {
private static final Interpolator INTERPOLATOR = new LinearOutSlowInInterpolator();
private final BottomNavigationWithSnackbar mWithSnackBarImpl = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ? new LollipopBottomNavWithSnackBarImpl() : new PreLollipopBottomNavWithSnackBarImpl();
private boolean isTablet;
private boolean hidden = false;
private ViewPropertyAnimatorCompat mOffsetValueAnimator;
private int mSnackbarHeight = -1;
private boolean scrollingEnabled = true;
private boolean hideAlongSnackbar = false;
int[] attrsArray = new int[] {
android.R.attr.id };
public BottomNavigationViewBehaviorEx() {
super();
}
public BottomNavigationViewBehaviorEx(Context context, AttributeSet attrs) {
super(context, attrs);
}
public static BottomNavigationViewBehaviorEx from(@NonNull V view) {
ViewGroup.LayoutParams params = view.getLayoutParams();
if (!(params instanceof CoordinatorLayout.LayoutParams)) {
throw new IllegalArgumentException("The view is not a child of CoordinatorLayout");
}
CoordinatorLayout.Behavior behavior = ((CoordinatorLayout.LayoutParams) params)
.getBehavior();
if (!(behavior instanceof BottomNavigationViewBehaviorEx)) {
throw new IllegalArgumentException(
"The view is not associated with BottomNavigationBehavior");
}
return (BottomNavigationViewBehaviorEx) behavior;
}
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, V child, View dependency) {
mWithSnackBarImpl.updateSnackbar(parent, dependency, child);
return dependency instanceof Snackbar.SnackbarLayout;
}
@Override
public void onDependentViewRemoved(CoordinatorLayout parent, V child, View dependency) {
updateScrollingForSnackbar(dependency, child, true);
super.onDependentViewRemoved(parent, child, dependency);
}
private void updateScrollingForSnackbar(View dependency, V child, boolean enabled) {
if (!isTablet && dependency instanceof Snackbar.SnackbarLayout) {
scrollingEnabled = enabled;
if (!hideAlongSnackbar && ViewCompat.getTranslationY(child) != 0) {
ViewCompat.setTranslationY(child, 0);
hidden = false;
hideAlongSnackbar = true;
}else if(hideAlongSnackbar){
hidden = true;
animateOffset(child, -child.getHeight());
}
}
}
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, V child, View dependency) {
updateScrollingForSnackbar(dependency, child, false);
return super.onDependentViewChanged(parent, child, dependency);
}
@Override
public boolean onLayoutChild(CoordinatorLayout parent, V child, int layoutDirection) {
boolean layoutChild = super.onLayoutChild(parent, child, layoutDirection);
return layoutChild;
}
@Override
public void onNestedVerticalOverScroll(CoordinatorLayout coordinatorLayout, V child, @ScrollDirection int direction, int currentOverScroll, int totalOverScroll) {
}
@Override
public void onDirectionNestedPreScroll(CoordinatorLayout coordinatorLayout, V child, View target, int dx, int dy, int[] consumed, @ScrollDirection int scrollDirection) {
handleDirection(child, scrollDirection);
}
private void handleDirection(V child, @ScrollDirection int scrollDirection) {
if (!scrollingEnabled) return;
if (scrollDirection == ScrollDirection.SCROLL_DIRECTION_DOWN && hidden) {
hidden = false;
animateOffset(child, 0);
} else if (scrollDirection == ScrollDirection.SCROLL_DIRECTION_UP && !hidden) {
hidden = true;
animateOffset(child, child.getHeight());
}
}
@Override
protected boolean onNestedDirectionFling(CoordinatorLayout coordinatorLayout, V child, View target, float velocityX, float velocityY, @ScrollDirection int scrollDirection) {
handleDirection(child, scrollDirection);
return true;
}
private void animateOffset(final V child, final int offset) {
ensureOrCancelAnimator(child);
mOffsetValueAnimator.translationY(offset).start();
}
private void ensureOrCancelAnimator(V child) {
if (mOffsetValueAnimator == null) {
mOffsetValueAnimator = ViewCompat.animate(child);
mOffsetValueAnimator.setDuration(200);
mOffsetValueAnimator.setInterpolator(INTERPOLATOR);
} else {
mOffsetValueAnimator.cancel();
}
}
public boolean isScrollingEnabled() {
return scrollingEnabled;
}
public void setScrollingEnabled(boolean scrollingEnabled) {
this.scrollingEnabled = scrollingEnabled;
}
public void setHidden(V view, boolean bottomLayoutHidden) {
if (!bottomLayoutHidden && hidden) {
animateOffset(view, 0);
} else if (bottomLayoutHidden && !hidden) {
animateOffset(view, -view.getHeight());
}
hidden = bottomLayoutHidden;
}
private interface BottomNavigationWithSnackbar {
void updateSnackbar(CoordinatorLayout parent, View dependency, View child);
}
private class PreLollipopBottomNavWithSnackBarImpl implements BottomNavigationWithSnackbar {
@Override
public void updateSnackbar(CoordinatorLayout parent, View dependency, View child) {
if (!isTablet && dependency instanceof Snackbar.SnackbarLayout) {
if (mSnackbarHeight == -1) {
mSnackbarHeight = dependency.getHeight();
}
int targetPadding = child.getMeasuredHeight();
int shadow = (int) ViewCompat.getElevation(child);
ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) dependency.getLayoutParams();
layoutParams.bottomMargin = targetPadding - shadow;
child.bringToFront();
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
child.getParent().requestLayout();
((View) child.getParent()).invalidate();
}
}
}
}
private class LollipopBottomNavWithSnackBarImpl implements BottomNavigationWithSnackbar {
@Override
public void updateSnackbar(CoordinatorLayout parent, View dependency, View child) {
if (!isTablet && dependency instanceof Snackbar.SnackbarLayout) {
if (mSnackbarHeight == -1) {
mSnackbarHeight = dependency.getHeight();
}
int targetPadding = (mSnackbarHeight +
child.getMeasuredHeight());
dependency.setPadding(dependency.getPaddingLeft(),
dependency.getPaddingTop(), dependency.getPaddingRight(), targetPadding
);
}
}
}
}
怎么使用想必知道CoordinatorLayout的应该会用把。
app:layout_behavior=.....