TabHost实现机制_源码分析

来源:互联网 发布:ipadpro网络不稳定 编辑:程序博客网 时间:2024/05/24 06:27
0.TabHost使用Demo
布局文件:
<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" >


<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >


<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" >


<LinearLayout
android:id="@+id/tab1"
android:layout_width="match_parent"
android:layout_height="match_parent" >


<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Tab 1 Content" />


</LinearLayout>


<LinearLayout
android:id="@+id/tab2"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<TextView
android:id="@+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Tab 2 Content" />
</LinearLayout>


<LinearLayout
android:id="@+id/tab3"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<TextView
android:id="@+id/textView3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Tab 3 Content" />
</LinearLayout>
</FrameLayout>
</LinearLayout>
</TabHost>


代码:
public class MainActivity extends TabActivity {


@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);


TabHost tabHost = getTabHost();// findViewById找到TabHost
tabHost.setup();// 入口0

// setNewTab(context, tabHost, tag, title, icon, contentID);
this.setNewTab(this, tabHost, "tab1", R.string.textTabTitle1, android.R.drawable.star_on, R.id.tab1);
this.setNewTab(this, tabHost, "tab2", R.string.textTabTitle2, android.R.drawable.star_on, R.id.tab2);
this.setNewTab(this, tabHost, "tab3", R.string.textTabTitle3, android.R.drawable.star_on, R.id.tab3);


//tabHost.setCurrentTabByTag("tab2"); //-- optional to set a tab programmatically.
}


private void setNewTab(Context context, TabHost tabHost, String tag, int title, int icon, int contentID ){
TabSpec tabSpec = tabHost.newTabSpec(tag);// 入口1
String titleString = getString(title);
tabSpec.setIndicator(titleString)); // 入口2 
tabSpec.setContent(contentID);// 入口3  contentID=R.id.tab1(FrameLayout中)
tabHost.addTab(tabSpec);// 入口4
}
}


---------------------------------------------源码实现---------------------------------------------------


1.TabSpec.java
   // TabSpec干的活:绑定和记录对应的content的id,tab,和tab的tag.执行对应的策略
   /**
     * A tab has a tab indicator, content, and a tag that is used to keep
     * track of it.  This builder helps choose among these options.
     *
     * For the tab indicator, your choices are:
     * 1) set a label
     * 2) set a label and an icon
     *
     * For the tab content, your choices are:
     * 1) the id of a {@link View}
     * 2) a {@link TabContentFactory} that creates the {@link View} content.
     * 3) an {@link Intent} that launches an {@link android.app.Activity}.
     */
    public class TabSpec {


        private String mTag;


        private IndicatorStrategy mIndicatorStrategy;// 针对指针的策略
        private ContentStrategy mContentStrategy; // 针对指针对应的content的view的策略


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


    /**
     * Specifies what you do to create a tab indicator.
     */
    private static interface IndicatorStrategy {


        /**
         * Return the view for the indicator.
         */
        View createIndicatorView();
    }


    /**
     * Specifies what you do to manage the tab content.
     */
    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();
    }

/**
     * How to create the tab content via a view id.
     */
    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;
        }


        public void tabClosed() {
            mView.setVisibility(View.GONE);
        }
    }


    /**
     * How to create a tab indicator that just has a label.
     */
    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 android.view.LayoutInflater.inflate(int resource, ViewGroup root, boolean attachToRoot)
Inflate a new view hierarchy from the specified xml resource. Throws InflateException if there is an error.


mTabLayoutId = R.layout.tab_indicator_holo;
*/
            View tabIndicator = inflater.inflate(mTabLayoutId,// 从该布局文件中inflate出view
                    mTabWidget, //tab widget is the parent// mTabWidget==rootView .mTabWidget是TabHost中成员变量
                    false); // no inflate params


            final TextView tv = (TextView) tabIndicator.findViewById(R.id.title);// TabWidget
            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.getResources().getColorStateList(R.color.tab_indicator_text_v4));
            }


            return tabIndicator; // 返回tab_indicator_holo.xml布局
        }

public void tabClosed() {
            mView.setVisibility(View.GONE);
        }
    }

tab_indicator_holo.xml
<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" />// 默认ImageView是不可见的


<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>



// TabWidget类才是引擎
2.TabWidget.java  //class TabWidget extends LinearLayout
// TabWidget.java中的函数
/**
* Provides a way for {@link TabHost} to be notified that the user clicked on a tab indicator.
*/
void setTabSelectionListener(OnTabSelectionChanged listener) {
mSelectionChangedListener = listener;
}
// TabWidget.java中的函数
@Override
public void addView(View child) {
...
// Ensure you can navigate to the tab with the keyboard, and you can touch it
child.setFocusable(true);
child.setClickable(true);


super.addView(child);// 调用LinearLayout的添加子View


// 设置TabWidget每个item的点击事件(这样每个item点击之后就会触发事件监听的方法)
// TODO: detect this via geometry with a tabwidget listener rather
// than potentially interfere with the view's listener
child.setOnClickListener(new TabClickListener(getTabCount() - 1));
}

// registered with each tab indicator so we can notify tab host
private class TabClickListener implements OnClickListener {


private final int mTabIndex;


private TabClickListener(int tabIndex) {
mTabIndex = tabIndex;
}


public void onClick(View v) {
mSelectionChangedListener.onTabSelectionChanged(mTabIndex, true);// 该监听函数是在setTabSelectionListener中设置
}
}


