[关闭]
@act262 2017-08-22T07:31:56.000000Z 字数 7456 阅读 1264

Android Widget TabHost分析

Android


TabHost

TabHost继承自FrameLayout,也就是各个内容排列方式是层叠的,默认切换Tab时显示内容都是通过显示/隐藏来控制的。

  1. // 专门用来存放Tab的控件
  2. private TabWidget mTabWidget;
  3. // 内容展示区域
  4. private FrameLayout mTabContent;
  5. // 存放Tab和对应的Content
  6. private List<TabSpec> mTabSpecs = new ArrayList<TabSpec>(2);
  7. // 当前显示Tab索引
  8. protected int mCurrentTab = -1;
  9. // 当前显示Content内容
  10. private View mCurrentView = null;

这里TabWidght和FrameLayout的id都必须是固定这个的,内容区域也是必须用FrameLayout布局的

  1. public void setup() {
  2. // TabWidget指定了使用的id->android.R.id.tabs
  3. mTabWidget = (TabWidget) findViewById(com.android.internal.R.id.tabs);
  4. if (mTabWidget == null) {
  5. throw new RuntimeException(
  6. "Your TabHost must have a TabWidget whose id attribute is 'android.R.id.tabs'");
  7. }
  8. // ....
  9. // 内容区域指定了使用FrameLayout和id->android.R.id.tabcontent
  10. mTabContent = (FrameLayout) findViewById(com.android.internal.R.id.tabcontent);
  11. if (mTabContent == null) {
  12. throw new RuntimeException(
  13. "Your TabHost must have a FrameLayout whose id attribute is "
  14. + "'android.R.id.tabcontent'");
  15. }
  16. }

添加Tab Item到TabHost

  1. public void addTab(TabSpec tabSpec) {
  2. if (tabSpec.mIndicatorStrategy == null) {
  3. throw new IllegalArgumentException("you must specify a way to create the tab indicator.");
  4. }
  5. if (tabSpec.mContentStrategy == null) {
  6. throw new IllegalArgumentException("you must specify a way to create the tab content");
  7. }
  8. View tabIndicator = tabSpec.mIndicatorStrategy.createIndicatorView();
  9. tabIndicator.setOnKeyListener(mTabKeyListener);
  10. // If this is a custom view, then do not draw the bottom strips for
  11. // the tab indicators.
  12. // 自定义View类型的不绘制底部的条状
  13. if (tabSpec.mIndicatorStrategy instanceof ViewIndicatorStrategy) {
  14. mTabWidget.setStripEnabled(false);
  15. }
  16. mTabWidget.addView(tabIndicator);
  17. mTabSpecs.add(tabSpec);
  18. if (mCurrentTab == -1) {
  19. setCurrentTab(0);
  20. }
  21. }

setCurrentTab(int index)中执行上一个ContentView的tabClosed方法,将其隐藏,然后再执行getContentView得到当前的ContentView同时将其显示出来.

  1. // 实现了ContentStrategy的在这里都是将View隐藏
  2. public void tabClosed() {
  3. mView.setVisibility(View.GONE);
  4. }
  1. // 实现了ContentStrategy的在这里都是将View显示出来
  2. public View getContentView() {
  3. mView.setVisibility(View.VISIBLE);
  4. return mView;
  5. }

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:

TabWidget继承自LinearLayout,.

  1. // 底部左边的条状
  2. private Drawable mLeftStrip;
  3. // 底部右边的条状
  4. private Drawable mRightStrip;
  5. // 是否绘制底部的条状物
  6. private boolean mDrawBottomStrips = true;
  7. // 底部条状物移动了标志,减少计算绘制
  8. private boolean mStripMoved;
  1. public void addView(View child) {
  2. // 如果没有设置TabItem的LayoutParams就会使用等比例宽度
  3. if (child.getLayoutParams() == null) {
  4. final LinearLayout.LayoutParams lp = new LayoutParams(0, ViewGroup.LayoutParams.MATCH_PARENT, 1.0f);
  5. p.setMargins(0, 0, 0, 0);
  6. child.setLayoutParams(lp);
  7. }
  8. // ...
  9. super.addView(child);
  10. // 添加点击事件
  11. child.setOnClickListener(new TabClickListener(getTabCount() - 1));
  12. }

