Android底部导航栏是非常常见的功能,UI设计大致分为两种:第一种纯小图+文字组合;第二种除了小图+文字组合外,还将中间设置大图。两种方式都有很多APP使用,并无优劣,根据需求选用即可。在这里我用我最熟悉的FragmentTabHost+Fragment实现上述功能。
注:文章末尾附项目源码下载链接。
效果展示
主要功能包括:FragmentTabHost的使用、图片选择器、文字选择器、沉浸式状态栏、带图片的底部导航栏等。
第一种纯小图+文字组合实现
页面布局
以下是Activity的布局文件,主要通过FragmentTabHost+FrameLayout实现底部导航栏,用View当做分割线,区分主体和底部导航栏。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ffffff"
android:orientation="vertical">
<FrameLayout
android:id="@+id/fl_content"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<View
android:layout_width="match_parent"
android:layout_height="0.4dp"
android:background="#99cccccc"
android:visibility="visible" />
<android.support.v4.app.FragmentTabHost
android:id="@+id/tabHost"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="2dp"
android:paddingTop="2dp"
android:visibility="visible" />
</LinearLayout>
创建多个图片选择器
在res的drawable文件夹下,创建与模块数量相同的图片选择器,使用户可以通过底部导航栏来区分,模块的选中和未选中状态。
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_selected="true" android:drawable="@mipmap/tab_home_selected"/>
<item android:state_pressed="true" android:drawable="@mipmap/tab_home_selected"/>
<item android:state_checked="true" android:drawable="@mipmap/tab_home_selected"/>
<item android:drawable="@mipmap/tab_home"/>
</selector>
创建一个文字选择器
在res的color文件夹(自行创建)下,创建一个文字选择器,用于底部导航栏的字体变色,一般创建一个即可。
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_selected="true" android:color="#ff0000"/>
<item android:state_pressed="true" android:color="#ff0000"/>
<item android:state_checked="true" android:color="#ff0000"/>
<item android:color="#666666"/>
</selector>
创建底部导航栏Tab的布局文件
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ffffff"
android:gravity="center">
<TextView
android:id="@+id/tv_indicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:drawablePadding="4dp"
android:drawableTop="@drawable/selector_tab_home"
android:gravity="center"
android:text="首页"
android:textColor="@color/selector_tab_text"
android:textSize="10sp" />
</RelativeLayout>
创建多个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为中间的大图。
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<FrameLayout
android:id="@+id/fl_content"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<View
android:layout_width="match_parent"
android:layout_height="0.4dp"
android:background="#99cccccc"
android:visibility="visible" />
<android.support.v4.app.FragmentTabHost
android:id="@+id/tabhost"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
<ImageView
android:id="@+id/listen"
android:layout_width="80dp"
android:layout_height="80dp"
android:layout_alignParentBottom="true"
android:layout_centerInParent="true"
android:contentDescription="@null"
android:paddingBottom="5dp"
android:src="@drawable/tab_center_img" />
</RelativeLayout>
图片选择器、文字选择器、底部导航栏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)