TabHost extends FrameLayout
关键属性:
private TabWidget mTabWidget;//选项卡标签,继承自LinearLayout
private FrameLayout mTabContent;//显示内容的区域
private List<TabSpec> mTabSpecs;//选项卡组
protected int mCurrentTab = -1;//当前选项卡
private View mCurrentView = null;//当前选项卡的显示内容
private OnTabChangeListener mOnTabChangeListener;//选项卡变换回调
TabHost 它的子view中必须有两个组件:
TabWidget 用来显示选项标签,id必须为:@android:id/tabs
FrameLayout:用来显示选项卡内容,id 必须为 @android:id/tabcontent
ps:其实选项卡里面的布局的内容都是加入到 tabcontent里作为子view,只有一个为 visible
当切换选项卡的时候,就是把不同的选项卡布局内容设置为可见和不可见而已。后面具体讲述。
TabSpec 用来表示一个选项卡,是 TabHost 的一个内部类,
可以通过 TabHost 的实例方法 newTabSpec 创建,TabSpec 里面有选项卡的 标签和内容,具体显示下面分析
先讲一个例子
<?xml version="1.0" encoding="utf-8"?>
<TabHost tools:context="thereisnospon.analyse.widget.MTabActivity"
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">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TabWidget
android:id="@android:id/tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"></TabWidget>
<FrameLayout
android:id="@android:id/tabcontent"
android:layout_width="match_parent"
android:layout_height="match_parent"></FrameLayout>
</LinearLayout>
</TabHost>
public class MTabActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_tab);
TabHost tabHost = (TabHost)findViewById(android.R.id.tabhost);
tabHost.setup();
LayoutInflater.from(this).inflate(R.layout.tab1, tabHost.getTabContentView());
LayoutInflater.from(this).inflate(R.layout.tab2, tabHost.getTabContentView());
LayoutInflater.from(this).inflate(R.layout.tab3, tabHost.getTabContentView());
tabHost.addTab(tabHost.newTabSpec("tab1").setIndicator("标签页一").setContent(R.id.tab1));
tabHost.addTab(tabHost.newTabSpec("tab2").setIndicator("标签页二").setContent(R.id.tab2));
tabHost.addTab(tabHost.newTabSpec("tab3").setIndicator("标签页三").setContent(R.id.tab3));
}
}
下面逐步讲有哪些部分的内容:
构造函数有多个重载,最终都是调用这个构造函数,然后初始化,没什么分析的,可以略过
public TabHost(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs);
final TypedArray a = context.obtainStyledAttributes(
attrs, com.android.internal.R.styleable.TabWidget, defStyleAttr, defStyleRes);
mTabLayoutId = a.getResourceId(R.styleable.TabWidget_tabLayout, 0);
a.recycle();
if (mTabLayoutId == 0) {
// In case the tabWidgetStyle does not inherit from Widget.TabWidget and tabLayout is
// not defined.
mTabLayoutId = R.layout.tab_indicator_holo;
}
initTabHost();
}
private void initTabHost() {
setFocusableInTouchMode(true);
setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
mCurrentTab = -1;
mCurrentView = null;
}
比较重要的是这个开始设置
//设置
public void setup() {
//可以看出我们必须设置这个id才行
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'");
}
// KeyListener to attach to all tabs. Detects non-navigation keys
// and relays them to the tab content.
mTabKeyListener = new OnKeyListener() {
public boolean onKey(View v, int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_DPAD_CENTER:
case KeyEvent.KEYCODE_DPAD_LEFT:
case KeyEvent.KEYCODE_DPAD_RIGHT:
case KeyEvent.KEYCODE_DPAD_UP:
case KeyEvent.KEYCODE_DPAD_DOWN:
case KeyEvent.KEYCODE_ENTER:
return false;
}
mTabContent.requestFocus(View.FOCUS_FORWARD);
return mTabContent.dispatchKeyEvent(event);
}
};
mTabWidget.setTabSelectionListener(new TabWidget.OnTabSelectionChanged() {
public void onTabSelectionChanged(int tabIndex, boolean clicked) {
setCurrentTab(tabIndex);
if (clicked) {
mTabContent.requestFocus(View.FOCUS_FORWARD);
}
}
});
//内容部分也必须这个id
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'");
}
}
然后主要将怎么添加 选项卡,可以通过 TabHost 的实例方法
public TabSpec newTabSpec(String tag) {
return new TabSpec(tag);
}
TabSpec 有两个比较重要的属性
private IndicatorStrategy mIndicatorStrategy;//创建选项标签的策略
private ContentStrategy mContentStrategy;//创建选项内容的策略
可以看出 TabSpec 使用了策略模式,因为选项标签可能是仅有标题,或者有标题和图标,或者自己想要的view
选项的内容可能是指定了id或者是指定了内容视图创建的工厂,这样通过不同的策略给出不同类型的标签
private static interface IndicatorStrategy {
/**
* Return the view for the indicator.
*/
View createIndicatorView();
}
private static interface ContentStrategy {
/**
* Return the content view. The view should may be cached locally.
*/
//显示选项的内容
View getContentView();
/**
* Perhaps do something when the tab associated with this content has
* been closed (i.e make it invisible, or remove it).
*/
//选项卡关闭
void tabClosed();
}
public class TabSpec {
private String mTag;
private IndicatorStrategy mIndicatorStrategy;
private ContentStrategy mContentStrategy;
private TabSpec(String tag) {
mTag = tag;
}
/**
* Specify a label as the tab indicator.
*/
public TabSpec setIndicator(CharSequence label) {
//选项标签只有标题,通过一个LabelIndicatorStrategy策略创建标签内容
mIndicatorStrategy = new LabelIndicatorStrategy(label);
return this;
}
/**
* Specify a label and icon as the tab indicator.
*/
public TabSpec setIndicator(CharSequence label, Drawable icon) {
//选项标签既有标题又有图标,通过另一种策略
mIndicatorStrategy = new LabelAndIconIndicatorStrategy(label, icon);
return this;
}
/**
* Specify a view as the tab indicator.
*/
public TabSpec setIndicator(View view) {
//选项标签指定了view,通过一种策略
mIndicatorStrategy = new ViewIndicatorStrategy(view);
return this;
}
/**
* Specify the id of the view that should be used as the content
* of the tab.
*/
public TabSpec setContent(int viewId) {
//指定id
mContentStrategy = new ViewIdContentStrategy(viewId);
return this;
}
/**
* Specify a {@link android.widget.TabHost.TabContentFactory} to use to
* create the content of the tab.
*/
public TabSpec setContent(TabContentFactory contentFactory) {
mContentStrategy = new FactoryContentStrategy(mTag, contentFactory);
return this;
}
/**
* Specify an intent to use to launch an activity as the tab content.
*/
public TabSpec setContent(Intent intent) {
mContentStrategy = new IntentContentStrategy(mTag, intent);
return this;
}
public String getTag() {
return mTag;
}
}
只有标题
private class LabelIndicatorStrategy implements IndicatorStrategy {
private final CharSequence mLabel;
private LabelIndicatorStrategy(CharSequence label) {
mLabel = label;
}
public View createIndicatorView() {
final Context context = getContext();
LayoutInflater inflater =
(LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View tabIndicator = inflater.inflate(mTabLayoutId,
mTabWidget, // tab widget is the parent
false); // no inflate params
final TextView tv = (TextView) tabIndicator.findViewById(R.id.title);
tv.setText(mLabel);
if (context.getApplicationInfo().targetSdkVersion <= Build.VERSION_CODES.DONUT) {
// Donut apps get old color scheme
tabIndicator.setBackgroundResource(R.drawable.tab_indicator_v4);
tv.setTextColor(context.getColorStateList(R.color.tab_indicator_text_v4));
}
return tabIndicator;
}
}
指定view为标签的策略
private class ViewIndicatorStrategy implements IndicatorStrategy {
private final View mView;
private ViewIndicatorStrategy(View view) {
mView = view;
}
public View createIndicatorView() {
return mView;
}
}
根据id显示内容的策略
private class ViewIdContentStrategy implements ContentStrategy {
private final View mView;
private ViewIdContentStrategy(int viewId) {
//所有的内容都是放到了tabContent里 ,并且切换就是设置为可见和不可见而已
mView = mTabContent.findViewById(viewId);
if (mView != null) {
mView.setVisibility(View.GONE);
} else {
throw new RuntimeException("Could not create tab content because " +
"could not find view with id " + viewId);
}
}
public View getContentView() {
mView.setVisibility(View.VISIBLE);
return mView;
}
public void tabClosed() {
mView.setVisibility(View.GONE);
}
}
自定义创建工厂显示内容的策略
private class FactoryContentStrategy implements ContentStrategy {
private View mTabContent;
private final CharSequence mTag;
private TabContentFactory mFactory;
public FactoryContentStrategy(CharSequence tag, TabContentFactory factory) {
mTag = tag;
mFactory = factory;
}
public View getContentView() {
if (mTabContent == null) {
mTabContent = mFactory.createTabContent(mTag.toString());
}
mTabContent.setVisibility(View.VISIBLE);
return mTabContent;
}
public void tabClosed() {
mTabContent.setVisibility(View.GONE);
}
}
要给出的工厂
public interface TabContentFactory {
/**
* Callback to make the tab contents
*
* @param tag
* Which tab was selected.
* @return The view to display the contents of the selected tab.
*/
View createTabContent(String tag);
}
可以看出 TabSpec 由两部分组成,标题部分,内容部分,两部分分别通过指定的策略显示,
根据不同的设置,选择显示仅文字的标题还是图文的,内容是指定id,还是指定工厂的。
接着看 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.
if (tabSpec.mIndicatorStrategy instanceof ViewIndicatorStrategy) {
mTabWidget.setStripEnabled(false);
}
//选项标题
mTabWidget.addView(tabIndicator);
//选项卡
mTabSpecs.add(tabSpec);
if (mCurrentTab == -1) {
setCurrentTab(0);
}
}
设置选项卡
public void setCurrentTab(int index) {
if (index < 0 || index >= mTabSpecs.size()) {
return;
}
if (index == mCurrentTab) {
return;
}
// notify old tab content
if (mCurrentTab != -1) {
//前面说的标签的内容显示的策略,关闭标签(也就是设置不可见而已
mTabSpecs.get(mCurrentTab).mContentStrategy.tabClosed();
}
mCurrentTab = index;
final TabHost.TabSpec spec = mTabSpecs.get(index);
// Call the tab widget's focusCurrentTab(), instead of just
// selecting the tab.
mTabWidget.focusCurrentTab(mCurrentTab);
// tab content
//通过内容显示策略获取选项卡内容,新建或者设置为可见而已
mCurrentView = spec.mContentStrategy.getContentView();
if (mCurrentView.getParent() == null) {
mTabContent
.addView(
mCurrentView,
new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));
}
if (!mTabWidget.hasFocus()) {
// if the tab widget didn't take focus (likely because we're in touch mode)
// give the current tab content view a shot
mCurrentView.requestFocus();
}
//mTabContent.requestFocus(View.FOCUS_FORWARD);
invokeOnTabChangeListener();
}
TabWidget 继承 LinearLayout
选项选择回调接口
static interface OnTabSelectionChanged {
void onTabSelectionChanged(int tabIndex, boolean clicked);
}
private class TabClickListener implements OnClickListener {
private final int mTabIndex;
private TabClickListener(int tabIndex) {
mTabIndex = tabIndex;
}
public void onClick(View v) {
mSelectionChangedListener.onTabSelectionChanged(mTabIndex, true);
}
}
添加选项,宽度均分
public void addView(View child) {
if (child.getLayoutParams() == null) {
//width:0 height:MATCH_PARENT weight:1
final LinearLayout.LayoutParams lp = new LayoutParams(
0,
ViewGroup.LayoutParams.MATCH_PARENT, 1.0f);
lp.setMargins(0, 0, 0, 0);
child.setLayoutParams(lp);
}
// Ensure you can navigate to the tab with the keyboard, and you can touch it
child.setFocusable(true);
child.setClickable(true);
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);
}
public void setCurrentTab(int index) {
if (index < 0 || index >= getTabCount() || index == mSelectedTab) {
return;
}
if (mSelectedTab != -1) {
getChildTabViewAt(mSelectedTab).setSelected(false);
}
mSelectedTab = index;
getChildTabViewAt(mSelectedTab).setSelected(true);
mStripMoved = true;
if (isShown()) {
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
}
}
public void onFocusChange(View v, boolean hasFocus) {
if (v == this && hasFocus && getTabCount() > 0) {
getChildTabViewAt(mSelectedTab).requestFocus();
return;
}
if (hasFocus) {
int i = 0;
int numTabs = getTabCount();
while (i < numTabs) {
if (getChildTabViewAt(i) == v) {
setCurrentTab(i);
mSelectionChangedListener.onTabSelectionChanged(i, false);
if (isShown()) {
// a tab is focused so send an event to announce the tab widget state
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
}
break;
}
i++;
}
}
}
一个例子;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_mtab);
this.tabhost = (TabHost) findViewById(android.R.id.tabhost);
tabhost.setup();
getLayoutInflater().inflate(R.layout.tab2,tabhost.getTabContentView());
tabhost.getTabWidget().setRightStripDrawable(R.mipmap.ic_launcher);
tabhost.addTab(tabhost.newTabSpec("t1")
.setIndicator("T1")
.setContent(new TabHost.TabContentFactory() {
@Override
public View createTabContent(String tag) {
return new LinearLayout(MTabActivity.this);
}
})
);
tabhost.addTab(tabhost.newTabSpec("t2")
.setIndicator(null,getResources().getDrawable(R.mipmap.ic_launcher))
.setContent(R.id.tab2)
);
}