【Android商城】一、利用FragmentTabHost+Fragment实现底部导航栏

常见的底部导航栏实现方式有三种:

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>


最终效果图:




图标文件

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值