@act262
2017-08-22T07:31:56.000000Z
字数 7456
阅读 1476
Android
TabHost继承自FrameLayout,也就是各个内容排列方式是层叠的,默认切换Tab时显示内容都是通过显示/隐藏来控制的。
// 专门用来存放Tab的控件private TabWidget mTabWidget;// 内容展示区域private FrameLayout mTabContent;// 存放Tab和对应的Contentprivate 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.tabsmTabWidget = (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.tabcontentmTabContent = (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"><LinearLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"><TabWidgetandroid:id="@android:id/tabs"android:layout_width="match_parent"android:layout_height="48dp" /><FrameLayoutandroid: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"><FrameLayoutandroid:id="@android:id/tabcontent"android:layout_width="match_parent"android:layout_height="match_parent"android:layout_marginBottom="58dp"android:background="#ff0" /><!-- 分割线--><Viewandroid:layout_width="match_parent"android:layout_height="2dp"android:layout_gravity="bottom"android:layout_marginBottom="58dp"android:background="#f00" /><TabWidgetandroid: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"><FrameLayoutandroid:id="@android:id/tabcontent"android:layout_width="match_parent"android:layout_height="match_parent"android:layout_marginBottom="58dp"android:background="#ff0" /><TabWidgetandroid: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