3.TabHost.java   TabHost extends FrameLayout 
    public TabSpec newTabSpec(String tag) {
        return new TabSpec(tag);
    }

// 干的活:找到mTabWidget,mTabContent控件,设置监听点击TabWidget事件
public void setup() {
        mTabWidget = (TabWidget) findViewById(com.android.internal.R.id.tabs);// 选项卡集合
...


/*
这里在TabWidget(LinearLayout子类)设置选项卡改变监听
*/
        mTabWidget.setTabSelectionListener(new TabWidget.OnTabSelectionChanged() {// 设置监听事件
            public void onTabSelectionChanged(int tabIndex, boolean clicked) {
                setCurrentTab(tabIndex); // 1.当TabWidget的item被选中时候回调TabHost的setCurrentTab函数
                if (clicked) {
                    mTabContent.requestFocus(View.FOCUS_FORWARD);
                }
            }
        });


        mTabContent = (FrameLayout) findViewById(com.android.internal.R.id.tabcontent);
    }


/**
     * Add a tab.
     * @param tabSpec Specifies how to create the indicator and content.
     */
    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");
        }

// 调用LabelIndicatorStrategy的createIndicatorView
        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) {   
...

// 关闭旧的Content View
        // notify old tab content
        if (mCurrentTab != -1) {
            mTabSpecs.get(mCurrentTab).mContentStrategy.tabClosed();// 设置当前Content对应view为:mView.setVisibility(View.GONE);
        }


        mCurrentTab = index;
        final TabHost.TabSpec spec = mTabSpecs.get(index);


// 打开新的Content View---如果是FragmentTabHost,则使用宽度和高度为0的view填充
        // tab content
        mCurrentView = spec.mContentStrategy.getContentView();// 设置当前Content对应view为:mTabContent.setVisibility(View.VISIBLE);




        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);
// 2.调用
        invokeOnTabChangeListener();
    }

   private void invokeOnTabChangeListener() {
if (mOnTabChangeListener != null) {
mOnTabChangeListener.onTabChanged(getCurrentTabTag());// 如果有设置监听,则执行回调函数
}
}


注:FragmentTabHost.java覆盖掉onTabChanged (TabHost子类)
public FragmentTabHost(Context context, AttributeSet attrs) {
super(context, attrs);
initFragmentTabHost(context, attrs);
}


private void initFragmentTabHost(Context context, AttributeSet attrs) {
TypedArray a = context.obtainStyledAttributes(attrs,
new int[] { android.R.attr.inflatedId }, 0, 0);
mContainerId = a.getResourceId(0, 0);
a.recycle();


super.setOnTabChangedListener(this);// FragmentTabHost实现了OnTabChangedListener接口,所以当tab点击的时候onTabChanged必被调用
}

   public void setOnTabChangedListener(OnTabChangeListener l) {
mOnTabChangeListener = l;
}

// 在TabWidget的item被选中之后,该回调函数被调用
@Override
public void onTabChanged(String tabId) {
if (mAttached) {
FragmentTransaction ft = doTabChanged(tabId, null);
if (ft != null) {
ft.commit();
}
}
,,,
}


private FragmentTransaction doTabChanged(String tabId, FragmentTransaction ft) {
TabInfo newTab = null;
for (int i=0; i<mTabs.size(); i++) {
TabInfo tab = mTabs.get(i);
if (tab.tag.equals(tabId)) {
newTab = tab;
}
}

if (mLastTab != newTab) {
if (ft == null) {
ft = mFragmentManager.beginTransaction();
}
if (mLastTab != null) {
if (mLastTab.fragment != null) {
ft.detach(mLastTab.fragment);
}
}
if (newTab != null) {
if (newTab.fragment == null) {
newTab.fragment = Fragment.instantiate(mContext,
newTab.clss.getName(), newTab.args);
// 把当前fragment添加到mContainrId对应的ViewGroup中 mContainerId==R.id.realtabcontent,在setup的时候传递进来
ft.add(mContainerId, newTab.fragment, newTab.tag);
} else {
ft.attach(newTab.fragment);
}
}


mLastTab = newTab;
}
return ft;
}

总结:
1.呈现:TabHost持有如下(所以TabHost是界面呈现的"引擎"/ViewGroup作用)
   private TabWidget mTabWidget;
private FrameLayout mTabContent;
private List<TabSpec> mTabSpecs = new ArrayList<TabSpec>(2);

private OnTabChangeListener mOnTabChangeListener;// 响应函数

2.事件响应:
TabHost的mOnTabChangeListener回调设置给mTabWidget,这样当TabWidget的view被选中,就会回调该接口.所以TabWidget是引擎  

如果在TabWidget的内部每个View被点击的时候能够调用TabHost的
当点击TabWidget的内部View(TabWidget.java)-->调用先前设置给TabWidget的接口onTabSelectionChanged(TabHost.java)-->
接口函数调用onTabSelectionChanged中的setCurrentTab(tabIndex)(TabHost.java)-->invokeOnTabChangeListener(TabHost.java)-->
mOnTabChangeListener.onTabChanged(getCurrentTabTag());执行具体响应事件(TabHost.java)

如果是FragmentTabHost.java
事件响应过程同上,由于FragmentTabHost实现OnTabChangeListener接口,并且在初始化的时候绑定该接口到TabHost的监听器,点击的时候
FragmentTabHost的OnTabChangeListener接口会被调用
               









0 0