本篇文章已授權(quán)微信公眾號(hào) guolin_blog (郭霖)獨(dú)家發(fā)布

上圖效果大家應(yīng)該都很熟悉了,基本市面上的App都會(huì)用到這種布局效果,實(shí)現(xiàn)起來(lái)也很簡(jiǎn)單,就是上面一個(gè)ViewPager,下面一個(gè)線性布局
TabContainerView就是把實(shí)現(xiàn)邏輯封裝起來(lái),讓開(kāi)發(fā)者可以通過(guò)更簡(jiǎn)單的代碼實(shí)現(xiàn)這種布局效果,提高工作效率。
使用
TabContainerView tabContainerView = (TabContainerView) findViewById(R.id.container_tab);
//設(shè)置適配器配置數(shù)據(jù)
tabContainerView.setAdapter(new MainTabContainerAdapter(getSupportFragmentManager(),
new Fragment[] {new MainFragment(), new WorkFragment(), new AppFragment(), new MineFragment()}));
就是這么簡(jiǎn)單,調(diào)用兩行代碼就可以實(shí)現(xiàn)了,不過(guò)需要我們自己創(chuàng)建一個(gè)適配器,MainTabContainerAdapter就是自己創(chuàng)建的,它需要繼承BaseAdapter來(lái)實(shí)現(xiàn)里面的抽象方法,BaseAdapter是此項(xiàng)目當(dāng)中自定義的抽象類。
思路
TabContainerView是一個(gè)RelativeLayout布局,整個(gè)布局由兩部分組成:底部布局,內(nèi)容布局
底部布局為一個(gè)LinearLayout布局,里面的單個(gè)Tab也是一個(gè)LinarLayout布局;中間的內(nèi)部區(qū)域是一個(gè)ViewPager。
實(shí)現(xiàn)
項(xiàng)目由6個(gè)類組成
//暴露給開(kāi)發(fā)者的View,主要負(fù)責(zé)添加底部和ViewPager的布局
TabContainerView
//底部布局的單個(gè)布局,包含單個(gè)布局的文本,圖片屬性信息
Tab
//底部布局的整體布局,負(fù)責(zé)Tab布局的添加和狀態(tài)切換
TabHost
//Tab選中的監(jiān)聽(tīng)
OnTabSelectedListener
//適配器提供了底部文本內(nèi)容,圖片內(nèi)容,fragment內(nèi)容
BaseAdapter
//內(nèi)容ViewPager的適配器
TabViewPagerAdapter
我們先來(lái)分析調(diào)用的第一行代碼
TabContainerView tabContainerView = (TabContainerView) findViewById(R.id.container_tab);
看看它的構(gòu)造方法
public TabContainerView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
構(gòu)造方法調(diào)用了init方法
private void init(Context context, AttributeSet attrs) {
initStyle(context, attrs);
initTabHost(context);
initDivideLine(context);
initViewPager(context);
tabHost.setContentViewPager(contentViewPager);
}
分別初始化了自定義屬性,底部的TabHost,分割線,ViewPager等;
initStyle就是初始化一些自定義的屬性,沒(méi)啥好說(shuō)的;
我們來(lái)看下initTabHost方法
private void initTabHost(Context context) {
tabHost = new TabHost(context);
addView(tabHost.getRootView());
}
public TabHost(Context context) {
this.context = context;
initView();
}
private void initView() {
rootView = new LinearLayout(context);
rootView.setOrientation(LinearLayout.HORIZONTAL);
rootView.setId(R.id.linearlayout_tab);
RelativeLayout.LayoutParams rootViewLp = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
rootViewLp.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
rootView.setLayoutParams(rootViewLp);
}
initTabHost方法里創(chuàng)建了一個(gè)TabHost,TabHost的構(gòu)造方法里調(diào)用initView方法,方法創(chuàng)建RootView布局,設(shè)置長(zhǎng)寬, 位置等屬性,然后把TabHost的根布局添加到TabContainerView布局當(dāng)中。
initDivideLine方法創(chuàng)建一個(gè)分割線View添加到布局當(dāng)中
initViewPager方法創(chuàng)建一個(gè)ViewPager添加到布局當(dāng)中
下面再來(lái)看看第二行代碼里面做了些什么事情
tabContainerView.setAdapter(new MainTabContainerAdapter(getSupportFragmentManager(),
new Fragment[] {new MainFragment(), new WorkFragment(), new AppFragment(), new MineFragment()}));
public void setAdapter(BaseAdapter baseAdapter) {
setAdapter(baseAdapter, 0);
}
public void setAdapter(BaseAdapter baseAdapter, int index) {
if (baseAdapter == null) return;
tabHost.addTabs(baseAdapter, textSize, textColor, selectedTextColor);
contentViewPager.setAdapter(new TabViewPagerAdapter(baseAdapter.getFragmentManager(), baseAdapter.getFragmentArray()));
setCurrentItem(index);
}
BaseAdapter是個(gè)抽象類
public abstract class BaseAdapter {
/**
* tab數(shù)量
*/
public abstract int getCount();
/**
* tab text 數(shù)組
*/
public abstract String[] getTextArray();
/**
* tab icon 數(shù)組
*/
public abstract int[] getIconImageArray();
/**
* tab icon 選中 數(shù)組
*/
public abstract int[] getSelectedIconImageArray();
/**
* fragment 數(shù)組
*/
public abstract Fragment[] getFragmentArray();
public abstract FragmentManager getFragmentManager();
}
它提供了容器當(dāng)中需要的文本,圖片,還有內(nèi)容區(qū)的fragment信息
方法里調(diào)用TabHost的addTabs方法給TabHost添加Tab
tabHost.addTabs(baseAdapter, textSize, textColor, selectedTextColor);
public void addTabs(BaseAdapter baseAdapter, int textSize, int textColor, int selectedTextColor) {
int count = baseAdapter.getCount();
String[] textArray = baseAdapter.getTextArray();
int[] iconImageArray = baseAdapter.getIconImageArray();
int[] selectedIconImageArray = baseAdapter.getSelectedIconImageArray();
if (count == 0 || textArray == null || iconImageArray == null || selectedIconImageArray == null) return;
if (textArray.length != count || iconImageArray.length != count || selectedIconImageArray.length != count) return;
for (int i = 0; i < count; i++) {
Tab tab = new Tab(context, textArray[i], textSize, textColor, selectedTextColor, iconImageArray[i], selectedIconImageArray[i], i);
addTab(tab);
}
}
通過(guò)方法得到文本,圖片等數(shù)組,然后通過(guò)循環(huán)創(chuàng)建Tab對(duì)象,添加到TabHost布局當(dāng)中,我們來(lái)看看Tab的構(gòu)造方法
public Tab(Context context, String text, int textSize, int textColor, int selectedTextColor, int iconImage, int selectedIconImage, int index) {
this.context = context;
this.text = text;
this.textSize = textSize;
this.textColor = textColor;
this.selectedTextColor = selectedTextColor;
this.iconImage = iconImage;
this.selectedIconImage = selectedIconImage;
this.index = index;
init();
}
private void init() {
initView();
rootView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
tabSelected();
}
});
}
private void initView() {
rootView = new LinearLayout(context);
rootView.setOrientation(LinearLayout.VERTICAL);
rootView.setGravity(Gravity.CENTER_HORIZONTAL);
rootView.setPadding(0, 25, 0, 0);
LinearLayout.LayoutParams rootViewLp = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
rootViewLp.weight = 1;
rootView.setLayoutParams(rootViewLp);
/**
* icon view
*/
iconImageView = new ImageView(context);
iconImageView.setImageResource(iconImage);
iconImageView.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
rootView.addView(iconImageView);
/**
* text view
*/
textTextView = new TextView(context);
textTextView.setText(text);
textTextView.setTextColor(textColor);
textTextView.setTextSize(textSize);
textTextView.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
rootView.addView(textTextView);
}
Tab的構(gòu)造方法就是給文本和圖片的屬性設(shè)值,添加監(jiān)聽(tīng),創(chuàng)建Tab需要的文本,圖片布局
接下來(lái)給ViewPager設(shè)置適配器,添加fragment
contentViewPager.setAdapter(new TabViewPagerAdapter(baseAdapter.getFragmentManager(), baseAdapter.getFragmentArray()));
這樣底部的Tab和內(nèi)容區(qū)域的ViewPager數(shù)據(jù)都填充完成了
接下來(lái)需要設(shè)置點(diǎn)擊狀態(tài)的切換,從Tab類的init方法里可以看出給RootView添加了點(diǎn)擊事件,onClick方法會(huì)調(diào)用TabHost設(shè)置給Tab的監(jiān)聽(tīng)回調(diào)類,下面的代碼就是TabHost給Tab添加的監(jiān)聽(tīng)
//TabHost里給Tab添加Tab選中的監(jiān)聽(tīng)
private void addTabChangeListener(Tab tab) {
tab.setOnTabSelectedListener(new OnTabSelectedListener() {
@Override
public void onTabSelected(Tab tab) {
contentViewPager.setCurrentItem(tab.getIndex());
}
});
}
onTabSelected方法里設(shè)置contentViewPager當(dāng)前選中的Item,然后會(huì)回調(diào)到ViewPager監(jiān)聽(tīng)類OnPageChangeListener的onPageSelected方法
public void onPageSelected(int position) {
tabHost.onChangeTabHostStatus(position);
Tab selectedTab = tabHost.getTabForIndex(position);
if (onTabSelectedListener != null && selectedTab != null)
onTabSelectedListener.onTabSelected(selectedTab);
}
首先調(diào)用onChangeTabHostStatus方法
public void onChangeTabHostStatus(int index) {
for (int i = 0, size = tabList.size(); i < size; i++) {
Tab tab = tabList.get(i);
tab.setTabIsSelected(index == i ? true : false);
}
}
.```
循環(huán)TabList,根據(jù)index判斷Tab狀態(tài)的選中與否
然后調(diào)用回調(diào)監(jiān)聽(tīng)onTabSelectedListener的onTabSelected方法
到此為止整個(gè)項(xiàng)目的實(shí)現(xiàn)過(guò)程就分析完了。
####結(jié)束語(yǔ)
整個(gè)項(xiàng)目的實(shí)現(xiàn)并沒(méi)有任何難度,把它封裝成一個(gè)View是為了以后在項(xiàng)目中更好更快的實(shí)現(xiàn)這種效果,提升開(kāi)發(fā)效率
想看完整代碼的可以移步至:https://github.com/chenpengfei88/TabContainerView
歡迎大家Star,F(xiàn)ollow,謝謝。
TabContainerView V2.0版本(http://www.itdecent.cn/p/9aaff43bbf9f )
我還有一篇封裝布局狀態(tài)切換的文章,大家有興趣也可以看看
http://www.itdecent.cn/p/9d53893b3eda