所以在addView的子View中最好不要去设置其LayoutParams,由TabWidget自动设置,或者手动给其加上layout_weight

  1. setup(){
  2. mTabWidget.setTabSelectionListener(new TabWidget.OnTabSelectionChanged()->setCurrentTab(tabIndex);
  3. }

如果需要对TabItem设置特殊的效果,则需要重新设置其OnClickListener,并且需要在TabHost.addTab后设置才有效果。

设置Tab间的分割线样式,当设置为null时不显示分割线.
setDividerDrawable(@Nullable Drawable drawable),
setDividerDrawable(@DrawableRes int resId)

  1. public void dispatchDraw(Canvas canvas) {
  2. super.dispatchDraw(canvas);
  3. // Do nothing if there are no tabs.
  4. if (getTabCount() == 0) return;
  5. // If the user specified a custom view for the tab indicators, then
  6. // do not draw the bottom strips.
  7. if (!mDrawBottomStrips) {
  8. // Skip drawing the bottom strips.
  9. return;
  10. }
  11. final View selectedChild = getChildTabViewAt(mSelectedTab);
  12. final Drawable leftStrip = mLeftStrip;
  13. final Drawable rightStrip = mRightStrip;
  14. // 同步tab的状态
  15. leftStrip.setState(selectedChild.getDrawableState());
  16. rightStrip.setState(selectedChild.getDrawableState());
  17. if (mStripMoved) {
  18. final Rect bounds = mBounds;
  19. bounds.left = selectedChild.getLeft();
  20. bounds.right = selectedChild.getRight();
  21. final int myHeight = getHeight();
  22. leftStrip.setBounds(Math.min(0, bounds.left - leftStrip.getIntrinsicWidth()),
  23. myHeight - leftStrip.getIntrinsicHeight(), bounds.left, myHeight);
  24. rightStrip.setBounds(bounds.right, myHeight - rightStrip.getIntrinsicHeight(),
  25. Math.max(getWidth(), bounds.right + rightStrip.getIntrinsicWidth()), myHeight);
  26. mStripMoved = false;
  27. }
  28. leftStrip.draw(canvas);
  29. rightStrip.draw(canvas);
  30. }

使用姿势

最常用到的上下排列的布局结构,内容区域的布局可以在这里写也可以拆分开来,通过实现TabContentFactory来动态加载指定的View。

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <TabHost xmlns:android="http://schemas.android.com/apk/res/android"
  3. android:id="@android:id/tabhost"
  4. android:layout_width="match_parent"
  5. android:layout_height="match_parent">
  6. <LinearLayout
  7. android:layout_width="match_parent"
  8. android:layout_height="match_parent"
  9. android:orientation="vertical">
  10. <TabWidget
  11. android:id="@android:id/tabs"
  12. android:layout_width="match_parent"
  13. android:layout_height="48dp" />
  14. <FrameLayout
  15. android:id="@android:id/tabcontent"
  16. android:layout_width="match_parent"
  17. android:layout_height="match_parent" />
  18. </LinearLayout>
  19. </TabHost>

java代码设置使用:

  1. TabHost tabHost = (TabHost) findViewById(android.R.id.tabhost);
  2. // 初始化
  3. tabHost.setup();
  4. // 指定Tab和对应的内容视图
  5. TabHost.TabSpec tabSpec1 =tabHost.newTabSpec("tab1").setIndicator("tab1").setContent(R.id.content1);
  6. TabHost.TabSpec tabSpec2 =tabHost.newTabSpec("tab2").setIndicator("tab2").setContent(R.id.content2);
  7. // 添加到TabHost中
  8. tabHost.addTab(tabSpec1);
  9. tabHost.addTab(tabSpec2);

在TabWidget中控制部分样式

  1. android:tabStripEnabled="true" 是否显示底部条状
  2. android:tabStripLeft="@color/colorPrimaryDark"
  3. android:tabStripRight="@color/colorAccent"

精简布局,去除LinearLayout这层,通过layout_gravitymargin来控制其位置

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <TabHost xmlns:android="http://schemas.android.com/apk/res/android"
  3. xmlns:tools="http://schemas.android.com/tools"
  4. android:id="@android:id/tabhost"
  5. android:layout_width="match_parent"
  6. android:layout_height="match_parent"
  7. tools:tabLayout="@layout/tab">
  8. <FrameLayout
  9. android:id="@android:id/tabcontent"
  10. android:layout_width="match_parent"
  11. android:layout_height="match_parent"
  12. android:layout_marginBottom="58dp"
  13. android:background="#ff0" />
  14. <!-- 分割线-->
  15. <View
  16. android:layout_width="match_parent"
  17. android:layout_height="2dp"
  18. android:layout_gravity="bottom"
  19. android:layout_marginBottom="58dp"
  20. android:background="#f00" />
  21. <TabWidget
  22. android:id="@android:id/tabs"
  23. android:layout_width="match_parent"
  24. android:layout_height="wrap_content"
  25. android:layout_gravity="bottom"
  26. android:background="@android:color/white"
  27. android:divider="@null" />
  28. </TabHost>

分割线还可以设置为TabWidget的背景图的上边沿线,可以减少一个分割线View

  1. <TabHost xmlns:android="http://schemas.android.com/apk/res/android"
  2. xmlns:tools="http://schemas.android.com/tools"
  3. android:id="@android:id/tabhost"
  4. android:layout_width="match_parent"
  5. android:layout_height="match_parent"
  6. tools:tabLayout="@layout/tab">
  7. <FrameLayout
  8. android:id="@android:id/tabcontent"
  9. android:layout_width="match_parent"
  10. android:layout_height="match_parent"
  11. android:layout_marginBottom="58dp"
  12. android:background="#ff0" />
  13. <TabWidget
  14. android:id="@android:id/tabs"
  15. android:layout_width="match_parent"
  16. android:layout_height="wrap_content"
  17. android:layout_gravity="bottom"
  18. android:background="@drawable/bg_divider"
  19. android:divider="@null" />
  20. </TabHost>
  1. <layer-list xmlns:android="http://schemas.android.com/apk/res/android">
  2. <item android:drawable="@android:color/white" />
  3. <item android:gravity="top">
  4. <shape android:shape="rectangle">
  5. <solid android:color="#f00" />
  6. <size android:height="2dp" />
  7. </shape>
  8. </item>
  9. </layer-list>

最后附上使用Sample
TabHostSample

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注