@act262
2017-08-22T07:31:56.000000Z
字数 7456
阅读 1385
Android
TabHost继承自FrameLayout,也就是各个内容排列方式是层叠的,默认切换Tab时显示内容都是通过显示/隐藏来控制的。
// 专门用来存放Tab的控件
private TabWidget mTabWidget;
// 内容展示区域
private FrameLayout mTabContent;
// 存放Tab和对应的Content
private List<TabSpec> mTabSpecs = new ArrayList<TabSpec>(2);
// 当前显示Tab索引
protected int mCurrentTab = -1;
// 当前显示Content内容
private View mCurrentView = null;
这里TabWidght和FrameLayout的id都必须是固定这个的,内容区域也是必须用FrameLayout布局的
public void setup() {
// TabWidget指定了使用的id->android.R.id.tabs
mTabWidget = (TabWidget) findViewById(com.android.internal.R.id.tabs);
if (mTabWidget == null) {
throw new RuntimeException(
"Your TabHost must have a TabWidget whose id attribute is 'android.R.id.tabs'");
}
// ....
// 内容区域指定了使用FrameLayout和id->android.R.id.tabcontent
mTabContent = (FrameLayout) findViewById(com.android.internal.R.id.tabcontent);
if (mTabContent == null) {
throw new RuntimeException(
"Your TabHost must have a FrameLayout whose id attribute is "
+ "'android.R.id.tabcontent'");
}
}
添加Tab Item到TabHost
public void addTab(TabSpec tabSpec) {
if (tabSpec.mIndicatorStrategy == null) {
throw new IllegalArgumentException("you must specify a way to create the tab indicator.");
}
if (tabSpec.mContentStrategy == null) {
throw new IllegalArgumentException("you must specify a way to create the tab content");
}
View tabIndicator = tabSpec.mIndicatorStrategy.createIndicatorView();
tabIndicator.setOnKeyListener(mTabKeyListener);
// If this is a custom view, then do not draw the bottom strips for
// the tab indicators.
// 自定义View类型的不绘制底部的条状
if (tabSpec.mIndicatorStrategy instanceof ViewIndicatorStrategy) {
mTabWidget.setStripEnabled(false);
}
mTabWidget.addView(tabIndicator);
mTabSpecs.add(tabSpec);
if (mCurrentTab == -1) {
setCurrentTab(0);
}
}
在setCurrentTab(int index)
中执行上一个ContentView的tabClosed
方法,将其隐藏,然后再执行getContentView
得到当前的ContentView同时将其显示出来.
// 实现了ContentStrategy的在这里都是将View隐藏
public void tabClosed() {
mView.setVisibility(View.GONE);
}
// 实现了ContentStrategy的在这里都是将View显示出来
public View getContentView() {
mView.setVisibility(View.VISIBLE);
return mView;
}
TabSpec
封装了Tab和Content的布局样式,
TabSpec内部使用了策略模式,ContentView的样式控制通过实现ContentStrategy
得到
setContent(int viewId)
-> ViewIdContentStrategy
setContent(TabContentFactory contentFactory)
-> FactoryContentStrategy
Tab的样式默认使用系统指定的布局
mTabLayoutId = R.layout.tab_indicator_holo;
Tab样式控制通过实现IndicatorStrategy
得到
TabSpec
以下方法对应封装的IndicatorStrategy
内部实现
setIndicator(CharSequence label)
-> LabelIndicatorStrategy
setIndicator(CharSequence label, Drawable icon)
-> LabelAndIconIndicatorStrategy
setIndicator(View view)
-> ViewIndicatorStrategy
自定义样式需要指定mTabLayoutId,在布局中指定,同时其中包含TextView和ImageView的id都是固定的.
TabWidget继承自LinearLayout,.
// 底部左边的条状
private Drawable mLeftStrip;
// 底部右边的条状
private Drawable mRightStrip;
// 是否绘制底部的条状物
private boolean mDrawBottomStrips = true;
// 底部条状物移动了标志,减少计算绘制
private boolean mStripMoved;
public void addView(View child) {
// 如果没有设置TabItem的LayoutParams就会使用等比例宽度
if (child.getLayoutParams() == null) {
final LinearLayout.LayoutParams lp = new LayoutParams(0, ViewGroup.LayoutParams.MATCH_PARENT, 1.0f);
p.setMargins(0, 0, 0, 0);
child.setLayoutParams(lp);
}
// ...
super.addView(child);
// 添加点击事件
child.setOnClickListener(new TabClickListener(getTabCount() - 1));
}
所以在addView
的子View中最好不要去设置其LayoutParams
,由TabWidget
自动设置,或者手动给其加上layout_weight
TabItem
被点击后会回调到TabHost的OnTabChangeListener
,然后调用setCurrentTab
切换Tab选中。
setup(){
mTabWidget.setTabSelectionListener(new TabWidget.OnTabSelectionChanged()->setCurrentTab(tabIndex);
}
如果需要对TabItem设置特殊的效果,则需要重新设置其OnClickListener
,并且需要在TabHost.addTab
后设置才有效果。
设置Tab间的分割线样式,当设置为null时不显示分割线.
setDividerDrawable(@Nullable Drawable drawable)
,
setDividerDrawable(@DrawableRes int resId)
public void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
// Do nothing if there are no tabs.
if (getTabCount() == 0) return;
// If the user specified a custom view for the tab indicators, then
// do not draw the bottom strips.
if (!mDrawBottomStrips) {
// Skip drawing the bottom strips.
return;
}
final View selectedChild = getChildTabViewAt(mSelectedTab);
final Drawable leftStrip = mLeftStrip;
final Drawable rightStrip = mRightStrip;
// 同步tab的状态
leftStrip.setState(selectedChild.getDrawableState());
rightStrip.setState(selectedChild.getDrawableState());
if (mStripMoved) {
final Rect bounds = mBounds;
bounds.left = selectedChild.getLeft();
bounds.right = selectedChild.getRight();
final int myHeight = getHeight();
leftStrip.setBounds(Math.min(0, bounds.left - leftStrip.getIntrinsicWidth()),
myHeight - leftStrip.getIntrinsicHeight(), bounds.left, myHeight);
rightStrip.setBounds(bounds.right, myHeight - rightStrip.getIntrinsicHeight(),
Math.max(getWidth(), bounds.right + rightStrip.getIntrinsicWidth()), myHeight);
mStripMoved = false;
}
leftStrip.draw(canvas);
rightStrip.draw(canvas);
}
最常用到的上下排列的布局结构,内容区域的布局可以在这里写也可以拆分开来,通过实现TabContentFactory
来动态加载指定的View。
<?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="48dp" />
<FrameLayout
android:id="@android:id/tabcontent"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
</TabHost>
java代码设置使用:
TabHost tabHost = (TabHost) findViewById(android.R.id.tabhost);
// 初始化
tabHost.setup();
// 指定Tab和对应的内容视图
TabHost.TabSpec tabSpec1 =tabHost.newTabSpec("tab1").setIndicator("tab1").setContent(R.id.content1);
TabHost.TabSpec tabSpec2 =tabHost.newTabSpec("tab2").setIndicator("tab2").setContent(R.id.content2);
// 添加到TabHost中
tabHost.addTab(tabSpec1);
tabHost.addTab(tabSpec2);
在TabWidget中控制部分样式
android:tabStripEnabled="true" 是否显示底部条状
android:tabStripLeft="@color/colorPrimaryDark"
android:tabStripRight="@color/colorAccent"
精简布局,去除LinearLayout这层,通过layout_gravity
、margin
来控制其位置
<?xml version="1.0" encoding="utf-8"?>
<TabHost xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@android:id/tabhost"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:tabLayout="@layout/tab">
<FrameLayout
android:id="@android:id/tabcontent"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="58dp"
android:background="#ff0" />
<!-- 分割线-->
<View
android:layout_width="match_parent"
android:layout_height="2dp"
android:layout_gravity="bottom"
android:layout_marginBottom="58dp"
android:background="#f00" />
<TabWidget
android:id="@android:id/tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:background="@android:color/white"
android:divider="@null" />
</TabHost>
分割线还可以设置为TabWidget的背景图的上边沿线,可以减少一个分割线View
<TabHost xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@android:id/tabhost"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:tabLayout="@layout/tab">
<FrameLayout
android:id="@android:id/tabcontent"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="58dp"
android:background="#ff0" />
<TabWidget
android:id="@android:id/tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:background="@drawable/bg_divider"
android:divider="@null" />
</TabHost>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/white" />
<item android:gravity="top">
<shape android:shape="rectangle">
<solid android:color="#f00" />
<size android:height="2dp" />
</shape>
</item>
</layer-list>
最后附上使用Sample
TabHostSample