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));    }}

效果图:
id为tab1 id为tab2 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。所以不会显示图片。

转载请注明出处,谢谢!

0 0
原创粉丝点击