天天看點

淺談Android的TabHost

這篇文章主要針對 TabHost 進行分析。相信大家和我一樣,在學習TabHost的時候遇到了很多問題,這裡我就把我所學到的和大家分享一二。

本人才疏學淺,若有錯誤,歡迎指正。

首先 TabHost 的用法相信大家都知道,就是用來放置标簽頁。話不多說,來看代碼。

用法如下:

TabHost mTabHost = (TabHost)  findViewById(android.R.id.tabhost);
mTabHost.setup();
mTabHost.addTab(TabSpec spec);
           

根據用法,分析代碼:

1.首先看 TabHost 的成員變量:

淺談Android的TabHost

mTabWidget 就是我們 Tab 的 Indicator。

mTabContent 就是我們 Tab 真正要顯示的内容。

mCurrentView 就是我們目前展示的内容。

mTabSpecs 就是我們 Tab 的資訊,使用的是政策模式。可以了解為TabSpecs是一個實體。

接下來看構造函數,由于構造函數沒有什麼難點,隻有一個函數,發上來大家看看:

private void initTabHost() {
        setFocusableInTouchMode(true);
        setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
        mCurrentTab = -1;
        mCurrentView = null;
    }
           

隻是對變量進行了初始化已經焦點設定。

2.setup()函數,這個是重點之一:

代碼如下:

public void setup() {
//這也就是為何xml中的ID 必須為系統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);
                }
            }
        });

//這也就是為何xml中的ID 必須為系統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'");
        }
    }
           

setup的函數意圖在于,給自己為初始化的變量指派,以及對焦點,按鍵進行處理。

3.addTab()。

這個函數寫的非常有意思,代碼如下:

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");
        }
//通過政策模式,得到目前Tab Indicator的視圖
        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);
        }

//在TabWidget(線性布局)中,加入tabIndicator。TabWidget會對childView進行特殊處理,具體看源碼。
        mTabWidget.addView(tabIndicator);
//在記載Tab資訊的List中,加入這條資訊
mTabSpecs.add(tabSpec);


        if (mCurrentTab == -1) {
    //設定目前Tab,比較複雜,繼續上源碼
            setCurrentTab(0);
        }
    }

 public void setCurrentTab(int index) {
        if (index < 0 || index >= mTabSpecs.size()) {
            return;
        }


        if (index == mCurrentTab) {
            return;
        }


//關閉上一個Tab的Content,通過目前政策去調用tabClosed,一般的政策都是設定目前View為不可見
        // 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);

//getContentView()這個函數的意圖為,設定目前這個View為可見的,并傳回這個可見的View。
        // tab content
        mCurrentView = spec.mContentStrategy.getContentView();

//如果目前的這個可見的View并沒有在View的樹結構之上,就把它添加上去,如果已經存在,就不需要這樣做了。
        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();
    }
           

通過以上三步,就大概了解了TabHost是如何運作的。而TabHost中,Tabwidget和TabContent是如何添加到TabHost之中的呢?之前我一直在代碼中尋找卻苦苦得不到答案,後來突然意識到,其實在布局檔案中,已經把TabWidget和TabContent添加到了TabHost中,是以對于TabWidget和TabContent的布局,我們都可以進行單獨的定制。在3.0以後,在Fragment 出現的同時,也同時出現了FragmentTabHost,FragmentTabHost 作為TabHost 的子類,修改的十分簡單卻十分好用。

        下篇部落格繼續分析FragmentTabHost的源碼。

        第一次寫部落格,寫的非常爛,大家湊合看吧,不好意思。

       QQ:157688302 歡迎聯系,探讨Android相關知識