下面的方法又被google丟掉了, 如果想要換成新版的TabHost請參考
如何使用TabLayout
以及進階應用
如何使用Toolbar+DrawerLayout+Tab+SwipRefresh
在之前如何使用Tab(分頁)的教學,
會產生很多問題(例如Back問題、切換activity的時候狀態儲存問題…等等),
因此Android開發了一個新的類別Fragment,
利用這個類別來開發TabActivity, 可以解決掉上述那些問題,
Fragment是3.0以後才出現的類別庫, 因此要下載官方的support library.
如果要下載官方的Support library,
你要先打開eclipse->window->Android SDK Manager-> 選擇Extra的Android Support下載
然後在你的android sdk資料夾之下,看到多出一個extras的資料夾, 以我的資料夾為例子
\android-sdk-windows\extras\android\support\v4\android-support-v4.jar
, 這個就是我們所需要的support library.
當我們建立一個專案的時候, 只要在專案下面在建立一個lib, 然後把android-support-v4.jar加進去,
接著點選專案右鍵, 選擇properties->Libraries->Add Library 將lib資料夾內的android-support-v4.jar加進去。
記得要到Order and Export那邊將android-support-v4.jar打勾。
如此一來就可以使用這個library的所有類別了。
官方說明
我們可以看到這個類別庫大致上有以下這幾類
Fragment
FragmentManager
FragmentTransaction
ListFragment
DialogFragment
LoaderManager
Loader
AsyncTaskLoader
CursorLoader
另外這邊有一個重點就是, 為了讓之前Android版本能夠跑我們的程式,
可以在AndroidManifest.xml上面進行設定。
<uses-sdk android:minSdkVersion="4" />
這樣一來, 無論手機上的android版本多舊, 我們仍然可以跑我們要的程式。
接下來進入我們的重點了, 如何使用TabActivity,
其實在我們下載android-support-v4.jar裡面, 就已經存在很多範例了,
但是為了簡化他的範例, 因此我用官網所提供的範例進行簡化。
首先新增一個fragment_tabs.xml
<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:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="@android:id/tabcontent"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="0"/>
<FrameLayout
android:id="@+android:id/realtabcontent"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/>
<HorizontalScrollView
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:scrollbars="none"
android:id="@+id/scroller">
<TabWidget
android:id="@android:id/tabs"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="0"/>
</HorizontalScrollView>
</LinearLayout>
</TabHost>
這邊我們建立一個TabHost的元件, 然後我想讓TabWidget能夠呈現在下方,
因此用LinearLayout包起來,
讓上方為一個FrameLayout(橙色部分), 下方才是我們的TabWidget(藍色部分)。
首先要建立一個FragmentActivity的子類別,
public class FragmentTabs extends FragmentActivity{
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
}
基本上跟一般的Activity沒什麼不一樣, 也是先建立一個onCreate的方法。
接著我們就把TabWidget元件從xml取出, 並且初始化。
public class FragmentTabs extends FragmentActivity{
private TabHost mTabHost;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.fragment_tabs);
mTabHost = (TabHost)findViewById(android.R.id.tabhost);
mTabHost.setup();
}
}
在這邊要注意一下, 在之前我們的類別是繼承TabActivity,
因此我們常常只需要getTabHost(); 就可以取得TabHost的物件了,
原因出在我們的TabActivity裡面就對TabHost元件初始化了,
但是現在是從xml取出物件, 因此要多加上setup()這個方法,
才能夠進行add的動作。
官網說法
Call setup() before adding tabs if loading TabHost using findViewById(). However: You do not need to call setup() after getTabHost() in TabActivity.
可以參考看看官方文件
接著我們必須宣告一個TabManager來處理當切換分頁的時候, 必須進行的一些動作。
類別長這樣:
public class TabManager implements TabHost.OnTabChangeListener {
private final FragmentActivity mActivity;
private final TabHost mTabHost;
private final int mContainerId;
private final HashMap<String, TabInfo> mTabs = new HashMap<String, TabInfo>();
TabInfo mLastTab;
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 TabHost.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;
}
}
public TabManager(FragmentActivity activity, TabHost tabHost, int containerId) {
mActivity = activity;
mTabHost = tabHost;
mContainerId = containerId;
mTabHost.setOnTabChangedListener(this);
}
public void addTab(TabHost.TabSpec tabSpec, Class<?> clss, Bundle args) {
tabSpec.setContent(new DummyTabFactory(mActivity));
String tag = tabSpec.getTag();
TabInfo info = new TabInfo(tag, clss, args);
info.fragment = mActivity.getSupportFragmentManager().findFragmentByTag(tag);
if (info.fragment != null && !info.fragment.isDetached()) {
FragmentTransaction ft = mActivity.getSupportFragmentManager().beginTransaction();
ft.detach(info.fragment);
ft.commit();
}
mTabs.put(tag, info);
mTabHost.addTab(tabSpec);
}
@Override
public void onTabChanged(String tabId) {
TabInfo newTab = mTabs.get(tabId);
if (mLastTab != newTab) {
FragmentTransaction ft = mActivity.getSupportFragmentManager().beginTransaction();
if (mLastTab != null) {
if (mLastTab.fragment != null) {
ft.detach(mLastTab.fragment);
}
}
if (newTab != null) {
newTab.fragment = Fragment.instantiate(mActivity,
newTab.clss.getName(), newTab.args);
ft.add(mContainerId, newTab.fragment, newTab.tag);
if (newTab.fragment == null) {
ft.detach(mLastTab.fragment);
} else {
mActivity.getSupportFragmentManager().popBackStack();
ft.replace(mContainerId, newTab.fragment);
ft.attach(newTab.fragment);
}
}
mLastTab = newTab;
ft.commit();
mActivity.getSupportFragmentManager().executePendingTransactions();
}
}
}
超複雜嗎? 你也可以不用管這個類別直接拿過用即可, 新建一個TabManager.java, 然後把上面內容複製貼上就可以直接使用了!
那麼以下內容你可以直接跳過
----------------------------跳過-------------------------------------------
基本上是照著官網上所寫的來進行,
你會發現在這個類別當中, 存在兩個靜態類別, 分別是TabInfo以及DummyTabFactory。
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;
}
}
TabInfo就是每一個分頁當中存在的資訊, 例如該分頁的名稱、傳到哪一個類別以及傳過去的時候, 所帶的Bundle。
static class DummyTabFactory implements TabHost.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;
}
}
DummyTabFactory目的是用來創造一個分頁所形成的View, 當你按下任一分頁, 它就會根據對應的View來進行切換。
public TabManager(FragmentActivity activity, TabHost tabHost, int containerId) {
mActivity = activity;
mTabHost = tabHost;
mContainerId = containerId;
mTabHost.setOnTabChangedListener(this);
}
一開始從建構子傳入的就是該FragmentActivity, 再來就是TabHost物件, 最後是一個容器的id,
這個容器就是用來裝我們切換分頁的view, 還記得fragment_tabs.xml當中, 我們用了兩個FrameLayout嗎? 這個就是放在上面的那個, 你可以對照一下後面我們會傳入的id, 就可以清楚的明白這個layout的功用。
public void addTab(TabHost.TabSpec tabSpec, Class<?> clss, Bundle args) {
tabSpec.setContent(new DummyTabFactory(mActivity));
String tag = tabSpec.getTag();
TabInfo info = new TabInfo(tag, clss, args);
info.fragment = mActivity.getSupportFragmentManager().findFragmentByTag(tag);
if (info.fragment != null && !info.fragment.isDetached()) {
FragmentTransaction ft = mActivity.getSupportFragmentManager().beginTransaction();
ft.detach(info.fragment);
ft.commit();
}
mTabs.put(tag, info);
mTabHost.addTab(tabSpec);
}
這邊就是要新增一個分頁, 就必須傳入TabHost.TabSpec物件, 以及你要轉跳的java檔案, 到時候會傳到那個Fragment, 最後是如果你有需要將此Activity的一些內容(如String, int..等等)的資料傳到指定的分頁, 就可以在最後一個參數利用Bundle物件夾帶, 如果沒有, 則可以指定null。
那麼一開始他就將TabSpec的物件帶入new出來的DummyTabFactory, 方便這個分頁的內容呈現一個View。
接著又把傳入的參數丟進去TabInfo物件, 完成初始化。
當我們要進行切換fragment的時候, 就會利用FragmentTransaction的物件來進行調度,而當commit出去的時候, 就可以進行變更。
當我們進行detach動作的時候, 就表示指定該fragment會出現在某一個View當中。
當我們進行detach動作的時候, 就表示指定該fragment會從該View當中分離。
當我們進行replace動作的時候, 才是表示將某一個fragment轉換成另外一個fragment。
官網寫法
@Override
public void onTabChanged(String tabId) {
TabInfo newTab = mTabs.get(tabId);
if (mLastTab != newTab) {
FragmentTransaction ft = mActivity.getSupportFragmentManager().beginTransaction();
if (mLastTab != null) {
if (mLastTab.fragment != null) {
ft.detach(mLastTab.fragment);
}
}
if (newTab != null) {
if (newTab.fragment == null) {
newTab.fragment = Fragment.instantiate(mActivity,
newTab.clss.getName(), newTab.args);
ft.add(mContainerId, newTab.fragment, newTab.tag);
} else {
ft.attach(newTab.fragment);
}
}
mLastTab = newTab;
ft.commit();
mActivity.getSupportFragmentManager().executePendingTransactions();
}
}
一開始先判斷按下的分頁是不是前一個分頁, 如果不是, 則進行處理,
那一開始我們就會把前一個分頁內容做檢查, 看是不是空的,
如果不是空的, 則把前一個分頁從分頁管理者的內容中移除,
再來就是檢查我們新的分頁是否有內容, 如果沒有,
我們就new一個空間讓他建立新的分頁,
然後加入到分頁管理者的儲存器當中,
如果有內容, 則直接指派該分頁是我們目前要呈現的分頁,
最後確定以後, 就把新的分頁跟舊的分頁設定為同一個分頁,
接著送交, 進行更新, 接著執行分頁轉換。
但是! 官網的做法讓我產生一些問題, 因此我做了修改,
大致上如下:
@Override
public void onTabChanged(String tabId) {
TabInfo newTab = mTabs.get(tabId);
if (mLastTab != newTab) {
FragmentTransaction ft = mActivity.getSupportFragmentManager().beginTransaction();
if (mLastTab != null) {
if (mLastTab.fragment != null) {
ft.detach(mLastTab.fragment);
}
}
if (newTab != null) {
newTab.fragment = Fragment.instantiate(mActivity,
newTab.clss.getName(), newTab.args);
ft.add(mContainerId, newTab.fragment, newTab.tag);
if (newTab.fragment == null) {
ft.detach(mLastTab.fragment);
} else {
ft.replace(mContainerId, newTab.fragment);
ft.attach(newTab.fragment);
}
}
mLastTab = newTab;
ft.commit();
mActivity.getSupportFragmentManager().executePendingTransactions();
}
}
這樣就可以成功解決我的問題。
如果這邊讓你覺得看不太懂, 沒關係, 直接Copy&Paste過去就好XD
--------------------結束-------------------------------------------
接著我們在onCreate就可以使用這個類別來操作我們的TabHost物件了。
public class FragmentTabs extends FragmentActivity{
private TabHost mTabHost;
private TabManager mTabManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.fragment_tabs);
mTabHost = (TabHost)findViewById(android.R.id.tabhost);
mTabHost.setup();
mTabManager = new TabManager(this, mTabHost, R.id.realtabcontent);
}
}
這樣基本TabHost的物件就初始化完成了。
讓我們利用TabManager類別的物件來操作這個TabHost吧!
public class FragmentTabs extends FragmentActivity{
private TabHost mTabHost;
private TabManager mTabManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.fragment_tabs);
mTabHost = (TabHost)findViewById(android.R.id.tabhost);
mTabHost.setup();
mTabManager = new TabManager(this, mTabHost, R.id.realtabcontent);
mTabHost.setCurrentTab(0);//設定一開始就跳到第一個分頁
mTabManager.addTab(
mTabHost.newTabSpec("Fragment1").setIndicator("Fragment1"),
Fragment1.class, null);
mTabManager.addTab(
mTabHost.newTabSpec("Fragment2").setIndicator("Fragment2"),
Fragment2.class, null);
mTabManager.addTab(
mTabHost.newTabSpec("Fragment3").setIndicator("Fragment3"),
Fragment3.class, null);
mTabManager.addTab(
mTabHost.newTabSpec("Fragment4").setIndicator("Fragment4"),
Fragment4.class, null);
}
}
我們加入了四個子類別, 分別是Fragment1~Fragment4,
這樣代表在切換分頁的時候, 會根據指定的分別跳置不同的Fragment。
在這邊我們必須再新增4個java檔案, 檔名當然就是Fragment1~Fragment4。
在eclipse點右鍵,
選擇sourece->override/overload 就可以找到onCreate&onCreateView這兩個方法了!
public class Fragment1 extends Fragment {
@Override
public void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// TODO Auto-generated method stub
return super.onCreateView(inflater, container, savedInstanceState);
}
}
其他的四個類別也相同。
那麼跑看看模擬器, 就會出現這樣。
怎麼會擠在一團咧XD
沒關係, 做一下調整。
public class FragmentTabs extends FragmentActivity{
private TabHost mTabHost;
private TabManager mTabManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.fragment_tabs);
mTabHost = (TabHost)findViewById(android.R.id.tabhost);
mTabHost.setup();
mTabManager = new TabManager(this, mTabHost, R.id.realtabcontent);
mTabHost.setCurrentTab(0);
mTabManager.addTab(
mTabHost.newTabSpec("Fragment1").setIndicator("Fragment1"),
Fragment1.class, null);
mTabManager.addTab(
mTabHost.newTabSpec("Fragment2").setIndicator("Fragment2"),
Fragment2.class, null);
mTabManager.addTab(
mTabHost.newTabSpec("Fragment3").setIndicator("Fragment3"),
Fragment3.class, null);
mTabManager.addTab(
mTabHost.newTabSpec("Fragment4").setIndicator("Fragment4"),
Fragment4.class, null);
DisplayMetrics dm = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(dm); //先取得螢幕解析度
int screenWidth = dm.widthPixels; //取得螢幕的寬
TabWidget tabWidget = mTabHost.getTabWidget(); //取得tab的物件
int count = tabWidget.getChildCount(); //取得tab的分頁有幾個
if (count > 3) {
for (int i = 0; i < count; i++) {
tabWidget.getChildTabViewAt(i)
.setMinimumWidth((screenWidth)/3);//設定每一個分頁最小的寬度
}
}
}
}
一開始就先取得螢幕的解析度, 接著由於我們是x軸要加寬, 所以只要取得螢幕的寬度即可,
再來算出我們一共用了幾個分頁, 假設你只想要呈現3個分頁, 因此就把螢幕的寬度除以3個分頁, 這樣一來, 畫面就會把第4個分頁放在螢幕外面, 當你需要第4個分頁的時候, 只需要拉動某一個分頁, 就會出現其他被隱藏的分頁了。
你看是不是好多了?
只要往左拉, 第4個分頁就出現了。
可是總覺得少了那麼一點東西, 加個icon好了。
public class FragmentTabs extends FragmentActivity{
private TabHost mTabHost;
private TabManager mTabManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.fragment_tabs);
mTabHost = (TabHost)findViewById(android.R.id.tabhost);
mTabHost.setup();
mTabManager = new TabManager(this, mTabHost, R.id.realtabcontent);
mTabHost.setCurrentTab(0);
mTabManager.addTab(
mTabHost.newTabSpec("Fragment1").setIndicator("Fragment1",
this.getResources().getDrawable(
android.R.drawable.ic_dialog_alert)),Fragment1.class, null);
mTabManager.addTab(
mTabHost.newTabSpec("Fragment2").setIndicator("Fragment2",
this.getResources().getDrawable(
android.R.drawable.ic_lock_lock)),Fragment2.class, null);
mTabManager.addTab(
mTabHost.newTabSpec("Fragment3").setIndicator("Fragment3",
this.getResources().getDrawable(
android.R.drawable.ic_input_add)),Fragment3.class, null);
mTabManager.addTab(
mTabHost.newTabSpec("Fragment4").setIndicator("Fragment4",
this.getResources().getDrawable(
android.R.drawable.ic_delete)),Fragment4.class, null);
DisplayMetrics dm = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(dm); //先取得螢幕解析度
int screenWidth = dm.widthPixels; //取得螢幕的寬
TabWidget tabWidget = mTabHost.getTabWidget(); //取得tab的物件
int count = tabWidget.getChildCount(); //取得tab的分頁有幾個
if (count > 3) {
for (int i = 0; i < count; i++) {
tabWidget.getChildTabViewAt(i)
.setMinimumWidth((screenWidth)/3);//設定每一個分頁最小的寬度
}
}
}
}
圖是我從android內建的icon找的, 你可以加入你喜歡的圖示。
看起來越來越有模樣了XD
後面還有教學
如何使用Fragment建立TabActivity之二
如何使用Fragment建立TabActivity之三