顶部导航栏效果图:
底部导航栏效果图:
关于android导航栏的实现方法有很多,像TabHost+TabWidget,RadioButton+RadioGroup,FragmentTabHost等等。随着androidapi的更新,个人认为,目前实现导航栏最易用且美观的方式是TabLayout和BottomNavigationView,TabLayout实现顶部导航栏,BottomNavigationView实现底部导航栏。这两个控件都是位于android.support.design.widget包中,TabLayout是android5.0(api21)添加的,而BottomNavigationView是android7.1.1(api25)添加的,目前已发布的android官方开发文档中还没有关于BottomNavigationView的介绍,需要去官网文档才能看到。下面就对这些控件实现android导航栏时的使用方法做一些记录,方便以后查阅。
首先,添加对design包的依赖,如下所示:
compile 'com.android.support:design:25.3.1'
实际根据项目不同,后面的版本号可能需要变动。
底部导航栏的使用
直接在布局中添加BottomNavigationView,如下所示:
<android.support.design.widget.BottomNavigationView
android:id="@+id/navigation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:background="?android:attr/windowBackground"
app:menu="@menu/navigation" />
其中,注意android:layout_gravity和app:menu两个属性,不指定layout_gravity属性为bottom导航栏可能会飞到上面。飞到上面不久成了顶部导航栏了么?在逻辑上来讲确实没有问题,但是通过观察可以发现,顶部导航栏和底部导航栏的样式还是有差别的,如果反过来用看着就很别扭。app:menu属性就是为导航栏设置每个item了,看一下navigation.xml中的内容,如下所示:
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/navigation_home"
android:icon="@drawable/ic_home_black_24dp"
android:title="@string/title_home" />
<item
android:id="@+id/navigation_dashboard"
android:icon="@drawable/ic_dashboard_black_24dp"
android:title="@string/title_dashboard" />
<item
android:id="@+id/navigation_notifications"
android:icon="@drawable/ic_notifications_black_24dp"
android:title="@string/title_notifications" />
</menu>
menu文件很简单,其中为每个item指定的icon和title在底部导航栏显示时就是每一项的图标和文字描述了。现在BottomNavigationView就已经添加好了,下面怎么在代码中使用他呢,也很简单,BottomNavigationView中目前提供了两个接口,如下图所示:
和给大部分控件设置点击方法一样,对应的方法就是这两个啦,如下图所示:
在代码中通过上述的两个方法给BottomNavigationView设置点击事件,在接口中判断MenuItem的id就可以执行相应的逻辑啦,本例中仅仅是改变了一个TextView中的文字,相关代码如下所示:
private BottomNavigationView.OnNavigationItemSelectedListener mOnNavigationItemSelectedListener
= new BottomNavigationView.OnNavigationItemSelectedListener() {
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
switch (item.getItemId()) {
case R.id.navigation_home:
mTextMessage.setText(R.string.title_home);
return true;
case R.id.navigation_dashboard:
mTextMessage.setText(R.string.title_dashboard);
return true;
case R.id.navigation_notifications:
mTextMessage.setText(R.string.title_notifications);
return true;
}
return false;
}
};
navigation.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener);
上面两片代码在程序结构上并不位于同一个变量作用域中,所以分成了两个代码片。其实细心的朋友可以发现mOnNavigationItemSelectedListener接口是类成员变量,而navigation呢就是BottomNavigationView啦。在接口中可以看到,通过判断点击的item的id给TextView设置了不同的文字。
下面问题来了,介绍了OnNavigationItemSelectedListener接口,那么OnNavigationItemReselectedListener接口又是干什么的呢?当时也是看了半天文档没想明白,不过没关系,做个实验就知道了。我给navigation又添加了OnNavigationItemReselectedListener接口,代码如下所示:
private BottomNavigationView.OnNavigationItemReselectedListener itemReselectedListener=new BottomNavigationView.OnNavigationItemReselectedListener() {
@Override
public void onNavigationItemReselected(@NonNull MenuItem item) {
String msg=null;
switch (item.getItemId()) {
case R.id.navigation_home:
msg="home";
break;
case R.id.navigation_dashboard:
msg="dashboard";
break;
case R.id.navigation_notifications:
msg="notifications";
break;
}
Toast.makeText(MainActivity.this, "item "+msg+" is reselected", Toast.LENGTH_SHORT).show();
}
};
navigation.setOnNavigationItemReselectedListener(itemReselectedListener);
下面来看看实验效果吧:
很明显吧,再次点击已选中的item就会触发setOnNavigationItemReselectedListener,那么这玩意有什么用呢?当时马上想到了新浪微博,相信各位都有一个已经习以为常的操作,就是点击home button刷新微博。剩下的不用多说了,嘿嘿。。。
在生活中更常见到的是点击底部导航栏的图标,上面的界面随之滑动切换。这里并没有实现这样的效果,但是思路也并不难,在处理点击事件的接口中设置viewPager的currentItemId就好了。但是还要注意一个地方,就是在用户手动滑动viewPager时底部导航栏的选中图标也该随之切换。是不是感觉有点麻烦,嗯。在顶部导航栏控件TabLayout中有这样一个方法,如下所示:
这个方法就是专门用来设置TabLayout与ViewPager协同工作的。在用户手动滑动ViewPager时TabLayout中的选中项也会随之切换,甚至还有动画。讲道理BottomNavigationView中也应该提供这样的方法,因为这些导航控件大多数时候都是要和ViewPager一起工作的,然而目前BottomNavigatoinView中并没有这样的方法,不过我相信未来会有的。
底部导航栏的基本使用大概就这些,下面开始说顶部导航栏。
顶部导航栏的使用
在顶部导航栏的例子中,是让TabLayout和ViewPager协同工作的。首先在布局文件中添加这两个控件就非常简单啦,代码如下所示:
<android.support.design.widget.TabLayout
android:id="@+id/tab_top"
android:layout_width="match_parent"
android:layout_height="wrap_content"></android.support.design.widget.TabLayout>
<android.support.v4.view.ViewPager
android:id="@+id/viewpager"
android:layout_width="match_parent"
android:layout_height="match_parent">
</android.support.v4.view.ViewPager>
官方文档中还提供了另外一种写法,如下所示:
<android.support.v4.view.ViewPager
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.design.widget.TabLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="top" />
</android.support.v4.view.ViewPager>
下面开始说在代码中的使用,关于ViewPager及其Fragment适配器和Fragment的创建这里就不说了,我们将重点放在TabLayout的使用上。TabLayout的使用也很简单,如下所示,注意看注释:
//获取TabLayout实例
tabTop=(TabLayout)findViewById(R.id.tab_top);
//设置TabLayout中tabs的行为模式
tabTop.setTabMode(TabLayout.MODE_FIXED);
//设置协同工作的ViewPager
tabTop.setupWithViewPager(viewPager);
以上并称TabLayout使用三部曲,是不是很简单。那么TabLayout的标题是从哪来的呢?这一点可以从官方文档中得到答案,TabLayout会自动从ViewPager的适配器获取标题,在FragmentPagerAdapter中有这样一个方法,如下图所示:
实际上这个方法继承自PagerAdapter,TabLayout正是通过这个方法来获取每一个tab的标题,在本例中,FragmentPagerAdapter中的getPageTitle方法是这样写的:
@Override
public CharSequence getPageTitle(int position) {
return "title"+(position+1);
}
所以就有了示例顶部导航栏中每一项的标题。还有一个问题,那就是TabLayout中的tabs的行为模式是个什么意思?不急给出答案,做个实验,实验做完自然就有答案了。setTabMode方法的参数可选值只有两个,如下所示:
也就是MODE_FIXED和MODE_SCROLLABLE,我们将行为模式设置为MODE_SCROLLABLE,ViewPager适配器的getCount方法返回值改为8,也就是TabLayout中有8个Tab,然后运行一下。其实从参数名字上也能猜出一二,还是看实验结果吧,这样更直观更形象,实验结果请看下图:
怎么样,很直观很形象吧。但是改了之后这些tab变小了啊,没关系,在布局中可以给TabLayout指定app:tabMinWidth属性来规定tab的最小宽度,这里没有指定tab的最小宽度,使用的是默认最小宽度。还有很多属性可以去看官方文档。总之,如果需要很多tab的话就使用MODE_SCROLLABLE并为tab设置合适的宽度,使用MODE_FIXED模式的话不管有多少tab,这些tab都会蜷缩在一屏内并平分屏幕的宽度,这样如果tab很多就会很挤而且使用也不方便,一般来说tab在5个以内可以使用MODE_FIXED,再多就改用MODE_SCROLLABLE。
OK,TabLayout的基本使用方法大概也就这些。
如果将顶部导航栏和底部导航栏结合到一起,且都使用ViewPager的话,需要考虑的一个严重问题就是滑动冲突。
好了,今天就记录到这里。