FragmentTabHost + Fragment 实现底部菜
一、实现效果
图一利用FragmentTabHost实现底部菜单,在该底部菜单中,包括了5个TabSpec,每个TabSpec中包含了一个View,而View中包含了一个ImageView和一个TextView。而要在TabSpec中显示这个View,就需要同过indicator来设置,eg:TabSpec.setIndicator(view);
二、动手实现
1、首先什么都不用说,先将准备好的5个TabSpec中的图片复制到AS中Android目录下的mipmap的文件夹中xxhdpi的文件夹下。这里需要注意的是,每个TabSpec中应该都包含两个图标,一个是正常状态的图标(灰色的),一个是该TabSpec被选中后的图标(红色的)。另外,为什么把图片放在xxhdpi文件夹下而不是hdpi文件夹下?因为放在xxhdpi文件下的图片显示效果会比较细腻些, 图标也显得小一些。如果是把图片放到hdpi文件夹下显示出来的图标就会比较粗糙,显得比较大一个,显示效果不够好。
2、写布局文件。在布局中需要使用到FragmentTabHost,在Android开发者文档中搜索FragmentTabHost,显示两个,一个是android.support.v4.app.FragmentTabHost,另外一个是android.support.v13.app.FragmentTabHost。这里使用的是v4包下的FragmentTabHost。在使用前,需要添加相关的依赖,这里导入的是com.android.support:support-v4:24.2.0。如果在就可以在actiivity-main布局文件中写布局文件。其中需要注意的是,FragmentTabHost的id是需要使用安卓自带的id,“@android:id/tabhost”,而FragmentTabHost中的FrameLayout也需要使用安卓自带的id,tabcontent。另外一点是,这个文件中有两个FrameLayout,因为实现的是底部菜单,所以在FragmentTabHost中的FrameLayout是设置为0的,而FragmentTabHost真正需要显示的内容是在上面的FrameLayout中进行显示的。布局文件如下所示:
<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=".MainActivity">
<FrameLayout
android:id="@+id/realcontent"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
</FrameLayout>
<android.support.v4.app.FragmentTabHost
android:id="@android:id/tabhost"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#ffffff" >
<FrameLayout
android:id="@android:id/tabcontent"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="0" >
</FrameLayout>
</android.support.v4.app.FragmentTabHost></LinearLayout>
3、在MainActivity中编写逻辑。首先因为我们是使用FragmentTabHost+Fragment来实现的,所以需要让MainActivity继承FragmentActivity,注意继承的这个FragmentActivity需要实在v4包下的。然后需要定义个FragmentTabHost,mTabHost,然后通过findViewById在视图中找到这个FragmentTabHost。然后需要调用 mTabHost的setup()方法,该方法一般带有3个参数,
mTabHost.setup(this, getSupportFragmentManager(), R.id.realcontent);,其中注意的是第三个参数,是我需要真正使用的FrameLayout的id。该方法的详细介绍可以查阅Android开发者网站。接下来的步骤就是利用mTabHost new一个TabSpec出来。然后利用TabSpec,setIndicator,最后就是讲这个TabSpec加到mTabHost中。
其中setIncator方法中需要一个View,因此需要编写一个tab_indicator的xml文件,根据图一发现,其实只是一个ImageView,然后在ImageView下放一个TextView。编写起来也相对简单。然后回到MainActivity中,利用LayoutInflater,将tab_indicator这个布局文件引入进来,View view = mInflater.inflate(R.layout.tab_indicator,null),然后通过view.findViewById 找到其中的ImageView和TextView,接着就对这两个View进行赋值。
此外,将TabSpec加到TabHost中是调用mTabHost.addTab(tabSpec,HomeFragment.class, null);
由此可以看出,还需要一个HomeFragment类,此外这个类还需要一个布局文件,这里主要是实现底部菜单,因此这个布局文件只是写了个TextView。这个文件和这个代码如下:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Home"
android:textSize="23sp"
android:textStyle="bold" />
</LinearLayout>
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);
}
}
3、对代码进行简单的优化
通过以上步骤就可以在底部菜单中实现一个图标,但是现在是要实现5个图标的菜单。比较简单、冗余的方法是,将刚才Activity中的代码适当的复制4份。但是这样会让代码显得冗余,而且不利于后期的维护,因此这里我们利用面向对象思想,对其中的一些经常使用到的,会发生改变的内容进行封装,将其封装成一个类,然后实例化这个类的对象,再通过调用这类的方法,获取相应的变量。这里我们定义一个Tab类。
public class Tab {
private int Image;
private int Text;
private Class Fragment;
public Tab(int image, int text, Class fragment) {
Image = image;
Text = text;
Fragment = fragment; }
public int getImage() { return Image; }
public void setImage(int image) { Image = image; }
public int getText() { return Text; }
public void setText(int text) { Text = text; }
public Class getFragment() { return Fragment; }
public void setFragment(Class fragment) { Fragment = fragment; }
}
然后修改MainActivity中的内容
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentTabHost;
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.lyh.mymalll5.bean.Tab;
import com.lyh.mymalll5.fragment.CartFragment;
import com.lyh.mymalll5.fragment.DiscoverFragment;
import com.lyh.mymalll5.fragment.HomeFragment;
import com.lyh.mymalll5.fragment.HotFragment;
import com.lyh.mymalll5.fragment.UserFragment;
import java.util.ArrayList;
public class MainActivity extends FragmentActivity {
private FragmentTabHost mTabHost;
private LayoutInflater mInflater;
private ArrayList<Tab> mTabs= new ArrayList<Tab>(5);
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initTab();
}
private void initTab() {
//实例化5个Tab类的对象
Tab Tab_home = new Tab(R.drawable.selector_home,R.string.home,HomeFragment.class);
Tab Tab_hot = new Tab(R.drawable.selector_hot,R.string.hot, HotFragment.class);
Tab Tab_discover = new Tab(R.drawable.selector_discover,R.string.discover, DiscoverFragment.class);
Tab Tab_cart = new Tab(R.drawable.selector_cart,R.string.cart, CartFragment.class);
Tab Tab_user = new Tab(R.drawable.selector_user,R.string.user, UserFragment.class);
//将这5个对象加到一个List中
mTabs.add(Tab_home);
mTabs.add(Tab_hot);
mTabs.add(Tab_discover);
mTabs.add(Tab_cart);
mTabs.add(Tab_user);
mTabHost = (FragmentTabHost) findViewById(android.R.id.tabhost);
mTabHost.setup(this, getSupportFragmentManager(), R.id.realcontent);
mInflater = LayoutInflater.from(this);
//通过循环实例化一个个TabSpec
//并调用其中setIndicator方法
//然后将TabSpec加到TabHost中
for (Tab tab :mTabs) {
TabHost.TabSpec tabSpec = mTabHost.newTabSpec(String.valueOf(tab.getText()));
tabSpec.setIndicator(buildView(tab));
mTabHost.addTab(tabSpec,tab.getFragment(), null);
}
//通过这行代码可以去除掉底部菜单5个图表之间的分割线
mTabHost.getTabWidget().setShowDividers(LinearLayout.SHOW_DIVIDER_NONE); }
//设置Indicator中的View
private View buildView(Tab tab) {
View view = mInflater.inflate(R.layout.tab_indicator,null);
ImageView Tab_img = (ImageView) view.findViewById(R.id.tab_img);
TextView Tab_txt = (TextView) view.findViewById(R.id.tab_txt);
Tab_img.setBackgroundResource(tab.getImage());
Tab_txt.setText(tab.getText());
return view;
}
}
4、简单的优化。通过上面步骤后,基本上可以实现底部菜单的功能,但是仅仅如此的话,还是不够全面,因为当点击图标的时候,图标和文字都还不会变成红色。首先设置图标。
在Drawable文件下编写5个xml文件,这5个文件基本上是一样,都是设置成当被点的时候改变图标,使用准备好的红色的图标。这里只给出其中的一个例子。
selector_home.xml
<selector xmlns:android="http://schemas.android.com/apk/res/android">
//被选中的时候的图标
<item android:state_selected="true" android:drawable="@mipmap/icon_home_press"/>
//正常状态下的图标
<item android:drawable="@mipmap/icon_home"/>
</selector>
然后在实例化Tab类的对象的时候,在其函数中Image参数栏使用R.drawable.selector_home.xml文件就可。
而文字的该变更为简单,在res目录下,新增一个color文件夹,在该文件夹下增添一个text_color.xml文件,在其中编写被选中时的颜色的变化,这里只需编写一个即可。
<selector xmlns:android="http://schemas.android.com/apk/res/android">
//被选中时候为红色
<item android:color="#eb4f38" android:state_selected="true"/>
//正常情况下为灰色
<item android:color="#a9b7b7" android:state_selected="false"/>
</selector>
然后在tab_indicator.xml文件中的TextView控件中声明textColor属性为@color/text_color即可。
最终实现的效果如下:
图二