前言: 前面写过一篇FragmentTabHost+FrameLayout实现底部导航栏 公司原来一直这个套路来处理底部导航栏和显示页面的切换,真的很好用。但是自从发现BottomNavigationView这个控件之后,感觉BottomNavigationView+ViewPager实现底部导航栏这个模式更简单而且效果更酷。写个小demo展示一下,下面先看一下效果图:
—>
首先: 来看一下整体布局代码:
activity_main.xml
"1.0" encoding="utf-8"?>
"http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.blueskygrid.test1.MainActivity">
<com.blueskygrid.test1.widget.NoSlidingViewPaper
android:id="@+id/vp_main_container"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
com.blueskygrid.test1.widget.NoSlidingViewPaper>
.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" />
代码很简单,总体分为两部分,第一部分,NoSlidingViewPaper显示主体内容部分,继承Viewpager屏蔽了ViewPager左右滑动,代码很简单,代码如下:
NoSlidingViewPaper.java
public class NoSlidingViewPaper extends ViewPager {
public NoSlidingViewPaper(Context context, AttributeSet attrs) {
super(context, attrs);
}
public NoSlidingViewPaper(Context context) {
super(context);
}
/*
* 表示把滑动事件传递给下一个view
*/
@Override
public boolean onInterceptTouchEvent(MotionEvent arg0) {
return false;
}
/*
* 可以啥都不做
*/
public boolean onTouchEvent(MotionEvent arg0) {
return false;
}
}
第二部分,BottomNavigationView就是底部的导航栏了,除了app:menu=”@menu/navigation”这个属性设置以外,其他的大家都知道。这个属性就是给导航栏设置菜单项,这个和我们在标题栏上放设置菜单项的原理是一样的。在meau文件夹下(如果你的工程没有这个文件夹,就建一个)新建navigation.xml,在里面写要展示的菜单项,先看一下代码:
navigation.xml
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/navigation_home"
android:icon="@drawable/ic_me_normal"
android:title="@string/title_home" />
<item
android:id="@+id/navigation_message"
android:icon="@drawable/ic_message_normal"
android:title="@string/title_message" />
<item
android:id="@+id/navigation_publish"
android:icon="@drawable/ic_publish_normal"
android:title="@string/title_publish" />
menu>
可以看到,我建立了三个菜单项,id这个大家知道,是唯一标识,为了处理点击事件做准备,icon代表菜单项图标,title菜单项文字。注意:这里的icon用的是矢量图 ,不怎么了解矢量图的同学可以百度先了解一下,下片文章,我会写一下我用位图怎么转化为矢量图的。如果你需要我的矢量图,可以到文章最下面下载。
接下来, 我们先来看一下逻辑代码:
public class MainActivity extends AppCompatActivity {
private NoSlidingViewPaper mViewPager;
//底部菜单栏各个菜单项的点击事件处理
private BottomNavigationView.OnNavigationItemSelectedListener mOnNavigationItemSelectedListener
= new BottomNavigationView.OnNavigationItemSelectedListener() {
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
switch (item.getItemId()) {
case R.id.navigation_home://主页
mViewPager.setCurrentItem(0);
return true;
case R.id.navigation_message://信息
mViewPager.setCurrentItem(1);
return true;
case R.id.navigation_publish://发布
mViewPager.setCurrentItem(2);
return true;
}
return false;
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
/*初始化显示内容*/
mViewPager = (NoSlidingViewPaper) findViewById(R.id.vp_main_container);
final ArrayList fgLists = new ArrayList<>(3);
fgLists.add(new HomeFragment());
fgLists.add(new MessageFragment());
fgLists.add(new PublishFragment());
FragmentPagerAdapter mAdapter = new FragmentPagerAdapter(getSupportFragmentManager()) {
@Override
public Fragment getItem(int position) {
return fgLists.get(position);
}
@Override
public int getCount() {
return fgLists.size();
}
};
mViewPager.setAdapter(mAdapter);
mViewPager.setOffscreenPageLimit(2); //预加载剩下两页
BottomNavigationView navigation = (BottomNavigationView) findViewById(R.id.navigation);
/*给底部导航栏菜单项添加点击事件*/
navigation.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener);
}
}
可以看出逻辑代码很少,很容易看懂,点击菜单跳转到相应的Fragement,至于菜单项选中和变色,我们都不用管,BottomNavigationView都已经帮我们处理好了,如果我们想修改菜单项选中的颜色,我们可以通过改变styles.xml文件中BaseTheme的colorPrimary的值。到这里其实已经可以实现前面图1的效果了。因为Viewpager有预加载下一页这个模式,我这里是设置预加载了两页。这三个页面都有网络请求,那进入app耗时长,体验效果很差。
所以,下面我们用懒加载来处理这个问题, 先来看一下代码:
BaseFragment.java
public abstract class BaseFragement extends Fragment {
protected View mView;
protected boolean isViewInitiated; //当前页面是否初始化
protected boolean isVisibleToUser; //当前页面是否显示
protected boolean isDataRequested; //是否已经请求了数据
protected Context mContext;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
mContext = getContext();
mView = inflater.inflate(getLayoutId(), null);
isViewInitiated = true;
initView();
prepareGetData();
return mView;
}
/*初始化页面布局和数据*/
protected abstract void initView();
/*布局*/
public abstract int getLayoutId();
/*服务器获取数据*/
protected abstract void getDataFromServer();
/**
* 当前页面是否展示
* @param isVisibleToUser 显示为true, 不显示为false
*/
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
this.isVisibleToUser = isVisibleToUser;
prepareGetData();
}
/**
* 如果只想第一次进入该页面请求数据,return prepareGetData(false)
* 如果想每次进入该页面就请求数据,return prepareGetData(true)
* @return
*/
private boolean prepareGetData(){
return prepareGetData(false);
}
/**
* 判断是否从服务器器获取数据
* @param isforceUpdate 强制更新的标记
* @return
*/
protected boolean prepareGetData(boolean isforceUpdate) {
if(isVisibleToUser && isViewInitiated && (!isDataRequested || isforceUpdate)){
/*从服务器获取数据*/
getDataFromServer();
isDataRequested = true;
return true;
}
return false;
}
}
HomeFragment.java
public class HomeFragment extends BaseFragement{
@Override
protected void initView() {
}
@Override
public int getLayoutId() {
return R.layout.fragment_home;
}
@Override
protected void getDataFromServer() {
Toast.makeText(mContext, "HomeFragment页面请求数据了", Toast.LENGTH_SHORT).show();
}
}
主要逻辑看BaseFragment,可以结合我的注释看,主要有一个不常用的方法setUserVisibleHint(boolean isVisibleToUser) 这个方法可以处理判断某个页面是否显示。
文章到这里已经结束了,有不对的地方,欢迎指正^_^,另一篇文章链接:BottomNavigationView3个菜单项以上时去除动画效果
demo下载地址