前言
如果你之前没接触过的话,建议先下载demo,对比本文进行学习;
如果你向我一样,总是忘记,那就动动小手收藏一下吧~~~
demo: https://github.com/liuleshuai/NavigationMenuBar
干货
现在的App底部一般都有一个可切换的菜单,通常使用有以下5种方式实现:
- TabHost(过时)
- Radiobutton(自定义)
- FragmentTabHost
- TabLayout(5.0新增)
- BottomNavigationView(5.0新增)
1.TabHost
它添加的是Activity而不像上面那些全部使用Fragment来显示内容。
过时,不推荐使用,略...
2.Radiobutton
原理就是通过设置button的样式来模拟菜单操作,可以参考这篇文章:
https://blog.csdn.net/duolaimila/article/details/51315623
不推荐,略...
3.FragmentTabHost
依赖
- 添加相关的依赖
com.android.support:support-v4:xx.x.x
布局
- 底部菜单栏的布局一般如下所示:
注意:
- FragmentTabHost的id是需要使用安卓自带的id,必须设置为
@android:id/tabhost
。- FragmentTabHost中的FrameLayout也需要使用安卓自带的id,
@android:id/tabcontent
。
另外,需要添加一个菜单Item的布局。
代码
- 绑定
mTabHost = (FragmentTabHost) findViewById(android.R.id.tabhost);
//绑定Fragment
mTabHost.setup(this, getSupportFragmentManager(), R.id.realcontent);
//绑定viewpager
// mTabHost.setup(this, getSupportFragmentManager(), R.id.pager);
- 设置菜单选项卡
for (int i = 0; i < count; i++) {
// 给每个Tab按钮设置标签、图标和文字
TabHost.TabSpec tabSpec = mTabHost.newTabSpec(textViewArray[i])
.setIndicator(getItemView(i));
// 将Tab按钮添加进Tab选项卡中,并绑定Fragment
mTabHost.addTab(tabSpec, fragmentArray[i], null);
//设置Tab被选中的时候颜色改变
mTabHost.getTabWidget().getChildAt(i)
.setBackgroundResource(R.drawable.selector_tab_background);
}
注意:
上述代码中的.newTabSpec(textViewArray[i])
是为菜单设置String类型的Tag标识,不一定要设置为菜单标题,但是一定不能重复,推荐设置成菜单的名称,肯定没人会设置两个一样的菜单吧 :)
private View getItemView(int i) {
View view = LayoutInflater.from(this).inflate(R.layout.fragment_tab_host_item, null, false);
TextView tv = view.findViewById(R.id.tab_textview);
tv.setText(textViewArray[i]);
Drawable drawable = ContextCompat.getDrawable(this,R.mipmap.ic_launcher);
drawable.setBounds(0, 0, drawable.getMinimumWidth(), drawable.getMinimumHeight());
tv.setCompoundDrawables(null, drawable, null, null);
return view;
}
- 其它设置
//去掉分割线 null 或 LinearLayout.SHOW_DIVIDER_NONE
mTabHost.getTabWidget().setDividerDrawable(null);
//设置默认第一个
mTabhost.setCurrentTab(0);
如果绑定的是ViewPager,还需要对页面切换进行处理
/*为了当点击下面菜单时,上面的ViewPager能滑动到对应的Fragment*/
mTabHost.setOnTabChangedListener(new TabHost.OnTabChangeListener() {
@Override
public void onTabChanged(String tabId) {
int position = mTabHost.getCurrentTab();
vp.setCurrentItem(position);
}
});
//设置页面切换时的监听器
vp.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageSelected(int position) {
mTabHost.setCurrentTab(arg0);
}
@Override
public void onPageScrollStateChanged(int state) {
}
});
不用担心上述二者的监听造成循环调用,VP和FragmentTabHost内部已经为我们做好了处理,即如果目标位置和当前位置相同,不会触发
OnPageChangeListener
和OnTabChangeListener
回调。
源码如下:
4.TabLayout
依赖
compile 'com.android.support:support-v4:xx.x.x'
compile 'com.android.support:design:xx.x.x'
布局
注意:
上述TabLayout
布局中,android:layout_height="100p"
这里需要注意,不能设置成wrap_content
,必须设置具体数值,否则会出现无法显示菜单标题的问题。切记!!!切记!!!切记!!!
style.xml
代码
//绑定VP
tabHost.setupWithViewPager(pager);
- 通过指定Tab的方式设置菜单
//指定Tab的位置
Tab one = mTabLayout.getTabAt(0);
Tab two = mTabLayout.getTabAt(1);
Tab three = mTabLayout.getTabAt(2);
Tab four = mTabLayout.getTabAt(3);
//设置Tab的图标,假如不需要则把下面的代码删去
one.setIcon(R.mipmap.ic_launcher);
two.setIcon(R.mipmap.ic_launcher);
three.setIcon(R.mipmap.ic_launcher);
four.setIcon(R.mipmap.ic_launcher);
绑定好VP和TabLayout后,菜单的tv内容默认是从适配器中getPageTitle
获得。
public class MyFragmentPagerAdapter extends FragmentPagerAdapter {
... ...
//PageTitle就是Tab的Text
@Override
public CharSequence getPageTitle(int position) {
return mTitles[position];
}
}
- 你还可以通过
setCustomView
的方式对菜单进行设置:
//自定义菜单
for (int i = 0; i < adapter.getCount(); i++) {
TabLayout.Tab tab = tabHost.getTabAt(i);
if (tab != null) {
tab.setCustomView(adapter.getTabView(i));
}
}
//在适配器中,返回自定义View
public View getTabView(int position) {
View view = LayoutInflater.from(context).inflate(R.layout.tab_layout, null);
ImageView iv = view.findViewById(R.id.iv);
TextView tv = view.findViewById(R.id.tv);
iv.setImageResource(images.get(position));
tv.setText(title.get(position));
return view;
}
通过这种方式设置的好处是,不用关心适配器中getPageTitle
的返回值啦!所有的设置都在view内。
5.BottomNavigationView
依赖
- 官方
compile 'com.android.support:support-v4:xx.x.x'
compile 'com.android.support:design:xx.x.x' - 也可以使用第三方库BottomNavigationBar
compile 'com.ashokvarma.android:bottom-navigation-bar:2.0.4'
使用方法:https://github.com/Ashok-Varma/BottomNavigation/wiki
本文用的是 官方!官方!官方!
布局
注意:
app:menu="@menu/bottom_navigation_view"
是必须设置的,这个布局里面是导航菜单的内容。
bottom_navigation_view.xml
代码
//监听VP,触发BNV
mViewPager.addOnPageChangeListener(onPageListener);
private ViewPager.OnPageChangeListener onPageListener = new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageSelected(int position) {
bottomNavigationView.getMenu().getItem(position).setChecked(true);
}
@Override
public void onPageScrollStateChanged(int state) {
}
};
//监听BNV,触发VP
bottomNavigationView.setOnNavigationItemSelectedListener(onItemSelectedListener);
private BottomNavigationView.OnNavigationItemSelectedListener onItemSelectedListener
= new BottomNavigationView.OnNavigationItemSelectedListener() {
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
resetToDefaultIcon();//重置到默认不选中图片
switch (item.getItemId()) {
case R.id.wechat:
mViewPager.setCurrentItem(Constants.FIRST);
item.setIcon(R.mipmap.icon_project_not_selected);
break;
case R.id.communcation:
mViewPager.setCurrentItem(Constants.SECOND);
break;
case R.id.find:
mViewPager.setCurrentItem(Constants.THIRD);
break;
case R.id.mine:
mViewPager.setCurrentItem(Constants.FOURTH);
break;
default:
break;
}
return true;
}
};
//解决 BottomNavigationView 图标位移非等分问题(使用的时候 item 数大于 3 个时会有位移)
BottomNavigationViewHelper.disableShiftMode(bottomNavigationView);
BottomNavigationViewHelper .java
public class BottomNavigationViewHelper {
@SuppressLint("RestrictedApi")
public static void disableShiftMode(BottomNavigationView view) {
BottomNavigationMenuView menuView = (BottomNavigationMenuView) view.getChildAt(0);
try {
Field shiftingMode = menuView.getClass().getDeclaredField("mShiftingMode");
shiftingMode.setAccessible(true);
shiftingMode.setBoolean(menuView, false);
shiftingMode.setAccessible(false);
for (int i = 0; i < menuView.getChildCount(); i++) {
BottomNavigationItemView item = (BottomNavigationItemView) menuView.getChildAt(i);
//noinspection RestrictedApi
item.setShiftingMode(false);
// set once again checked value, so view will be updated
//noinspection RestrictedApi
item.setChecked(item.getItemData().isChecked());
}
} catch (NoSuchFieldException e) {
Log.e("BNVHelper", "Unable to get shift mode field", e);
} catch (IllegalAccessException e) {
Log.e("BNVHelper", "Unable to change value of shift mode", e);
}
}
}
补充
如果想禁用选中菜单时放大的动画效果,只需要在资源文件dimen.xml
中,如下设置:
10sp
10sp
0sp
0sp
16dp
Demo
https://github.com/liuleshuai/NavigationMenuBar