Android底部导航栏是非常常见的功能,UI设计大致分为两种:第一种纯小图+文字组合;第二种除了小图+文字组合外,还将中间设置大图。两种方式都有很多APP使用,并无优劣,根据需求选用即可。在这里我用我最熟悉的FragmentTabHost+Fragment实现上述功能。
注:文章末尾附项目源码下载链接。
主要功能包括:FragmentTabHost的使用、图片选择器、文字选择器、沉浸式状态栏、带图片的底部导航栏等。
页面布局
以下是Activity的布局文件,主要通过FragmentTabHost+FrameLayout实现底部导航栏,用View当做分割线,区分主体和底部导航栏。
创建多个图片选择器
在res的drawable文件夹下,创建与模块数量相同的图片选择器,使用户可以通过底部导航栏来区分,模块的选中和未选中状态。
创建一个文字选择器
在res的color文件夹(自行创建)下,创建一个文字选择器,用于底部导航栏的字体变色,一般创建一个即可。
创建底部导航栏Tab的布局文件
创建多个Fragment(继承v4包下的Fragment)
创建与模块数量相同的的Fragment,首页的UI、功能等都将在这些Fragment中编写,并且由于Activity实现了沉浸式状态栏(4.4以上),因此布局需要做适配。
package com.wy.fragmenttabhosttwoimplementions.fragment;
import android.content.Context;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.wy.fragmenttabhosttwoimplementions.R;
/**
* 首页
*/
public class HomeTabFragment extends Fragment {
private Context context;
private View view;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
context = getActivity();
if (view == null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
view = View.inflate(context, R.layout.fragment_tab_home_v19, null);
} else {
view = View.inflate(context, R.layout.fragment_tab_home, null);
}
}
return view;
}
}
创建FragmentTabHost的枚举Tab
用于定义底部导航栏和Fragment的对应关系,并将创建的图片选择器与Fragment传进去。同时提供了构造函数、get、set方法供后续使用。
package com.wy.fragmenttabhosttwoimplementions.menu;
import com.wy.fragmenttabhosttwoimplementions.R;
import com.wy.fragmenttabhosttwoimplementions.fragment.HomeTabFragment;
import com.wy.fragmenttabhosttwoimplementions.fragment.OrderTabFragment;
import com.wy.fragmenttabhosttwoimplementions.fragment.StatisticsTabFragment;
public enum MainTabs {
Home(0,"首页", R.drawable.selector_tab_home, HomeTabFragment.class),
Classify(1,"订单", R.drawable.selector_tab_home, OrderTabFragment.class),
ShoppingCar(2,"统计", R.drawable.selector_tab_home, StatisticsTabFragment.class);
private int i;
private String name;
private int icon;
private Class> cla;
MainTabs(int i, String name, int icon, Class> cla) {
this.i = i;
this.name = name;
this.icon = icon;
this.cla = cla;
}
public int getI() {
return i;
}
public void setI(int i) {
this.i = i;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getIcon() {
return icon;
}
public void setIcon(int icon) {
this.icon = icon;
}
public Class> getCla() {
return cla;
}
public void setCla(Class> cla) {
this.cla = cla;
}
}
初始化FragmentTabHost
前边创建了那么多文件,终于到了整合使用的时候了,这也是实现底部导航栏的最后一步。首先将FragmentTabHost和FrameLayout关联起来,并去掉分割线(也可以保留,看需求),然后通过枚举类MainTabs添加Tab及其对应的Fragment,最后将Tab添加到FragmentTabHost中。大功告成,可以看看效果了。
/**
* 初始化FragmentTabHost
*/
private void initFragmentTabHost() {
//初始化tabHost
FragmentTabHost tabHost = (FragmentTabHost) findViewById(R.id.tabHost);
//将tabHost和FragmentLayout关联
tabHost.setup(getApplicationContext(), getSupportFragmentManager(), R.id.fl_content);
//去掉分割线
if (Build.VERSION.SDK_INT > 10) {
tabHost.getTabWidget().setShowDividers(0);
}
//添加tab和其对应的fragment
MainTabs[] tabs = MainTabs.values();
for (int i = 0; i < tabs.length; i++) {
MainTabs mainTabs = tabs[i];
TabHost.TabSpec tabSpec = tabHost.newTabSpec(mainTabs.getName());
View indicator = View.inflate(getApplicationContext(), R.layout.tab_indicator, null);
TextView tv_indicator = (TextView) indicator.findViewById(R.id.tv_indicator);
Drawable drawable = getApplicationContext().getResources().getDrawable(mainTabs.getIcon());
tv_indicator.setCompoundDrawablesWithIntrinsicBounds(null, drawable, null, null);
tv_indicator.setText(mainTabs.getName());
tabSpec.setIndicator(indicator);
tabHost.addTab(tabSpec, mainTabs.getCla(), null);
}
}
页面布局
以下是Activity的布局文件,主要通过FragmentTabHost+FrameLayout实现底部导航栏,用View当做分割线,区分主体和底部导航栏。ImageView为中间的大图。
图片选择器、文字选择器、底部导航栏Tab的布局文件、Fragment文件均与第一种相同,参照上边的即可。
创建FragmentTabHost的枚举Tab
与第一种不同的是中间的Classify的内容修改,这样看着更加清晰一下。也可以不修改,反正初始化FragmentTabHost的时候也不用。(推荐修改,更清晰)
package com.wy.fragmenttabhosttwoimplementions.menu;
import com.wy.fragmenttabhosttwoimplementions.R;
import com.wy.fragmenttabhosttwoimplementions.fragment.HomeTabFragment;
import com.wy.fragmenttabhosttwoimplementions.fragment.OrderTabFragment;
import com.wy.fragmenttabhosttwoimplementions.fragment.StatisticsTabFragment;
public enum MainTabsSecond {
Home(0,"首页", R.drawable.selector_tab_home, HomeTabFragment.class),
Classify(1,"", R.mipmap.ic_launcher, null),
ShoppingCar(2,"统计", R.drawable.selector_tab_home, StatisticsTabFragment.class);
private int i;
private String name;
private int icon;
private Class> cla;
MainTabsSecond(int i, String name, int icon, Class> cla) {
this.i = i;
this.name = name;
this.icon = icon;
this.cla = cla;
}
public int getI() {
return i;
}
public void setI(int i) {
this.i = i;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getIcon() {
return icon;
}
public void setIcon(int icon) {
this.icon = icon;
}
public Class> getCla() {
return cla;
}
public void setCla(Class> cla) {
this.cla = cla;
}
}
初始化FragmentTabHost
与第一种不同的是需要判断,当tab替换成ImageView时,隐藏tab并保持占位,其它的跟第一种相同。最后再为中间的ImageView设置点击事件监听即可。
/**
* 初始化FragmentTabHost
*/
private void initFragmentTabHost() {
//初始化tabHost
FragmentTabHost tabHost = (FragmentTabHost) findViewById(R.id.tabhost);
ImageView listen = (ImageView) findViewById(R.id.listen);
//将tabHost和FragmentLayout关联
tabHost.setup(getApplicationContext(), getSupportFragmentManager(), R.id.fl_content);
//去掉分割线
if (Build.VERSION.SDK_INT > 10) {
tabHost.getTabWidget().setShowDividers(0);
}
//添加tab和其对应的fragment
MainTabsSecond[] tabs = MainTabsSecond.values();
for (int i = 0; i < tabs.length; i++) {
MainTabsSecond mainTabs = tabs[i];
TabHost.TabSpec tabSpec = tabHost.newTabSpec(mainTabs.getName());
View indicator = View.inflate(getApplicationContext(), R.layout.tab_indicator, null);
TextView tv_indicator = (TextView) indicator.findViewById(R.id.tv_indicator);
Drawable drawable = getApplicationContext().getResources().getDrawable(mainTabs.getIcon());
//当tab替换成ImageView时,隐藏tab并保持占位
if (i == 1) {
tv_indicator.setVisibility(View.INVISIBLE);
} else {
tv_indicator.setCompoundDrawablesWithIntrinsicBounds(null, drawable, null, null);
tv_indicator.setText(mainTabs.getName());
}
tabSpec.setIndicator(indicator);
tabHost.addTab(tabSpec, mainTabs.getCla(), null);
//图片的点击事件
listen.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(getApplicationContext(), "点击了图片", Toast.LENGTH_SHORT).show();
//替换Fragment
//不推荐这么干,因为将tab替换成了ImageView
// getSupportFragmentManager().beginTransaction()
// .add(R.id.fl_content, new OrderTabFragment()).commit();
}
});
}
}
经过以上的种种努力,终于实现了底部导航栏的两种效果,试一下效果感觉还是挺好的。当然这只是简单的Demo,主体UI、功能就等待我们的后续开发了。底部导航栏当然还有其它的实现方式,有更好的idea可以相互交流,不过适合自己的才是最好的,坚持使用一种并不断的研究完善,相信会更好。
项目源码下载地址(https://download.csdn.net/download/qq941263013/10554431)
GitHub项目地址(https://github.com/wangyang0313/FragmentTabHostTwoImplementions)
---------------------------------------------------------------------------------------------------------------------------
早计划,早准备,早完成。 欢迎关注!交流!Star!
GitHub:https://github.com/wangyang0313
微信公众号:一个灵活的胖子MrWang
简书:https://www.jianshu.com/u/e5e733d79b96