常见的底部导航栏实现方式有三种:
1、TabHost+Activity:这种方式一般来说资源开销比较大,官方已经不推荐使用了。
2、RadioButton(RadioGroup)+Fragment:这种实现起来相对来说比较麻烦一点。
3、FragmentTabHost+Fragment:这种实现起来简单,资源开销小,推荐使用。
FragmentTabHost简介:
如下图所示,导航栏中整一个都是FragmentTabHost,里面包含的每一个“栏目”我们叫做TabSpec,也就是每一个分页,TabSpec里面要有指示器Indicator,用于指示用户选中了哪一个,其中里面包含了一张图片和文字描述。
FragmentTableHost实现起来也非常的简单,其中要注意的有下面几点:
1、所用到的Activity中必须要继承FragmentActivity,不然项目就会崩溃掉的。
2、调用FragmentTabHost的setup()方法,设置FragmentManager,以及指定用于装载Fragment的而已容器。
3、调用FragmentTabHost的addTab()方法进行添加分页。
项目结构图:
具体实现代码:
要使用FragmentTabHost,首先要从布局中添加进来,我这里并没有使用官方提供的V4包中的Fragment,而是使用了我们自定义的FragmentTabHost,其实内容都是差不多,只不过是对FragmentTabHost进行了一些优化,因为官方提供的FragmentTabHost中每次都会初始化一次Fragment。
下面新建一个项目,新建weiget包,新建FragmentTabHost.java文件,加入下面代码(如果不加这个导官方V4的包也可以用)
package com.example.pc.myshop.weiget;
import java.util.ArrayList;
import android.content.Context;
import android.content.res.TypedArray;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.TabHost;
import android.widget.TabWidget;
/**
* 功能描述:修改过的FragmentTabHost,保存fragment实例不销毁
*/
public class FragmentTabHost extends TabHost implements
TabHost.OnTabChangeListener {
private final ArrayList<TabInfo> mTabs = new ArrayList<TabInfo>();
private FrameLayout mRealTabContent;
private Context mContext;
private FragmentManager mFragmentManager;
private int mContainerId;
private OnTabChangeListener mOnTabChangeListener;
private TabInfo mLastTab;
private boolean mAttached;
static final class TabInfo {
private final String tag;
private final Class<?> clss;
private final Bundle args;
private Fragment fragment;
TabInfo(String _tag, Class<?> _class, Bundle _args) {
tag = _tag;
clss = _class;
args = _args;
}
}
static class DummyTabFactory implements TabContentFactory {
private final Context mContext;
public DummyTabFactory(Context context) {
mContext = context;
}
@Override
public View createTabContent(String tag) {
View v = new View(mContext);
v.setMinimumWidth(0);
v.setMinimumHeight(0);
return v;
}
}
static class SavedState extends BaseSavedState {
String curTab;
SavedState(Parcelable superState) {
super(superState);
}
private SavedState(Parcel in) {
super(in);
curTab = in.readString();
}
@Override
public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags);
out.writeString(curTab);
}
@Override
public String toString() {
return "FragmentTabHost.SavedState{"
+ Integer.toHexString(System.identityHashCode(this))
+ " curTab=" + curTab + "}";
}
public static final Creator<SavedState> CREATOR = new Creator<SavedState>() {
public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
}
public FragmentTabHost(Context context) {
// Note that we call through to the version that takes an AttributeSet,
// because the simple Context construct can result in a broken object!
super(context, null);
initFragmentTabHost(context, null);
}
public FragmentTabHost(Context context, AttributeSet attrs) {
super(context, attrs);
initFragmentTabHost(context, attrs);
}
private void initFragmentTabHost(Context context, AttributeSet attrs) {
TypedArray a = context.obtainStyledAttributes(attrs,
new int[]{android.R.attr.inflatedId}, 0, 0);
mContainerId = a.getResourceId(0, 0);
a.recycle();
super.setOnTabChangedListener(this);
}
private void ensureHierarchy(Context context) {
// If owner hasn't made its own view hierarchy, then as a convenience
// we will construct a standard one here.
if (findViewById(android.R.id.tabs) == null) {
LinearLayout ll = new LinearLayout(context);
ll.setOrientation(LinearLayout.VERTICAL);
addView(ll, new LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));
TabWidget tw = new TabWidget(context);
tw.setId(android.R.id.tabs);
tw.setOrientation(TabWidget.HORIZONTAL);
ll.addView(tw, new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT, 0));
FrameLayout fl = new FrameLayout(context);
fl.setId(android.R.id.tabcontent);
ll.addView(fl, new LinearLayout.LayoutParams(0, 0, 0));
mRealTabContent = fl = new FrameLayout(context);
mRealTabContent.setId(mContainerId);
ll.addView(fl, new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT, 0, 1));
}
}
/**
* @deprecated Don't call the original TabHost setup, you must instead call
* {@link #setup(Context, FragmentManager)} or
* {@link #setup(Context, FragmentManager, int)}.
*/
@Override
@Deprecated
public void setup() {
throw new IllegalStateException(
"Must call setup() that takes a Context and FragmentManager");
}
public void setup(Context context, FragmentManager manager) {
ensureHierarchy(context); // Ensure views required by super.setup()
super.setup();
mContext = context;
mFragmentManager = manager;
ensureContent();
}
public void setup(Context context, FragmentManager manager, int containerId) {
ensureHierarchy(context); // Ensure views required by super.setup()
super.setup();
mContext = context;
mFragmentManager = manager;
mContainerId = containerId;
ensureContent();
mRealTabContent.setId(containerId);
// We must have an ID to be able to save/restore our state. If
// the owner hasn't set one at this point, we will set it ourself.
if (getId() == View.NO_ID) {
setId(android.R.id.tabhost);
}
}
private void ensureContent() {
if (mRealTabContent == null) {
mRealTabContent = (FrameLayout) findViewById(mContainerId);
if (mRealTabContent == null) {
throw new IllegalStateException(
"No tab content FrameLayout found for id "
+ mContainerId);
}
}
}
@Override
public void setOnTabChangedListener(OnTabChangeListener l) {
mOnTabChangeListener = l;
}
public void addTab(TabSpec tabSpec, Class<?> clss, Bundle args) {
tabSpec.setContent(new DummyTabFactory(mContext));
String tag = tabSpec.getTag();
TabInfo info = new TabInfo(tag, clss, args);
if (mAttached) {
// If we are already attached to the window, then check to make
// sure this tab's fragment is inactive if it exists. This shouldn't
// normally happen.
info.fragment = mFragmentManager.findFragmentByTag(tag);
if (info.fragment != null && !info.fragment.isDetached()) {
FragmentTransaction ft = mFragmentManager.beginTransaction();
// ft.detach(info.fragment);
ft.hide(info.fragment);
ft.commit();
}
}
mTabs.add(info);
addTab(tabSpec);
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
String currentTab = getCurrentTabTag();
// Go through all tabs and make sure their fragments match
// the correct state.
FragmentTransaction ft = null;
for (int i = 0; i < mTabs.size(); i++) {
TabInfo tab = mTabs.get(i);
tab.fragment = mFragmentManager.findFragmentByTag(tab.tag);
// if (tab.fragment != null && !tab.fragment.isDetached()) {
if (tab.fragment != null) {
if (tab.tag.equals(currentTab)) {
// The fragment for this tab is already there and
// active, and it is what we really want to have
// as the current tab. Nothing to do.
mLastTab = tab;
} else {
// This fragment was restored in the active state,
// but is not the current tab. Deactivate it.
if (ft == null) {
ft = mFragmentManager.beginTransaction();
}
// ft.detach(tab.fragment);
ft.hide(tab.fragment);
}
}
}
// We are now ready to go. Make sure we are switched to the
// correct tab.
mAttached = true;
ft = doTabChanged(currentTab, ft);
if (ft != null) {
ft.commitAllowingStateLoss();
mFragmentManager.executePendingTransactions();
}
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
mAttached = false;
}
@Override
protected Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
SavedState ss = new SavedState(superState);
ss.curTab = getCurrentTabTag();
return ss;
}
@Override
protected void onRestoreInstanceState(Parcelable state) {
SavedState ss = (SavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
setCurrentTabByTag(ss.curTab);
}
@Override
public void onTabChanged(String tabId) {
if (mAttached) {
FragmentTransaction ft = doTabChanged(tabId, null);
if (ft != null) {
ft.commit();
}
}
if (mOnTabChangeListener != null) {
mOnTabChangeListener.onTabChanged(tabId);
}
}
private FragmentTransaction doTabChanged(String tabId,
FragmentTransaction ft) {
TabInfo newTab = null;
for (int i = 0; i < mTabs.size(); i++) {
TabInfo tab = mTabs.get(i);
if (tab.tag.equals(tabId)) {
newTab = tab;
}
}
if (newTab == null) {
throw new IllegalStateException("No tab known for tag " + tabId);
}
if (mLastTab != newTab) {
if (ft == null) {
ft = mFragmentManager.beginTransaction();
}
if (mLastTab != null) {
if (mLastTab.fragment != null) {
// ft.detach(mLastTab.fragment);
ft.hide(mLastTab.fragment);
}
}
if (newTab != null) {
if (newTab.fragment == null) {
newTab.fragment = Fragment.instantiate(mContext,
newTab.clss.getName(), newTab.args);
ft.add(mContainerId, newTab.fragment, newTab.tag);
} else {
// ft.attach(newTab.fragment);
ft.show(newTab.fragment);
}
}
mLastTab = newTab;
}
return ft;
}
}
打开activity_main.xml,先写入布局文件
<?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:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.example.pc.myshop.MainActivity">
//这里是真正放Fragment的。
<FrameLayout
android:id="@+id/realtabcontent"
android:layout_width="match_parent"
android:layout_height="0dip"
android:layout_weight="1"></FrameLayout>
<com.example.pc.myshop.weiget.FragmentTabHost
android:id="@android:id/tabhost"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#ffffff">
//这里FrameLayout是官方要求这样写的,那就要按要求写就好了,另外要注意id必须写成跟下面一样
<FrameLayout
android:id="@android:id/tabcontent"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="0"></FrameLayout>
</com.example.pc.myshop.weiget.FragmentTabHost>
</LinearLayout>
上面布局文件中,第一个FrameLayout是用于装载Fragment的,第二个FrameLayout是官方要求这些子写的,我们按照要求写就可以了。
MainActivity中的具体代码:
package com.example.pc.myshop;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TabHost;
import android.widget.TextView;
import com.example.pc.myshop.bean.Tab;
import com.example.pc.myshop.fragment.CartFragment;
import com.example.pc.myshop.fragment.CategoryFragment;
import com.example.pc.myshop.fragment.HomeFragment;
import com.example.pc.myshop.fragment.HotFragment;
import com.example.pc.myshop.fragment.MineFragment;
import com.example.pc.myshop.weiget.FragmentTabHost;
import java.util.ArrayList;
import java.util.List;
//这里我们说过要用FragmentTabHost就要让Activity继承FragmentActivity的,AppCompatActivity也是继承自FragmentActivity的
public class MainActivity extends AppCompatActivity {
private FragmentTabHost mTabhost;
private LayoutInflater mInflater;
//这里用于装载每一个分布中的Tab
private List<Tab> mTabs=new ArrayList<>(5);
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//初始化数据
initTab();
}
private void initTab() {
//新建5个分页,并且添加到List中去,其中图标用了selector进行状态选择,下面会有介绍到
Tab tab_home=new Tab(R.string.home,R.drawable.selector_icon_home,HomeFragment.class);
Tab tab_hot=new Tab(R.string.hot,R.drawable.selector_icon_hot,HotFragment.class);
Tab tab_category=new Tab(R.string.category,R.drawable.selector_icon_category, CategoryFragment.class);
Tab tab_cart=new Tab(R.string.cart,R.drawable.selector_icon_cart,CartFragment.class);
Tab tab_mine=new Tab(R.string.mine,R.drawable.selector_icon_mine,MineFragment.class);
mTabs.add(tab_home);
mTabs.add(tab_hot);
mTabs.add(tab_category);
mTabs.add(tab_cart);
mTabs.add(tab_mine);
mTabhost= (FragmentTabHost) this.findViewById(android.R.id.tabhost);
//调用setup()方法,设置FragmentManager,以及指定用于装载Fragment的布局容器(第三个参数),也就是主布局上面的那个FrameLayout
mTabhost.setup(this, getSupportFragmentManager(), R.id.realtabcontent);
for (Tab tab:mTabs){
//新建5个TabSpec,并且设置好Indicator
TabHost.TabSpec tabSpec=mTabhost.newTabSpec(getString(tab.getTitle())) ;
//setIndicator方法里面放入的是一个view
tabSpec.setIndicator(buildIndicator(tab));
//把每个TabSpec添加到FragmentTabHost里面去
mTabhost.addTab(tabSpec, tab.getFragment(), null);
}
//去掉自带的分隔线
mTabhost.getTabWidget().setShowDividers(LinearLayout.SHOW_DIVIDER_NONE);
//设置主页为默认页
mTabhost.setCurrentTab(0);
}
private View buildIndicator(Tab tab){
mInflater=LayoutInflater.from(this);
View view=mInflater.inflate(R.layout.tab_indicator, null);
ImageView img=(ImageView)view.findViewById(R.id.icon_tab);
TextView text=(TextView)view.findViewById(R.id.txt_indicator);
img.setImageResource(tab.getIcon());
text.setText(tab.getTitle());
return view;
}
}
封装Tab类代码:
package com.example.pc.myshop.bean;
/**
* Created by pc on 2018/3/30.
*/
public class Tab {
private int title;
private int icon;
private Class fragment;
public Tab(int title, int icon, Class fragment) {
this.title = title;
this.icon = icon;
this.fragment = fragment;
}
public int getTitle() {
return title;
}
public void setTitle(int title) {
this.title = title;
}
public int getIcon() {
return icon;
}
public void setIcon(int icon) {
this.icon = icon;
}
public Class getFragment() {
return fragment;
}
public void setFragment(Class fragment) {
this.fragment = fragment;
}
}
每个TabSpec中的Indicator都有一个ImageView 和一个TextView
indicator布局:tab_indicator.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:gravity="center"
android:orientation="vertical"
android:paddingBottom="3dp">
<ImageView
android:id="@+id/icon_tab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="5dp" />
<TextView
android:id="@+id/txt_indicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:textColor="@color/selector_tab_text" />
</LinearLayout>
HomeFragment代码:
package com.example.pc.myshop.fragment;
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.example.pc.myshop.R;
/**
* Created by pc on 2018/3/30.
*/
public class HomeFragment extends Fragment {
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_home,container,false);
}
}
HomeFragment布局文件:fragment_home.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:text="主页"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
另外HotFragment、CategoryFragment、CartFragment、MineFragment按照上面的炮制就可以了。
Selector背景选择器,在drawable文件夹下创建selector_icon_home.xml和其他四个xml
selector_icon_home.xml代码:(其他四个xml照着炮制就可以了)
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<!--no focused states-->
<item android:state_focused="false" android:state_selected="false" android:state_pressed="false" android:drawable="@mipmap/icon_home"/>
<item android:state_focused="false" android:state_selected="true" android:state_pressed="false" android:drawable="@mipmap/icon_home_press"/>
<!--Focused stats-->
<item android:state_focused="true" android:state_selected="false" android:state_pressed="false" android:drawable="@mipmap/icon_home_press"/>
<item android:state_focused="true" android:state_selected="true" android:state_pressed="false" android:drawable="@mipmap/icon_home_press"/>
<!--Pressed-->
<item android:state_selected="true" android:state_pressed="true" android:drawable="@mipmap/icon_home_press"/>
<item android:state_pressed="true" android:drawable="@mipmap/icon_home_press"/>
</selector>
在color文件夹下添加selector_tab_text.xml文件,用于改变不同选择状态下的文字颜色
selector_tab_text.xml代码:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_selected="true" android:color="#eb4f38" />
<item android:state_active="true" android:color="#eb4f38"/>
<item android:state_selected="false" android:color="#a9b7b7"/>
<item android:state_active="false" android:color="#a9b7b7"/>
</selector>
最终效果图:
图标文件