TabHost
从可视化UI编辑器中拖一个TabHost:
<TabHost
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/tabHost">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TabWidget
android:id="@android:id/tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</TabWidget>
<FrameLayout
android:id="@android:id/tabcontent"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!--也可以通过tabHost.addTab(tabSpec)添加tab内容-->
<LinearLayout
android:id="@+id/tab1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
</LinearLayout>
<LinearLayout
android:id="@+id/tab2"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
</LinearLayout>
</FrameLayout>
</LinearLayout>
</TabHost>
通过TabHost可以简单的实现tab页签效果:
tabHost = (TabHost) findViewById(android.R.id.tabhost);
tabHost.setup(); //如果TabActivity的话,直接getTabHost(),无需setup。
TabSpec tabSpec = tabHost.newTabSpec(tag).setIndicator(indicator).setContent(viewId);
tabHost.addTab(tabSpec);
其中TabSpec的setIndicator和setContent分别重载了三个不同的方法:
public TabSpec setIndicator(CharSequence label)
public TabSpec setIndicator(CharSequence label, Drawable icon)
public TabSpec setIndicator(View view)
public TabSpec setContent(int viewId)
public TabSpec setContent(TabContentFactory contentFactory)
public TabSpec setContent(Intent intent)
当点击tab页签头时,页签的内容发生变化,可以通过设置TabHost.OnTabChangedListener来监听这个变化:
tabHost.setOnTabChangedListener(new OnTabChangeListener() {
@Override
public void onTabChanged(String tabId) {
// do something
}
});
ViewPager
ViewPager是v4包定义的view,布局xml定义:
<android.support.v4.view.ViewPager
android:id="@+id/pager"
android:layout_width="match_parent"
android:layout_height="match_parent">
</android.support.v4.view.ViewPager>
使用ViewPager需要设置它的PagerAdapter,可以设置ViewPager.OnPageChangeListener来监听page页面的切换:
adapter = new MyPagerAdapter(getSupportFragmentManager());
viewPager = (ViewPager) findViewById(R.id.pager);
viewPager.setAdapter(adapter);
viewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageSelected(int position) {
// do something
}
@Override
public void onPageScrollStateChanged(int state) {
}
});
在实现ViewPager的adapter时,可以选择继承PagerAdapter、FragmentPagerAdapter和FragmentStatePagerAdapter。
如果tab是一个Fragment时,选择后两者;如果tabs是不同的Fragment时,选择FragmentPagerAdapter;如果是相同的Fragment时选择FragmentStatePagerAdapter。
至于FragmentPagerAdapter和FragmentStatePagerAdapter的详细区别,参考这里
TabHost和ViewPager结合使用
具体参考如下代码,代码有个小bug,是由于布局引起的,会在后面提出两种解决办法:
activity布局文件:activity_my_tab.xml
<?xml version="1.0" encoding="utf-8"?>
<TabHost xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/tabhost"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TabWidget
android:id="@android:id/tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</TabWidget>
<FrameLayout
android:id="@android:id/tabcontent"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<android.support.v4.view.ViewPager
android:id="@+id/pager"
android:layout_width="match_parent"
android:layout_height="match_parent">
</android.support.v4.view.ViewPager>
</FrameLayout>
</LinearLayout>
</TabHost>
activity类文件:MyTabActivity.java
public class MyTabActivity extends FragmentActivity {
private MyPagerAdapter adapter;
private ViewPager viewPager;
private TabHost tabHost;
public static final String[] tabs = {"Tab0", "Tab1", "Tab2", "Tab3", "Tab4" };
@Override
protected void onCreate(Bundle arg0) {
super.onCreate(arg0);
setContentView(R.layout.activity_my_tab);
initTabs();
initPager();
}
private void initTabs() {
tabHost = (TabHost) findViewById(android.R.id.tabhost);
tabHost.setup();
for (int i = 0; i < tabs.length; i++) {
TabSpec tabSpec = tabHost.newTabSpec(tabs[i]).setIndicator(tabs[i])
.setContent(android.R.id.tabcontent);
tabHost.addTab(tabSpec);
}
tabHost.setOnTabChangedListener(new OnTabChangeListener() {
@Override
public void onTabChanged(String tabId) {
if (viewPager != null) {
viewPager.setCurrentItem(tabHost.getCurrentTab());
}
}
});
}
private void initPager() {
adapter = new MyPagerAdapter(getSupportFragmentManager());
viewPager = (ViewPager) findViewById(R.id.pager);
viewPager.setAdapter(adapter);
viewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageSelected(int position) {
if (viewPager != null) {
tabHost.setCurrentTab(position);
}
}
@Override
public void onPageScrollStateChanged(int state) {
}
});
}
public class MyPagerAdapter extends FragmentPagerAdapter {
public MyPagerAdapter(FragmentManager fm) {
super(fm);
}
@Override
public Fragment getItem(int i) {
return MyFragment.newInstance("Tab", Integer.toString(i));
}
@Override
public int getCount() {
return tabs.length;
}
@Override
public CharSequence getPageTitle(int position) {
return "PageTitle " + (position);
}
}
public static class MyFragment extends Fragment {
private static final String ARG_PARAM1 = "param1";
private static final String ARG_PARAM2 = "param2";
private String mParam1;
private String mParam2;
public static MyFragment newInstance(String param1, String param2) {
MyFragment fragment = new MyFragment();
Bundle args = new Bundle();
args.putString(ARG_PARAM1, param1);
args.putString(ARG_PARAM2, param2);
fragment.setArguments(args);
return fragment;
}
public MyFragment() {
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
mParam1 = getArguments().getString(ARG_PARAM1);
mParam2 = getArguments().getString(ARG_PARAM2);
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_my, container,
false);
Bundle args = getArguments();
((TextView) rootView.findViewById(R.id.tv)).setText(mParam1 + " : " + mParam2);
return rootView;
}
}
}
注意:以上的Fragment和FragmentManager类均是v4包的
tab中所显示的fragment的布局文件:fragment_my.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:textAppearance="?android:attr/textAppearanceLarge"
android:gravity="center"
android:id="@+id/tv" />
</LinearLayout>
运行后显示:
bug出现,初次显示时tab的内容并没有显示出来
只有点击其他页签再返回,该页签才显示出来。
网上有解决办法是:
@Override
protected void onCreate(Bundle arg0) {
super.onCreate(arg0);
setContentView(R.layout.activity_my_tab);
initTabs();
initPager();
tabHost.setCurrentTab(1);
tabHost.setCurrentTab(0);
}
虽然这种办法解决了bug,但却做了无用功,只有加载其他页签才能返回显示第一个页签。
该问题其实由于初次显示时,TabHost里面内容FrameLayout“遮挡”了ViewPager的显示。所以我的解决方法是:
- 方法1:
调整activity_my_tab.xml,将FrameLayout包含ViewPager改为:ViewPager包含FragmLayout,高度属性也调换了。
<android.support.v4.view.ViewPager
android:id="@+id/pager"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<FrameLayout
android:id="@android:id/tabcontent"
android:layout_width="match_parent"
android:layout_height="match_parent">
</FrameLayout>
</android.support.v4.view.ViewPager>
- 方法2:
- 调整activity_my_tab.xml,将FrameLayout包含ViewPager改为:ViewPager和FragmLayout并列为兄弟,并修改它们的属性
- 修改MyTabActivity.java ,增加一个实现了 TabHost.TabContentFactory接口的静态内部类DummyTabContent,并将initTabs方法中的setContent(android.R.id.tabcontent)改为setContent(dummyTabContent),具体参考下面修改后的代码。
<FrameLayout
android:id="@android:id/tabcontent"
android:layout_width="0dp"
android:layout_height="0dp" >
</FrameLayout>
<android.support.v4.view.ViewPager
android:id="@+id/pager"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
</android.support.v4.view.ViewPager>
private void initTabs() {
tabHost = (TabHost) findViewById(android.R.id.tabhost);
tabHost.setup();
// 当TabHost和ViewPager组合使用时,如果ViewPager放在tabcontent的FrameLayout中,
// tab第一次显示时,将无法显示它的子视图ViewPager。因此,在布局中,将FrameLayout显示为宽高为0dp,
// ViewPager作为它的兄弟视图。
// 生成一个假的tab内容,填充id为android.R.id.tabcontent的FrameLayout,
// 真正的tab内容显示在它的兄弟视图ViewPager。
TabHost.TabContentFactory dummyTabContent = new DummyTabContent(this);
for (int i = 0; i < tabs.length; i++) {
TabSpec tabSpec = tabHost.newTabSpec(tabs[i]).setIndicator(tabs[i])
.setContent(dummyTabContent);
tabHost.addTab(tabSpec);
}
tabHost.setOnTabChangedListener(new OnTabChangeListener() {
@Override
public void onTabChanged(String tabId) {
if (viewPager != null) {
viewPager.setCurrentItem(tabHost.getCurrentTab());
}
}
});
}
private static final class DummyTabContent implements TabHost.TabContentFactory{
private Context mContext;
public DummyTabContent(Context context){
mContext = context;
}
@Override
public View createTabContent(String tag) {
View v = new View(mContext);
v.setMinimumWidth(0);
v.setMinimumHeight(0);
return v;
}
}
修改后显示:
Tab页签头的位置
如果想将tab页签头显示在下方,则只需将TabWidget和ViewPager的位置调换,此时页签头和页签内容之间没有蓝条隔开,Android官方是推荐页签头显示在上方的。效果如下:
关于FragmentTabHost和ViewPager
不建议FragmentTabHost和ViewPager一同使用,因为FragmentTabHost会替我们创建并管理Fragment,而使用ViewPager需要实现PagerAdapter,实现PagerAdapter、FragmentPagerAdapter和FragmentStatePagerAdapter需要我们自己创建并返回一个Fragment,这就意味着一个要显示的Fragment的被创建了两次。
FragmentTabHost使用方法
public void addTab(TabHost.TabSpec tabSpec, Class<?> clss, Bundle args)
添加Fragment的class,并在方法private FragmentTransaction doTabChanged(String tabId, FragmentTransaction ft)
中创建class对应的Fragment。具体可参考FragmentTabHost的源码。
FragmentPagerAdapter和FragmentStatePagerAdapter会为我们管理Fragment,但是需要我们自己创建Fragment,即复写方法public Fragment getItem(int position)
。