在使用BottomNavigationView时item数量为3时还正常啊,求教如何取消数量大于4的时候就会出现的那个奇葩的动画效果,实现像微信下方导航栏的样子,谢谢。
1条回答 默认 最新
- 夜幕色倾城 2017-03-31 21:32关注
新建一个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; } }
}
本回答被题主选为最佳回答 , 对您是否有帮助呢?解决 无用评论 打赏 举报