問題
iOS上的Tab Bar相信大家都很熟悉了,就是界面底部幾個(gè)按鈕禀忆,點(diǎn)擊可以切換子頁面士葫。
而在Android上,可以用support v4中的FragmentTabHost
來實(shí)現(xiàn)類似效果拱镐,它繼承自TabHost
,每個(gè)子頁面都是一個(gè)Fragment
持际。
今天遇到一個(gè)需求沃琅,需要在點(diǎn)擊Tab按鈕后,不切換子頁面蜘欲,而是跳轉(zhuǎn)到一個(gè)新的頁面益眉。通過查閱文檔,FragmentTabHost
與其父類TabHost
似乎都沒有提供相關(guān)函數(shù)可以自定義Tab的點(diǎn)擊事件姥份。只有一個(gè)當(dāng)Tab切換完成后的回調(diào)監(jiān)聽OnTabChangeListener
郭脂,但是切換已經(jīng)完成為時(shí)已晚啊。
在查閱文檔無果后殿衰,我祭出了Google朱庆,但是卻苦于不知道該如何組織關(guān)鍵詞(英文不會,中文離譜)闷祥,試了幾個(gè)關(guān)鍵詞依然無果娱颊。既然如此,就嘗試看下源碼吧凯砍,沒想到最終還是非常簡單就可以解決問題箱硕,遂記錄一下。
首先我們來看一下FragmentTabHost
的基本使用悟衩,來自官方文檔
mTabHost = new FragmentTabHost(getActivity());
mTabHost.setup(getActivity(), getChildFragmentManager(), R.id.fragment1);
mTabHost.addTab(mTabHost.newTabSpec("simple").setIndicator("Simple"), FragmentStackSupport.CountingFragment.class, null);
mTabHost.addTab(mTabHost.newTabSpec("contacts").setIndicator("Contacts"), LoaderCursorSupport.CursorLoaderListFragment.class, null);
第二行通過setup
進(jìn)行初始化剧罩,三四行通過addTab
方法添加了兩個(gè)Tab,我們就從Tab的添加入手座泳,開始追蹤源碼惠昔,看看它都做了什么,是如何設(shè)置每個(gè)Tab的點(diǎn)擊事件的挑势。以下的源碼經(jīng)過了簡化镇防,只保留部分關(guān)鍵信息。
源碼追蹤
首先是FragmentTabHost
潮饱,它繼承自TabHost
来氧,增加了一個(gè)addTab
的重載方法,第二個(gè)參數(shù)接收Fragment的class,用于Fragment相關(guān)邏輯啦扬。在完成對Fragment的處理后中狂,繼續(xù)調(diào)用父類TabHost
的addTab
方法。
public class FragmentTabHost extends TabHost {
public void addTab(TabHost.TabSpec tabSpec, Class<?> clss, Bundle args) {
...
addTab(tabSpec);
}
}
然后來到TabHost
扑毡,其中成員變量mTabWidget
是Tab的容器胃榕。在addTab
方法中,先取出了Tab的View僚楞,然后執(zhí)行mTabWidget的addView勤晚,將Tab添加到容器中。
public class TabHost extends FrameLayout {
private TabWidget mTabWidget;
public void addTab(TabSpec tabSpec) {
...
View tabIndicator = tabSpec.mIndicatorStrategy.createIndicatorView();
...
mTabWidget.addView(tabIndicator);
...
}
}
最后來到TabWidget
泉褐,可以看到它繼承自我們的老朋友LinearLayout,這為Tab View提供了線性排布鸟蜡。在addView
方法的最后膜赃,我們找到了為每個(gè)Tab設(shè)置點(diǎn)擊監(jiān)聽的地方。
public class TabWidget extends LinearLayout {
@Override
public void addView(View child) {
...
super.addView(child);
// TODO: detect this via geometry with a tabwidget listener rather
// than potentially interfere with the view's listener
child.setOnClickListener(new TabClickListener(getTabCount() - 1));
child.setOnFocusChangeListener(this);
}
}
解決問題
通過一路追蹤源碼揉忘,現(xiàn)在我們已經(jīng)知道了FragmentTabHost
是如何設(shè)定每個(gè)Tab的點(diǎn)擊事件的跳座。最后設(shè)置監(jiān)聽器的地方給了我啟發(fā),假如能夠獲取到Tab View泣矛,就可以設(shè)置自己的點(diǎn)擊監(jiān)聽疲眷,同時(shí)覆蓋掉了系統(tǒng)的監(jiān)聽器,從而完成自定義點(diǎn)擊效果的任務(wù)您朽。
于是從TabWidget
反過來查找獲取Tab View的方法狂丝。首先TabWidget
提供了getChildTabViewAt(int index)
方法,可以根據(jù)Tab的索引獲取到Tab View哗总。然后通過TabHost
的getTabWidget
可以獲取到TabWidget 几颜。得到目標(biāo)Tab View后,設(shè)定自己的OnClickListener讯屈,搞定任務(wù)蛋哭。
下面代碼演示了設(shè)置第一個(gè)Tab點(diǎn)擊事件的方法。
mTabHost.getTabWidget().getChildTabViewAt(0).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
}
});
總結(jié)
系統(tǒng)的源碼真的寫的非常清楚涮母,只要肯耐心看谆趾,很多問題都可以迎刃而解。