TabHost的使用及原理
来源:互联网 发布:vb滚动条属性 编辑:程序博客网 时间:2024/05/22 06:52
一、TabHost是什么
TabHost在文档中的定义是:Container for a tabbed window view.直译过来是一个选项卡式窗口视图容器。简单来说,就是类似于微信首页那种,有几个选择卡,点击选择卡可以跳到不同的页面。
二、在了解TabHost之前,有以下知识是需要了解的:
1、TabHost的xml文件中,TabHost、TabWidget和显示TabHost的部分(通常用FrameLayout),这3部分的id是不可修改的,必须使用Android提供的id。
2、TabHost在自定义前,需要初始化,也就是调用tab.setup();(tab是TabHost对象)
3、使用TabHost在几个activity间跳转时,需要继承AcitvityGroup类。并且在初始化时需要调用tab.setup(ActivityGroup.getLocalActivityManager());
4、TabSpec是TabHost的内部类,TabHost需要通过tab.addTab(TabSpec对象)来添加组件,添加的组件之间可以通过TabHost跳转。而实现跳转最重要的是TabSpec的setContent方法,简单来说就是TabSpec的setContent方法传入什么参数,决定TabHost会跳转到哪里。(如果这里不懂没有关系,讲原理的时候会加上源码,到时候就懂了)
三、TabHost有3种实现方式:(通过TabSpec的setContent方法实现)
1、第一种是把需要改变的布局全部写在TabHost的xml中,然后setContent传入id。
2、第二种是在几个Activity间跳转,setContent传入intent对象。
3、第三种是自己定义几种View,然后在这几种View之间跳转。setContent传入TabHost的内部类TabContentFactory的对象。需要重写TabContentFactory的抽象方法。
四、具体使用实现
1、第一种:把需要改变的布局全部写在TabHost的xml中
这种方法是将所有要跳转的布局全部写在TabHost控件中。在给TabHost的对象添加组件时,给TabSpec的setContent方法传入int类型的参数,参数的意义为:要跳转到的布局的id,这个布局为TabHost的子控件。示例如下:
tabspec_int.xml
<?xml version="1.0" encoding="utf-8"?><!-- TabHost组件id值不可变--><TabHost xmlns:android="http://schemas.android.com/apk/res/android" android:id="@android:id/tabhost" android:layout_height="fill_parent" android:layout_width="fill_parent"> <RelativeLayout android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent"> <!-- TabWidget组件id值不可变--> <TabWidget android:id="@android:id/tabs" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_alignParentBottom="true"> </TabWidget> <!-- FrameLayout布局,id值不可变--> <FrameLayout android:id="@android:id/tabcontent" android:layout_width="fill_parent" android:layout_height="fill_parent" android:layout_above="@android:id/tabs"> <!-- 第一个tab的布局 --> <LinearLayout android:id="@+id/tab1" android:layout_width="match_parent" android:layout_height="match_parent" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="第一个tab的布局" /> </LinearLayout> <!-- 第二个tab的布局 --> <LinearLayout android:id="@+id/tab2" android:layout_width="match_parent" android:layout_height="match_parent" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="第二个tab的布局" /> </LinearLayout> <!-- 第三个tab的布局 --> <LinearLayout android:id="@+id/tab3" android:layout_width="match_parent" android:layout_height="match_parent" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="第三个tab的布局" /> </LinearLayout> </FrameLayout> </RelativeLayout> </TabHost>
上面的xml中,tabcontent中有3个tab的布局,id分别为tab1、tab2和tab3。在TabspecInt.java中,在给tab.addTab添加组件时,给TabSpec的setContent传入tab1、tab2和tab3的id。
TabspecInt.java
package com.example.tabtest;import android.app.Activity;import android.os.Bundle;import android.widget.TabHost;public class TabspecInt extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.tabspec_int); TabHost tab = (TabHost) findViewById(android.R.id.tabhost); //初始化TabHost容器 tab.setup(); //在TabHost创建标签,然后设置:标题/图标/标签页布局 tab.addTab(tab.newTabSpec("tab1").setIndicator("标签1" , getResources().getDrawable(R.drawable.ic_launcher)).setContent(R.id.tab1)); tab.addTab(tab.newTabSpec("tab2").setIndicator("标签2" , null).setContent(R.id.tab2)); tab.addTab(tab.newTabSpec("tab3").setIndicator("标签3" , null).setContent(R.id.tab3)); }}
效果图:
2、第二种:在几个Activity间跳转
这种方法应该是最常用的方法。意思就是让TabHost可以在几个Activity之间进行跳转。在给TabHost的对象添加组件时,给TabSpec的setContent方法传入intent类型的参数,参数的意义为:跳转到哪个activity(和startActivity的参数相同)。示例如下:
tabspec_intent.xml
<?xml version="1.0" encoding="utf-8"?><!-- TabHost组件id值不可变--><TabHost xmlns:android="http://schemas.android.com/apk/res/android" android:id="@android:id/tabhost" android:layout_height="fill_parent" android:layout_width="fill_parent"> <RelativeLayout android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent"> <!-- TabWidget组件id值不可变--> <TabWidget android:id="@android:id/tabs" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_alignParentBottom="true"> </TabWidget> <!-- FrameLayout布局,id值不可变--> <FrameLayout android:id="@android:id/tabcontent" android:layout_width="fill_parent" android:layout_height="fill_parent" android:layout_above="@android:id/tabs"> </FrameLayout> </RelativeLayout></TabHost>
tabspec_intent.xml 和 tabspec_int.xml 相比,在tabcontent中,什么组件都没有。这个layout看起来更加简单。之后会在tabcontent的位置显示若干Activity。在TabspecInt.java中,在给tab.addTab添加组件时,给TabSpec的setContent传入intent对象,intent对象中包含要跳转到的activity名。
TabspecIntent.java
package com.example.tabtest;import android.app.Activity;import android.app.LocalActivityManager;import android.content.Intent;import android.os.Bundle;import android.util.Log;import android.widget.TabHost;import android.app.ActivityGroup;import android.content.Intent;import android.os.Bundle;import android.widget.TabHost;public class TabspecIntent extends ActivityGroup { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.tabspec_intent); TabHost tab = (TabHost) findViewById(android.R.id.tabhost); //初始化TabHost容器 tab.setup(getLocalActivityManager()); //在TabHost创建标签,然后设置:标题/图标/标签页布局 tab.addTab(tab.newTabSpec("tab1").setIndicator("标签1" , getResources().getDrawable(R.drawable.ic_launcher)).setContent(new Intent(this, Tab1.class))); tab.addTab(tab.newTabSpec("tab2").setIndicator("标签2" , null).setContent(new Intent(this, Tab2.class))); tab.addTab(tab.newTabSpec("tab3").setIndicator("标签3" , null).setContent(new Intent(this, Tab3.class))); }}
这里要注意的是:这个activity继承的是ActivityGroup类,并且在tab初始化时,调用的tab.setup(getLocalActivityManager());
效果图:
Tab1.java效果图:
Tab2.java效果图:
Tab3.java效果图:
TabHost效果图:
3、第三种:在自己定义的几个view之间跳转。
这里的几个view都是用LayoutInflater类生成xml的,然后把这些view添加为TabHost的组件。这种方法需要实现TabHost的一个内部接口:TabContentFactory,并且实现TabContentFactory的方法:createTabContent,这个方法返回的是View类型对象,也就是点击选择卡后,跳转时要展示的View。示例如下:
xml还可以使用tabspec_intent.xml,因为这种方法实现要跳转到的view时,用到的xml并没有写在TabHost中。因此,xml的TabHost中的tabcontent中,也是什么组件都没有。
TabspecFactory.java
package com.example.tabtest;import android.app.Activity;import android.os.Bundle;import android.view.LayoutInflater;import android.view.View;import android.widget.TabHost;import android.widget.TabHost.TabContentFactory;public class TabspecFactory extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.tabspec_intent); TabHost tab = (TabHost) findViewById(android.R.id.tabhost); //初始化TabHost容器 tab.setup(); //在TabHost创建标签,然后设置:标题/图标/标签页布局 tab.addTab(tab.newTabSpec("tab1").setIndicator("标签1" , getResources().getDrawable(R.drawable.ic_launcher)).setContent(factory1)); tab.addTab(tab.newTabSpec("tab2").setIndicator("标签2" , null).setContent(factory2)); tab.addTab(tab.newTabSpec("tab3").setIndicator("标签3" , null).setContent(factory3)); } private TabContentFactory factory1 = new TabContentFactory() { @Override public View createTabContent(String tag) { return LayoutInflater.from(TabspecFactory.this).inflate(R.layout.tab1, null); } }; private TabContentFactory factory2 = new TabContentFactory() { @Override public View createTabContent(String tag) { return LayoutInflater.from(TabspecFactory.this).inflate(R.layout.tab2, null); } }; private TabContentFactory factory3 = new TabContentFactory() { @Override public View createTabContent(String tag) { return LayoutInflater.from(TabspecFactory.this).inflate(R.layout.tab3, null); } };}
效果图:
到这里为止,所有的使用都讲完了,源码可以在此处下载。源码下载
接下来要讲TabHost的原理了,对原理感兴趣的朋友可以看一看。本文会对TabHost的源码进行讲解,本文展示的源码不是全部源码,只截出一些重要的代码放出,基本不影响阅读,如果想要看完整源码,可以在android的sdk中找一下TabHost.java,自行阅读。本文使用的是android4.4的源码。
五、TabHost原理讲解
TabHost是通过addTab接口,添加若干组件,然后可以实现在若干组件之间跳转。首先看看addTab的源码:
TabHost.java的addTab方法:
public void addTab(TabSpec tabSpec) { ...... View tabIndicator = tabSpec.mIndicatorStrategy.createIndicatorView(); tabIndicator.setOnKeyListener(mTabKeyListener); ...... mTabWidget.addView(tabIndicator); mTabSpecs.add(tabSpec); ...... }
addTab的参数是TabSpec类。TabSpec类是TabHost的内部类,记录了TabHost中组件的信息,里面包含了indicator和content,indicator是TabHost的选择卡上的相关信息,content是TabHost点击选择卡后所展示内容的信息。
对于indicator,你可以有两种设定方式:
1)设定一个label,也就是选择卡上显示的文字。
2)设定一个label和一个icon,也就是选择卡上显示的文字和显示的图标。
对于content,你可以有3中设定方式:
1)设定一个View,需要将要跳转的View写在TabHost的xml中。
2)设定一个intent,可以展示对应的activity。
3)设定一个TabHost.TabContentFactory,TabContentFactory是TabHost的一个内部接口,其中包含一个方法:createTabContent。使用的时候,需要继承TabContentFactory,并让createTabContent方法返回需要跳转的View。这种设定方式和第一种相比,不需要把要跳转的View的布局写在TabHost的xml中,可以写在其他的xml中或者直接在java中用代码实现一个View,然后在createTabContent方法中返回这个View,这个View就是点击选择卡后要跳转到的界面。
下来看一看TabSpec类的源码:
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) { 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) { 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) { 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; } }
主要看看setContent方法。一共有3种参数,分别是:int类型、intent类型和TabContentFactory类型。
接下来分别看一下实现:
int类型:
private class ViewIdContentStrategy implements ContentStrategy { private final View mView; private ViewIdContentStrategy(int viewId) { 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; } } private FrameLayout mTabContent; mTabContent = (FrameLayout) findViewById(com.android.internal.R.id.tabcontent);
ContentStrategy是一个内部接口,里面有两个方法getContentView和tabClosed。在TabHost的选择卡发生改变的时候,会调用TabHost.setCurrentTab方法,这个方法中会调用spec.mContentStrategy.getContentView()获取跳转后要展示的View。
如果Tabspec.setContent的参数是int类型时,就会调用ViewIdContentStrategy.getContentView。同理,如果Tabspec.setContent的参数是intent类型,会调用IntentContentStrategy.getContentView;如果Tabspec.setContent的参数是TabContentFactory类型,会调用FactoryContentStrategy.getContentView。
TabContentFactory类型:
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; } }
mFactory是TabSpec.setContent传入的FactoryContentStrategy对象。mFactory.createTabContent方法是在Activity中实现的。
intent类型:
private class IntentContentStrategy implements ContentStrategy { private final String mTag; private final Intent mIntent; private View mLaunchedView; private IntentContentStrategy(String tag, Intent intent) { mTag = tag; mIntent = intent; } public View getContentView() { ...... final Window w = mLocalActivityManager.startActivity( mTag, mIntent); final View wd = w != null ? w.getDecorView() : null; if (mLaunchedView != wd && mLaunchedView != null) { if (mLaunchedView.getParent() != null) { mTabContent.removeView(mLaunchedView); } } mLaunchedView = wd; ...... return mLaunchedView; } public void tabClosed() { if (mLaunchedView != null) { mLaunchedView.setVisibility(View.GONE); } } } protected LocalActivityManager mLocalActivityManager = null; public void setup(LocalActivityManager activityGroup) { setup(); mLocalActivityManager = activityGroup; }
在IntentContentStrategy类中,可以看到getContentView方法的返回参数mLaunchedView就是mLocalActivityManager.startActivity(mTag, mIntent).getDecorView();
而mLocalActivityManager变量是在TabHost的setup(LocalActivityManager)中初始化的,这也就是为什么前面说,使用TabHost在Activity间跳转时,需要继承ActivityGroup类。并且在初始化时需要调用tab.setup(ActivityGroup.getLocalActivityManager());
ActivityGroup类的getLocalActivityManager方法可以获取LocalActivityManager对象,来看一看ActivityGroup的代码:
ActivityGroup.java
package android.app;import java.util.HashMap;import android.content.Intent;import android.os.Bundle;@Deprecatedpublic class ActivityGroup extends Activity { protected LocalActivityManager mLocalActivityManager; public ActivityGroup(boolean singleActivityMode) { mLocalActivityManager = new LocalActivityManager(this, singleActivityMode); } @Override protected void onCreate(Bundle savedInstanceState) { ...... mLocalActivityManager.dispatchCreate(states); } @Override protected void onResume() { super.onResume(); mLocalActivityManager.dispatchResume(); } @Override protected void onPause() { super.onPause(); mLocalActivityManager.dispatchPause(isFinishing()); } @Override protected void onStop() { super.onStop(); mLocalActivityManager.dispatchStop(); } @Override protected void onDestroy() { super.onDestroy(); mLocalActivityManager.dispatchDestroy(isFinishing()); } public final LocalActivityManager getLocalActivityManager() { return mLocalActivityManager; }}
可以看出,ActivityGroup类就是创建了LocalActivityManager对象,并且调用了LocalActivityManager的方法。所以,Tabspec_intent.java可以写成下面的这样:
Tabspec_intent.xml另一种写法:
public class TabspecIntent extends Activity { LocalActivityManager mLocalActivityManager; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.tabspec_intent); TabHost tab = (TabHost) findViewById(android.R.id.tabhost); mLocalActivityManager = new LocalActivityManager(this, true); mLocalActivityManager.dispatchCreate(savedInstanceState); //初始化TabHost容器 tab.setup(mLocalActivityManager); //在TabHost创建标签,然后设置:标题/图标/标签页布局 tab.addTab(tab.newTabSpec("tab1").setIndicator("标签1" , getResources().getDrawable(R.drawable.ic_launcher)).setContent(new Intent(this, Tab1.class))); tab.addTab(tab.newTabSpec("tab2").setIndicator("标签2" , null).setContent(new Intent(this, Tab2.class))); tab.addTab(tab.newTabSpec("tab3").setIndicator("标签3" , null).setContent(new Intent(this, Tab3.class))); } @Override protected void onResume() { super.onResume(); mLocalActivityManager.dispatchResume(); } @Override protected void onPause() { super.onPause(); mLocalActivityManager.dispatchPause(isFinishing()); } @Override protected void onStop() { super.onStop(); mLocalActivityManager.dispatchStop(); } @Override protected void onDestroy() { super.onDestroy(); mLocalActivityManager.dispatchDestroy(isFinishing()); }}
如上面代码所示,Tabspec_intent.java可以继承Activity,只需要在Activity中创建LocalActivityManager对象,并调用LocalActivityManager的方法即可。
现在再来看看LocalActivityManager类是如何让Acttivity可以放入TabHost中的。前面说过,关键的代码是:
mLocalActivityManager.startActivity(mTag, mIntent).getDecorView();
mLocalActivityManager.java
public Window startActivity(String id, Intent intent) { ...... } public Activity getActivity(String id) { LocalActivityRecord r = mActivities.get(id); return r != null ? r.activity : null; }
看LocalActivityManager的源码,可以知道:每个要放入TabHost的Activity,只需要调用一次LocalActivityManager.startActivity即可,之后可以使用LocalActivityManager.getActivity获取Activity,然后再调用Activity.getWindow().getDecorView()获取View。当然,每次都调用LocalActivityManager.startActivity也是可以的。
也就是说,可以不使用TabHost,也可以达到点击选择卡,跳转到不同页面。
具体实现与LocalActivityManager的源码的源码解析有关。不过LocalActivityManager已经不建议使用了,建议使用Fragment,但是了解一下Tab实现方法,如果哪天有这个需要,也可以用一用。
六、为什么选择卡里没有图片
可以看到代码中,的确给选择卡中设置了图片,可是效果图中并没有图片。
可以看到Tabspec.java中,给选择卡添加图片的接口如下:
public TabSpec setIndicator(CharSequence label, Drawable icon) { mIndicatorStrategy = new LabelAndIconIndicatorStrategy(label, icon); return this; }
LabelAndIconIndicatorStrategy.java
private class LabelAndIconIndicatorStrategy implements IndicatorStrategy { private final CharSequence mLabel; private final Drawable mIcon; private LabelAndIconIndicatorStrategy(CharSequence label, Drawable icon) { mLabel = label; mIcon = icon; } 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); final ImageView iconView = (ImageView) tabIndicator.findViewById(R.id.icon); ...... return tabIndicator; } }
这里使用的布局是mTabLayoutId,这个变量的定义如下:
private int mTabLayoutId; mTabLayoutId = R.layout.tab_indicator_holo;
R.layout.tab_indicator_holo在sdk中没有,源码中可以找到。在源码中搜索tab_indicator_holo,可以在
Intel_x86_sysimg_4.4_Source_Files_20131206\frameworks\base\core\res\res\layout文件夹中找到tab_indicator_holo.xml,代码如下:
tab_indicator_holo.xml
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_height="?android:attr/actionBarSize" android:orientation="horizontal" style="@android:style/Widget.Holo.Tab"> <ImageView android:id="@android:id/icon" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:visibility="gone" /> <TextView android:id="@android:id/title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" style="@android:style/Widget.Holo.TabText" /></LinearLayout>
上面的ImageView的visibility为gone。所以不会显示图片。
转载请注明出处,谢谢!
- TabHost的使用及原理
- Android中TabHost的原理及使用
- TabHost及ActivityGroup的使用总结
- Andriod TabHost的使用TabHost
- tabhost简单使用及tabhost源码分析
- tabhost简单使用及tabhost源码分析
- TabHost各个选项卡之间传递对象、参数及TabHost的使用
- Android TabHost的使用
- Android TabHost的使用
- TabActivity & TabHost 的使用
- Android TabHost的使用
- Tabhost的使用
- Android TabHost的使用
- android Tabhost的使用
- TabHost的使用
- Android TabHost的使用 .
- Android TabHost的使用
- Android TabHost的使用
- HDU 3435 A new Graph Game(二分图最优匹配:有向环覆盖)
- Android-Universal-Image-Loader优缺点
- 用AT89S52控制单个舵机并且三段数码管显示转的角度
- 在WampServer环境下安装Drupal框架
- Spring Ioc原理
- TabHost的使用及原理
- activity 生命周期
- Linux中profile、bashrc、bash_profile之间的区别和联系
- WKWebView捕获HTML弹出的Alert和Confirm
- 在查找预编译头使用时跳过解决
- update-database 到azure sql database
- 冒泡、鸡尾酒、选择、插入、归并、快速排序的C++程序
- C++纯虚函数与抽象类
- 【 bzoj 2553 】 [BeiJing2011]禁忌 - AC自动机+矩阵乘法