在早期 Android 開發中,TabHost 配合 TabActivity 是實現底部導航或頂部標籤頁切換的經典方案。雖然現在推薦使用 ViewPager + Fragment 或 BottomNavigationView,但理解 TabHost 的運作機制對維護老專案或學習 Android 視圖體系仍有幫助。本文詳細解析兩種主流實現方式:純代碼動態構建與 XML 佈局靜態定義。
TabHost。TabSpec。public class MainTabActivity extends TabActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
TabHost tabHost = getTabHost(); // 獲取 TabHost 實例,內部已調用 setContentView
Resources res = getResources();
// 標籤 1:藝術
Intent intentArt = new Intent(this, ArtActivity.class);
TabHost.TabSpec specArt = tabHost.newTabSpec("art")
.setIndicator("藝術", res.getDrawable(R.drawable.tab_selector))
.setContent(intentArt);
tabHost.addTab(specArt);
// 標籤 2:音樂
Intent intentMusic = new Intent(this, MusicActivity.class);
TabHost.TabSpec specMusic = tabHost.newTabSpec("music")
.setIndicator("音樂", res.getDrawable(R.drawable.tab_selector))
.setContent(intentMusic);
tabHost.addTab(specMusic);
// 標籤 3:體育
Intent intentSport = new Intent(this, SportActivity.class);
TabHost.TabSpec specSport = tabHost.newTabSpec("sport")
.setIndicator("體育", res.getDrawable(R.drawable.tab_selector))
.setContent(intentSport);
tabHost.addTab(specSport);
// 設置默認顯示第二個標籤
tabHost.setCurrentTab(1);
}
}
在 res/drawable/tab_selector.xml 中定義選中/未選中圖標切換:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="https://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/tab_selected" android:state_selected="true"/>
<item android:drawable="@drawable/tab_unselected"/>
</selector>
所有作為標籤內容的 Activity 必須在清單文件中聲明:
<activity android:name=".MainTabActivity" android:theme="@android:style/Theme.NoTitleBar">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity android:name=".ArtActivity"/>
<activity android:name=".MusicActivity"/>
<activity android:name=".SportActivity"/>
根佈局必須是 FrameLayout,內部放置各標籤頁的具體視圖:
<FrameLayout xmlns:android="https://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- 第一個標籤內容 -->
<LinearLayout android:id="@+id/tab_content_one"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<EditText android:layout_width="match_parent" android:layout_height="wrap_content" android:text="內容一"/>
<Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="按鈕"/>
</LinearLayout>
<!-- 第二個標籤內容 -->
<LinearLayout android:id="@+id/tab_content_two"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<AnalogClock android:layout_width="wrap_content" android:layout_height="wrap_content"/>
</LinearLayout>
<!-- 第三個標籤內容 -->
<LinearLayout android:id="@+id/tab_content_three"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<RadioGroup android:layout_width="wrap_content" android:layout_height="wrap_content">
<RadioButton android:text="選項一"/>
<RadioButton android:text="選項二"/>
</RadioGroup>
</LinearLayout>
</FrameLayout>
public class TabHostXmlActivity extends TabActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
TabHost tabHost = getTabHost();
// 將佈局文件 inflate 到 TabHost 的內容容器中
LayoutInflater.from(this).inflate(R.layout.main_tabs, tabHost.getTabContentView(), true);
tabHost.addTab(tabHost.newTabSpec("one").setIndicator("A").setContent(R.id.tab_content_one));
tabHost.addTab(tabHost.newTabSpec("two").setIndicator("B", getResources().getDrawable(R.drawable.icon)).setContent(R.id.tab_content_two));
tabHost.addTab(tabHost.newTabSpec("three").setIndicator("C", getResources().getDrawable(R.drawable.icon)).setContent(R.id.tab_content_three));
}
}
| 維度 | 動態代碼方式 | XML 佈局方式 |
|---|---|---|
| 靈活性 | 高,可動態決定標籤數量和內容 | 靜態,佈局固定 |
| 分層清晰度 | 每個標籤獨立一個 Activity,邏輯解耦好 | 所有內容在一個 Activity,視圖層級較深 |
| 性能 | 啟動多個 Activity 有開銷 | 單 Activity 內切換 View,較輕量 |
| 適用場景 | 標籤頁業務邏輯複雜、獨立性強 | 標籤頁內容簡單、共用數據多 |
Fragment + ViewPager 或 BottomNavigationView 實現。android:theme="@android:style/Theme.NoTitleBar",否則系統默認標題欄會遮蓋 TabHost。res/drawable/ 目錄下,而非 drawable-hdpi 等密度目錄。setContent(R.id.xxx) 對應的 ID 必須是 FrameLayout 直接子視圖的 ID。TabHost 體現了模板方法模式:框架處理標籤切換邏輯,開發者只需提供具體內容。雖然已過時,但其「容器-規格-內容」的設計思想在現代組件中依然普遍存在。