BottomNavigationView从入门到强行改造,取消位移动画?和ViewPager绑定?添加Badge?
2017-01-07 20:17
585 查看
如有转载,请申明:
转载至 http://blog.csdn.net/qq_35064774/article/details/54177702
考虑到一些人可能没时间看到最后,我把改造的库地址放在最前面 BottomNavigationViewEx。
这里添加的是 25.1.0,因为 25.0.0 版本有一个小bug,就是设置点击监听事件的返回值不起作用。
这里我把背景设置成主色调 colorPrimary,图标和文本设置为一样的颜色 selector_item_color,具体内容如下:
也就是选中的时候是白色,默认为灰色。
最后是菜单
菜单的图片建议用矢量图片,也就是 svg 导入后的xml文件。
最后运行出来是这样的。
看到这里,我只能说,没毛病。
也就是设置一个点击的监听器。
回调方法有个返回值,如果返回 false,则你的点击会被取消,也就是不会切换到下一个菜单。
回调方法中给的参数是
貌似依旧没毛病,官方的库用法简单实用。
如果 PM 非要你改成没有动画的效果,如下图,这库是不是就很难用了?
大致有两种途径:
把整个控件代码复制一份,然后进行修改。
把类包裹一层,利用反射去修改。
这两种途径各有优缺点。
第一种直接修改的途径,优点是简单直接,性能高。缺点是需要把整个控件的代码都复制一份,每次官方对控制做出修改后,无法享受新特性。
第二种包裹的途径,优点是只需要针对一个类进行包裹,不容易影响到原来类的作用。缺点是反射性能不高。
在权衡一番之后,我选择了第二种方式。
然后看构造方法,是把
然后再看构造函数,设置了一个点击监听器,接收到的是
然而这里并没有直接的对
在 presenter 的
具体调用顺序如下:
所以,最后控制每个子菜单怎么显示的是 mButtons ,也就是 BottomNavigationItemView 。
分析到这里,基本算是了解主线了。
底部菜单是由一个一个
由于变量是私有的,且没有提供 set 方法,所以只能通过反射来做。
这个位移变量有两处,控制的内容是不一样的,我们先看 BottomNavigationMenuView 里面的。
要想修改这个变量,必须先取得 mMenuView,然后在设置里面的 mShiftingMode。具体的代码如下。
这里没有把反射细节代码写出来,因为反射很简单,只是步骤繁琐,所以节省篇幅,就略过,有兴趣可以查看我写的库的代码。
修改方法和上面类似。
那个就是关键,只要能模拟发出一个 click 事件,就能设置当前选中项。
为了调用这一方法,需要先获取到对应位置的
没错,就是
具体代码如下:
给控件加上 Bagde 的思路大致有以下几种:
给控件加个红点 ImageView
给控件图片的 Drawable 外面套一个带红点的 Drawable,然后替换 Drawable。
在顶级容器上加入小红点,调整位置,伪装成和控件一体。
事实上这几种方法对于底部导航栏来说都行得通。
但实现起来难度不一样。
我为了省事,直接用了第三方库 BadgeView 。
本想采用第一种方法,但发现,加在图片或 BottomNavigationItemView 上都会导致排版错乱。
于是尝试第三种方案,发现行得通。
具体代码如下:
把红点加载根布局上,然后获取到目标图片的位置,计算出间距就行了。
代码放在了 Github ,地址在最前面,若有兴趣,记得 star 收藏。
转载至 http://blog.csdn.net/qq_35064774/article/details/54177702
前言
BottomNavigationView 这个官方控件出了几个月了,也有一些介绍该控件的文章,但我发现大部分博文只是做了简单的用法介绍,并未解决一些需求,比如:取消位移动画、和ViewPager一起使用、加入Badge。所以我又写了这么一篇博客。考虑到一些人可能没时间看到最后,我把改造的库地址放在最前面 BottomNavigationViewEx。
基本用法
1. 添加依赖
compile 'com.android.support:design:25.1.0'
这里添加的是 25.1.0,因为 25.0.0 版本有一个小bug,就是设置点击监听事件的返回值不起作用。
2. 在 xml 中使用库
<android.support.design.widget.BottomNavigationView android:id="@+id/bnve" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:background="@color/colorPrimary" app:itemIconTint="@color/selector_item_color" app:itemTextColor="@color/selector_item_color" app:menu="@menu/menu_navigation_with_view_pager" />
background: 控件背景
app:itemBackground: 子菜单背景
app:itemIconTint: 图标颜色
app:itemTextColor: 文本颜色
app:menu: 菜单
这里我把背景设置成主色调 colorPrimary,图标和文本设置为一样的颜色 selector_item_color,具体内容如下:
<?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:color="#fff" android:state_checked="true"/> <item android:color="#fff" android:state_pressed="true"/> <item android:color="#bbb"/> </selector>
也就是选中的时候是白色,默认为灰色。
最后是菜单
menu_navigation_with_view_pager,和普通菜单一样。
<?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="@+id/menu_music" android:checked="true" android:icon="@drawable/ic_audiotrack_black_24dp" android:title="@string/music" /> <item android:id="@+id/menu_backup" android:icon="@drawable/ic_backup_black_24dp" android:title="@string/backup" /> <item android:id="@+id/menu_friends" android:icon="@drawable/ic_camera_black_24dp" android:title="@string/friends" /> </menu>
菜单的图片建议用矢量图片,也就是 svg 导入后的xml文件。
最后运行出来是这样的。
看到这里,我只能说,没毛病。
3. 方法
如果去看类的文档,就会发现,公开的方法中,常用的就只有setOnNavigationItemSelectedListener。
也就是设置一个点击的监听器。
// set listener to do something then item selected bnve.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() { @Override public boolean onNavigationItemSelected(@NonNull MenuItem item) { Log.d(TAG, item.getItemId() + " item was selected-------------------"); // you can return false to cancel select return true; } });
回调方法有个返回值,如果返回 false,则你的点击会被取消,也就是不会切换到下一个菜单。
回调方法中给的参数是
MenuItem,你可以获取到被点击菜单的 id,也就是你可以这么做。
int id = 0; switch (item.getItemId()) { case R.id.menu_music: id = 0; break; case R.id.menu_backup: id = 1; break; case R.id.menu_friends: id = 2; break; } vp.setCurrentItem(id, false);// 改变的 ViewPager 的当前页面
貌似依旧没毛病,官方的库用法简单实用。
官方库的需求问题
1. 和 ViewPager 一起使用
但仔细想一下,如果我想滑动 ViewPager 时,顺便改变控件的选中项(Material Design 反对这样设计,但需求确实存在)。2. 取消位移动画
如果你的菜单数大于3个,则界面是这样的。如果 PM 非要你改成没有动画的效果,如下图,这库是不是就很难用了?
3. 加入 Badge
对于底部导航栏,一个带数字的小红圈是很常见的需求,对于这种需求,又该怎么办?动手改造
由于种种原因,官方的底部导航栏目前满足不了我的需求,所以我产生了改造库的想法。大致有两种途径:
把整个控件代码复制一份,然后进行修改。
把类包裹一层,利用反射去修改。
这两种途径各有优缺点。
第一种直接修改的途径,优点是简单直接,性能高。缺点是需要把整个控件的代码都复制一份,每次官方对控制做出修改后,无法享受新特性。
第二种包裹的途径,优点是只需要针对一个类进行包裹,不容易影响到原来类的作用。缺点是反射性能不高。
在权衡一番之后,我选择了第二种方式。
分析源码
要改造库,首先得了解库的内部原理。1. BottomNavigationView
进入BottomNavigationView,发现最主要的成员是下面两个,由变量命名可以猜测出分别是作为视图和控制器。
private final BottomNavigationMenuView mMenuView; private final BottomNavigationPresenter mPresenter = new BottomNavigationPresenter();
然后看构造方法,是把
mMenuView添加到Layout里了。所以,如果想要了解界面怎么显示的,还得分析
BottomNavigationMenuView。
addView(mMenuView, params);
2. BottomNavigationMenuView
通过对成员变量的粗略查看,发现以下几个关键的成员。private final OnClickListener mOnClickListener;// 点击监听器 private boolean mShiftingMode = true;// 控制导航条的位移模式 private BottomNavigationItemView[] mButtons;// 子菜单View
然后再看构造函数,设置了一个点击监听器,接收到的是
BottomNavigationItemView,处理的是点击子菜单的事件。
mOnClickListener = new OnClickListener() { @Override public void onClick(View v) { final BottomNavigationItemView itemView = (BottomNavigationItemView) v; final int itemPosition = itemView.getItemPosition(); if (!mMenu.performItemAction(itemView.getItemData(), mPresenter, 0)) { activateNewButton(itemPosition); } } };
然而这里并没有直接的对
mButtons赋值,这个时候就应该去找 presenter,对MVP熟悉的就知道, presenter 负责把 M 和 V 联系起来。
在 presenter 的
updateMenuView方法中调用了 mMenuView 中的
updateMenuView去创建 mButtons。而 BottomNavigationView 中负责调用 presenter。
具体调用顺序如下:
BottomNavigationView()// BottomNavigationView -> inflateMenu(a.getResourceId(R.styleable.BottomNavigationView_menu, 0));// BottomNavigationView -> mPresenter.updateMenuView(true);// BottomNavigationView -> mMenuView.updateMenuView();// BottomNavigationPresenter -> buildMenuView();// BottomNavigationMenuView -> mButtons = new BottomNavigationItemView[mMenu.size()];// BottomNavigationMenuView
所以,最后控制每个子菜单怎么显示的是 mButtons ,也就是 BottomNavigationItemView 。
3. BottomNavigationItemView
查看成员变量,发现负责显示的成员。private boolean mShiftingMode;// 子菜单的位移模式 private ImageView mIcon;// 图片 private final TextView mSmallLabel;// 小文本 private final TextView mLargeLabel;// 大文本
分析到这里,基本算是了解主线了。
底部菜单是由一个一个
BottomNavigationItemView组成,而
BottomNavigationItemView是由
ImageView和
TextView组成的。
取消位移动画
分析完源码后,发现最容易做的是取消位移动画,因为在分析过程中,我发现了一个重要的 boolean 成员变量,从名字就可以看出是控制位移动画的。事实上,这猜测也是正确的,在代码里搜索mShiftingMode就会发现根据这个变量的真假,会有两套显示效果。这里就不展开了,毕竟不是专门分析源码的博文。
由于变量是私有的,且没有提供 set 方法,所以只能通过反射来做。
这个位移变量有两处,控制的内容是不一样的,我们先看 BottomNavigationMenuView 里面的。
1. BottomNavigationMenuView 中的 mShiftingMode
这里的 mShiftingMode 控制的是菜单之间的宽度,具体不太好说,对照上面的图片就容易理解的,选中的宽度大。要想修改这个变量,必须先取得 mMenuView,然后在设置里面的 mShiftingMode。具体的代码如下。
/** * 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(); }
这里没有把反射细节代码写出来,因为反射很简单,只是步骤繁琐,所以节省篇幅,就略过,有兴趣可以查看我写的库的代码。
2. BottomNavigationItemView 中的 mShiftingMode
这个位移模式是只文字的显示,如果开启,则选择项显示图标和文字,其他的只显示图片。修改方法和上面类似。
/** * 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(); }
设置当前选中项
还记得在 BottomNavigationMenuView 看到的 mOnClickListener 吗?那个就是关键,只要能模拟发出一个 click 事件,就能设置当前选中项。
onClick方法需要传递一个
View,而且是
BottomNavigationItemView。
为了调用这一方法,需要先获取到对应位置的
BottomNavigationItemView。而这个 View 似曾相识。
没错,就是
mButtons,只要取得了
mButtons,然后获取数组对应位置的值,就是这个参数了。
具体代码如下:
/** * 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", View.OnClickListener.class); // System.out.println("mMenuView:" + mMenuView + " mButtons:" + mButtons + " mOnClickListener" + mOnClickListener); // 3. call mOnClickListener.onClick(); mOnClickListener.onClick(mButtons[item]); }
加入 Badge
Bagde 就是字面意思,一个标记。一般都是一个小红圈,里面有数字。给控件加上 Bagde 的思路大致有以下几种:
给控件加个红点 ImageView
给控件图片的 Drawable 外面套一个带红点的 Drawable,然后替换 Drawable。
在顶级容器上加入小红点,调整位置,伪装成和控件一体。
事实上这几种方法对于底部导航栏来说都行得通。
但实现起来难度不一样。
我为了省事,直接用了第三方库 BadgeView 。
本想采用第一种方法,但发现,加在图片或 BottomNavigationItemView 上都会导致排版错乱。
于是尝试第三种方案,发现行得通。
具体代码如下:
private void initView() { // disable all animations bind.bnve.enableAnimation(false); bind.bnve.enableShiftingMode(false); bind.bnve.enableItemShiftingMode(false); // add a BadgeView at second icon bind.bnve.post(new Runnable() { @Override public void run() { badgeView1 = addBadgeViewAt(1, "1", BadgeView.SHAPE_OVAL); badgeView3 = addBadgeViewAt(3, "99", BadgeView.SHAPE_OVAL); // hide the red circle when click bind.bnve.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() { @Override public boolean onNavigationItemSelected(@NonNull MenuItem item) { int position = bind.bnve.getMenuItemPosition(item); switch (position) { case 1: toggleBadgeView(badgeView1); break; case 3: toggleBadgeView(badgeView3); break; } return true; } }); } }); } /** * show or hide badgeView * @param badgeView */ private void toggleBadgeView(BadgeView badgeView) { badgeView.setVisibility(badgeView.getVisibility() == View.VISIBLE ? View.INVISIBLE : View.VISIBLE); } /** * add a BadgeView on icon at position * @param position add to which icon * @param text the text show on badge * @param shape the badge view shape * @return */ private BadgeView addBadgeViewAt(int position, String text, int shape) { // get position ImageView icon = bind.bnve.getIconAt(position); int[] pos = new int[2]; icon.getLocationInWindow(pos); // action bar height ActionBar actionBar = getSupportActionBar(); int actionBarHeight = 0; if (null != actionBar) { actionBarHeight = actionBar.getHeight(); } int x = (int) (pos[0] + icon.getMeasuredWidth() * 0.7f); int y = (int) (pos[1] - actionBarHeight - icon.getMeasuredHeight() * 1.25f); // calculate width int width = 16 + 4 * (text.length() - 1); int height = 16; BadgeView badgeView = BadgeFactory.create(this) .setTextColor(Color.WHITE) .setWidthAndHeight(width, height) .setBadgeBackground(Color.RED) .setTextSize(10) .setBadgeGravity(Gravity.LEFT | Gravity.TOP) .setBadgeCount(text) .setShape(shape) // .setMargin(0, 0, 0, 0) .bind(this.bind.rlRoot); badgeView.setX(x); badgeView.setY(y); return badgeView; }
把红点加载根布局上,然后获取到目标图片的位置,计算出间距就行了。
代码放在了 Github ,地址在最前面,若有兴趣,记得 star 收藏。
相关文章推荐
- BottomNavigationView从入门到强行改造,取消位移动画?和ViewPager绑定?添加Badge?
- Android官方BottomNavigationView添加Badge(角标)
- Android编程入门--BottomNavigationView+ViewPager
- 给ViewPager添加切换动画
- viewpager切换添加动画效果viewpager切换添加动画效果--IT蓝豹
- BottomNavigationView结合ViewPager
- 安卓开发入门之底部导航BottomNavigationView(翻译)
- Android至ViewPager添加切换动画——使用属性动画
- BottomNavigationView+ViewPager实现底部导航栏
- 如何去掉BottomNavigationView的Item大于3个时的动画效果
- 如何给ViewPager的条目添加渐变动画
- 20.ViewFilpper+手势实现ViewPager功能+添加动画
- viewpager添加切换动画
- BottomNavigationView+ViewPager打造底部导航栏
- Swift教程_通过改造官方Sample学习Swift(八)_swift重写Sample(添加View的动画效果、添加View的阴影)
- ViewPager添加动画效果(一行代码)
- ViewPager 添加动画效果
- ViewPager 从入门到带你撸个启动页之实战PageTransformer切换动画特效(四)
- Android流行UI布局——底部导航(BottomNavigationView+ViewPager+Fragment)
- BottomNavigationView去掉动画效果