1. 程式人生 > >超詳細!ActionBar 使用·詳解 .

超詳細!ActionBar 使用·詳解 .

一、ActionBar介紹

  在Android 3.0中除了我們重點講解的Fragment外,Action Bar也是一個非常重要的互動元素,Action Bar取代了傳統的tittle bar和menu,在程式執行中一直置於頂部,對於Android平板裝置來說螢幕更大它的標題使用Action Bar來設計可以展示更多豐富的內容,方便操控。

二、ActionBar的功能

  用圖的方式來講解它的功能

  

  <1> ActionBar的圖示,可顯示軟體圖示,也可用其他圖示代替。當軟體不在最高階頁面時,圖示左側會顯示一個左箭頭,使用者可以通過這個箭頭向上導航;

  <2> 如果你的應用要在不同的View中顯示資料,這部分允許使用者來切換檢視。一般的作法是用一個下拉選單或者是Tab選項卡。如果只有一個介面,那這裡可以顯示應用程式的標題或者是更長一點的商標資訊;

  <3> 兩個action按鈕,這裡放重要的按鈕功能,為使用者進行某項操作提供直接的訪問;

  <4> overflow按鈕,放不下的按鈕會被置於“更多...”選單項中,“更多...”選單項是以下拉形式實現的。

三、ActionBar 奧義·詳解

1、新增ActionBar

    ActionBar的新增非常簡單,只需要在AndroidManifest.xml中指定Application或Activity的theme是Theme.Holo或其子類就可以了,在Android 3.0及更高的版本中,Activity中都預設包含有ActionBar元件。

2、取消ActionBar

  如果需要隱藏Action Bar可以在你的Activity的屬性中設定主題風格為NoTitleBar在你的manifest檔案中

<activity android:theme="@android:style/Theme.NoTitleBar">

  還有一種做法,在執行時呼叫hide()方法也可以隱藏ActionBar,呼叫show()方法來顯示ActionBar()。

ActionBar actionBar = getActionBar();  
actionBar.hide();  

  當你隱藏ActionBar時,系統會將Activity的整個內容充滿整個空間。

  注意:如果使用一個主題(theme)來移除Activity上得ActionBar,那麼視窗將不再會有ActionBar,因此在執行時也就沒有辦法來新增ActionBar——呼叫getActionBar()方法會返回null值。

3.修改Action Bar的圖示和標題

預設情況下,系統會使用<application>或者<activity>中icon屬性指定的圖片來作為ActionBar的圖示,但是我們也可以改變這一預設行為。如果我們想要使用另外一張圖片來作為ActionBar的圖示,可以在<application>或者<activity>中通過logo屬性來進行指定,而標題中的內容使用label屬性來指定。比如專案的res/drawable目錄下有一張cnblog_icon.png圖片,就可以在AndroidManifest.xml中這樣指定:

 <activity
            android:name=".MainActivity"
            android:label="召喚ActionBar吧"
            android:logo="@drawable/cnblog_icon" >

效果圖如下:

4.新增Action按鈕

ActionBar還可以根據應用程式當前的功能來提供與其相關的Action按鈕,這些按鈕都會以圖示或文字的形式直接顯示在ActionBar上。當然,如果按鈕過多,ActionBar上顯示不完,多出的一些按鈕可以隱藏在overflow裡面(最右邊的三個點就是overflow按鈕),點選一下overflow按鈕就可以看到全部的Action按鈕了。

當Activity啟動的時候,系統會呼叫Activity的onCreateOptionsMenu()方法來取出所有的Action按鈕,我們只需要在這個方法中去載入一個menu資源,並把所有的Action按鈕都定義在資原始檔裡面就可以了。

那麼我們先來看下menu資原始檔該如何定義,程式碼如下所示:

複製程式碼

複製程式碼

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" >

    <item
        android:id="@+id/user_p"
        android:icon="@drawable/icon_user_p"
        android:showAsAction="always"
        android:title="使用者"/>
    <item
        android:id="@+id/write_p"
        android:icon="@drawable/icon_write_p"
        android:showAsAction="always"
        android:title="釋出"/>
    <item
        android:id="@+id/favo_p"
        android:icon="@drawable/icon_favo_p"
        android:showAsAction="never"
        android:title="收藏"/>

</menu>

複製程式碼

複製程式碼

可以看到,這裡我們通過兩個<item>標籤定義了三個Action按鈕。<item>標籤中又有一些屬性,其中id是該Action按鈕的唯一識別符號,icon用於指定該按鈕的圖示,title用於指定該按鈕可能顯示的文字(在圖示能顯示的情況下,通常不會顯示文字),actionViewClass用來指定一個構建視窗所使用的佈局資源,showAsAction則指定了該按鈕顯示的位置,主要有以下幾種值可選:

fRoom 會顯示在Item中,但是如果已經有4個或者4個以上的Item時會隱藏在溢位列表中。當然個
數並不僅僅侷限於4個,依據螢幕的寬窄而定
never 永遠不會顯示。只會在溢位列表中顯示,而且只顯示標題,所以在定義item的時候,最好
把標題都帶上。
always 無論是否溢位,總會顯示。
withText withText值示意Action bar要顯示文字標題。Action bar會盡可能的顯示這個
標題,但是,如果圖示有效並且受到Action bar空間的限制,文字標題有可
能顯示不全。
   collapseActionView   聲明瞭這個操作視窗應該被摺疊到一個按鈕中,當用戶選擇這個按鈕時,這個操作視窗展開。否則,
這個操作視窗在預設的情況下是可見的,並且即便在用於不適用的時候,也要佔據操作欄的有效空間。
一般要配合ifRoom一起使用才會有效果。

接著,重寫Activity的onCreateOptionsMenu()方法,程式碼如下所示:

複製程式碼

複製程式碼

@Override 
public boolean onCreateOptionsMenu(Menu menu) {  
    MenuInflater inflater = getMenuInflater();  
    inflater.inflate(R.menu.menu_main, menu);  
return super.onCreateOptionsMenu(menu);  
}  

複製程式碼

複製程式碼

這部分程式碼很簡單,僅僅是呼叫了MenuInflater的inflate()方法來載入menu資源就可以了。現在重新執行一下程式,結果如下圖所示:

可以看到,menu_search和menu_setting這兩個按鈕已經在ActionBar中顯示出來了,而menu_delete這個按鈕由於showAsAction屬性設定成了never,所以被隱藏到了overflow當中,只要點選一下overflow按鈕就可以看到它了。

這裡我們注意到,顯示在ActionBar上的按鈕都只有一個圖示而已,我們在title中指定的文字並沒有顯示出來。沒錯,title中的內容通常情況下只會在overflow中顯示出來,ActionBar中由於螢幕空間有限,預設是不會顯示title內容的。但是出於以下幾種因素考慮,即使title中的內容無法顯示出來,我們也應該給每個item中都指定一個title屬性:

  • 當ActionBar中的剩餘空間不足的時候,如果Action按鈕指定的showAsAction屬性是ifRoom的話,該Action按鈕就會出現在overflow當中,此時就只有title能夠顯示了。
  • 如果Action按鈕在ActionBar中顯示,使用者可能通過長按該Action按鈕的方式來檢視到title的內容。

5.響應Action按鈕的點選事件

當用戶點選Action按鈕的時候,系統會呼叫Activity的onOptionsItemSelected()方法,通過方法傳入的MenuItem引數,我們可以呼叫它的getItemId()方法和menu資源中的id進行比較,從而辨別出使用者點選的是哪一個Action按鈕,比如:

複製程式碼

複製程式碼

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
         switch (item.getItemId()) {  
         case R.id.user_p:  
                Toast.makeText(this, "你點選了“使用者”按鍵!", Toast.LENGTH_SHORT).show();  
                return true;  
            case R.id.write_p:  
                Toast.makeText(this, "你點選了“釋出”按鍵!", Toast.LENGTH_SHORT).show();  
                return true;  
            case R.id.favo_p:  
                Toast.makeText(this, "你點選了“收藏”按鍵!", Toast.LENGTH_SHORT).show();  
                return true;  
            default:  
                return super.onOptionsItemSelected(item);  
            }  
    }

複製程式碼

複製程式碼

可以看到,我們讓每個Action按鈕被點選的時候都彈出一個Toast,現在重新執行一下程式碼,結果如下圖所示:

5.通過Action Bar圖示進行導航

啟用ActionBar圖示導航的功能,可以允許使用者根據當前應用的位置來在不同介面之間切換。比如,A介面展示了一個列表,點選某一項之後進入了B介面,這時B介面就應該啟用ActionBar圖示導航功能,這樣就可以回到A介面。

我們可以通過呼叫setDisplayHomeAsUpEnabled()方法來啟用ActionBar圖示導航功能,比如:

setTitle("Yanis");  
setContentView(R.layout.activity_main);
ActionBar actionBar = getActionBar();  
actionBar.setDisplayHomeAsUpEnabled(true); 

現在重新執行一下程式,結果如下圖所示:

可以看到,在ActionBar圖示的左側出現了一個向左的箭頭,通常情況下這都表示返回的意思,因此最簡單的實現就是在它的點選事件裡面加入finish()方法就可以了,如下所示:

複製程式碼

複製程式碼

switch (item.getItemId()) {  
        case android.R.id.home:  
            finish();
            return true; 
        ...
        }  

複製程式碼

複製程式碼

當點選ActionBar圖示的時候,系統同樣會呼叫onOptionsItemSelected()方法,並且此時的itemId是android.R.id.home,所以finish()方法也就是加在這裡的了。

現在看上去,ActionBar導航和Back鍵的功能貌似是一樣的。沒錯,如果我們只是簡單地finish了一下,ActionBar導航和Back鍵的功能是完全一樣的,但ActionBar導航的設計初衷並不是這樣的,它和Back鍵的功能還是有一些區別的,舉個例子吧。

上圖中的Conversation List是收件箱的主介面,現在我們點選第一封郵件會進入到Conversation1 details介面,然後點選下一封郵件會進入到Conversation 2 details介面,再點選下一封郵箱會進入到Conversation3 details介面。好的,這個時候如果我們按下Back鍵,應該會回到Conversation 2 details介面,再按一次Back鍵應該回到Conversation1 details介面,再按一次Back鍵才會回到Conversation List。而ActionBar導航則不應該表現出這種行為,無論我們當前在哪一個Conversation details介面,點選一下導航按鈕都應該回到Conversation List介面才對。

這就是ActionBar導航和Back鍵在設計上的區別,那麼該怎樣才能實現這樣的功能呢?其實並不複雜,實現標準的ActionBar導航功能只需三步走。

第一步我們已經實現了,就是呼叫setDisplayHomeAsUpEnabled()方法,並傳入true。

第二步需要在AndroidManifest.xml中配置父Activity,如下所示:

 <activity android:name="com.yanis.actionbar.TabActivity">
             <meta-data
                android:name="android.support.PARENT_ACTIVITY"
                android:value="com.yanis.actionbar.MainActivity" />
        </activity>

可以看到,這裡通過meta-data標籤指定了MainActivity的父Activity是LaunchActivity,在Android 4.1版本之後,也可以直接使用android:parentActivityName這個屬性來進行指定,如下所示:

<activity 
    android:name="com.yanis.actionbar.TabActivity"
    android:parentActivityName="com.yanis.actionbar.MainActivity" > 
</activity> 

第三步則需要對android.R.id.home這個事件進行一些特殊處理,如下所示:

複製程式碼

複製程式碼

@Override 
public boolean onOptionsItemSelected(MenuItem item) {  
    switch (item.getItemId()) {  
    case android.R.id.home:  
        Intent upIntent = NavUtils.getParentActivityIntent(this);  
        if (NavUtils.shouldUpRecreateTask(this, upIntent)) {  
            TaskStackBuilder.create(this)  
                    .addNextIntentWithParentStack(upIntent)  
                    .startActivities();  
        } else {  
            upIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);  
            NavUtils.navigateUpTo(this, upIntent);  
        }  
        return true;  
        ......  
    }  
}  

複製程式碼

複製程式碼

其中,呼叫NavUtils.getParentActivityIntent()方法可以獲取到跳轉至父Activity的Intent,然後如果父Activity和當前Activity是在同一個Task中的,則直接呼叫navigateUpTo()方法進行跳轉,如果不是在同一個Task中的,則需要藉助TaskStackBuilder來建立一個新的Task。

這樣,就按照標準的規範成功實現ActionBar導航的功能了。

效果圖如下:

6.新增Action View

ActionView是一種可以在ActionBar中替換Action按鈕的控制元件,它可以允許使用者在不切換介面的情況下通過ActionBar完成一些較為豐富的操作。比如說,你需要完成一個搜尋功能,就可以將SeachView這個控制元件新增到ActionBar中。

為了宣告一個ActionView,我們可以在menu資源中通過actionViewClass屬性來指定一個控制元件,還記得前面寫過的嗎:

複製程式碼

複製程式碼

<item
        android:id="@+id/action_search"
        android:actionViewClass="android.widget.SearchView"
        android:showAsAction="always"
        android:title="搜尋"/>

複製程式碼

複製程式碼

如果你還希望在程式碼中對SearchView的屬性進行配置(比如新增監聽事件等),完全沒有問題,只需要在onCreateOptionsMenu()方法中獲取該ActionView的例項就可以了,程式碼如下所示:

複製程式碼

複製程式碼

@Override 
public boolean onCreateOptionsMenu(Menu menu) {  
    MenuInflater inflater = getMenuInflater();  
    inflater.inflate(R.menu.main, menu);  
    MenuItem searchItem = menu.findItem(R.id.action_search);  
    SearchView searchView = (SearchView) searchItem.getActionView();  
    // 配置SearchView的屬性  
    ......  
    return super.onCreateOptionsMenu(menu);  
}  

複製程式碼

複製程式碼

除此之外,有些程式可能還希望在ActionView展開和合並的時候顯示不同的介面,其實我們只需要去註冊一個ActionView的監聽器就能實現這樣的功能了,程式碼如下所示:

複製程式碼

複製程式碼

@Override 
public boolean onCreateOptionsMenu(Menu menu) {  
    MenuInflater inflater = getMenuInflater();  
    inflater.inflate(R.menu.main, menu);  
    MenuItem searchItem = menu.findItem(R.id.action_search);  
    searchItem.setOnActionExpandListener(new OnActionExpandListener() {  
        @Override 
        public boolean onMenuItemActionExpand(MenuItem item) {  
            Log.d("TAG", "on expand");  
            return true;  
        }  
          
        @Override 
        public boolean onMenuItemActionCollapse(MenuItem item) {  
            Log.d("TAG", "on collapse");  
            return true;  
        }  
    });  
    return super.onCreateOptionsMenu(menu);  
}  

複製程式碼

複製程式碼

可以看到,呼叫MenuItem的setOnActionExpandListener()方法就可以註冊一個監聽器了,當SearchView展開的時候就會回撥onMenuItemActionExpand()方法,當SearchView合併的時候就會呼叫onMenuItemActionCollapse()方法,我們在這兩個方法中進行相應的UI操作就可以了。

7.Overflow按鈕不顯示的情況

雖然現在我們已經掌握了不少ActionBar的用法,但是當你真正去使用它的時候還是可能會遇到各種各樣的問題,比如很多人都會碰到overflow按鈕不顯示的情況。明明是同樣的一份程式碼,overflow按鈕在有些手機上會顯示,而在有些手機上偏偏就不顯示,如下圖:

可以看到,ActionBar最右邊的overflow按鈕不見,按一下Menu鍵,隱藏在overflow中的Action按鈕就會從底部出來。

有人總結了一下,overflow按鈕的顯示情況和手機的硬體情況是有關係的,如果手機沒有物理Menu鍵的話,overflow按鈕就可以顯示,如果有物理Menu鍵的話,overflow按鈕就不會顯示出來。比如我們啟動一個有Menu鍵的模擬器,然後將程式碼執行到該模擬器上

實際上,在ViewConfiguration這個類中有一個叫做sHasPermanentMenuKey的靜態變數,系統就是根據這個變數的值來判斷手機有沒有物理Menu鍵的。當然這是一個內部變數,我們無法直接訪問它,但是可以通過反射的方式修改它的值,讓它永遠為false就可以了,程式碼如下所示:

複製程式碼

複製程式碼

@Override 
protected void onCreate(Bundle savedInstanceState) {  
    ......  
    setOverflowShowingAlways();  
}  
 
private void setOverflowShowingAlways() {  
    try {  
        ViewConfiguration config = ViewConfiguration.get(this);  
        Field menuKeyField = ViewConfiguration.class.getDeclaredField("sHasPermanentMenuKey");  
        menuKeyField.setAccessible(true);  
        menuKeyField.setBoolean(config, false);  
    } catch (Exception e) {  
        e.printStackTrace();  
    }  
}  

複製程式碼

複製程式碼

這裡我們在onCreate()方法的最後呼叫了setOverflowShowingAlways()方法,而這個方法的內部就是使用反射的方式將sHasPermanentMenuKey的值設定成false,現在重新執行一下程式碼,結果如下圖所示:

可以看到,即使是在有Menu鍵的手機上,也能讓overflow按鈕顯示出來了,這樣就可以大大增加我們軟體介面和操作的統一性。

8.讓Overflow中的選項顯示圖示

如果你點選一下overflow按鈕去檢視隱藏的Action按鈕,你會發現這部分Action按鈕都是隻顯示文字不顯示圖示的,如下圖所示:

這是官方的預設效果,Google認為隱藏在overflow中的Action按鈕都應該只顯示文字。當然,如果你認為這樣不夠美觀,希望在overflow中的Action按鈕也可以顯示圖示,我們仍然可以想辦法來改變這一預設行為。

其實,overflow中的Action按鈕應不應該顯示圖示,是由MenuBuilder這個類的setOptionalIconsVisible變數來決定的,如果我們在overflow被展開的時候將這個變數賦值為true,那麼裡面的每一個Action按鈕對應的圖示就都會顯示出來了。賦值的方法當然仍然是用反射了,程式碼如下所示:

複製程式碼

複製程式碼

@Override 
public boolean onMenuOpened(int featureId, Menu menu) {  
    if (featureId == Window.FEATURE_ACTION_BAR && menu != null) {  
        if (menu.getClass().getSimpleName().equals("MenuBuilder")) {  
            try {  
                Method m = menu.getClass().getDeclaredMethod("setOptionalIconsVisible", Boolean.TYPE);  
                m.setAccessible(true);  
                m.invoke(menu, true);  
            } catch (Exception e) {  
            }  
        }  
    }  
    return super.onMenuOpened(featureId, menu);  
}  

複製程式碼

複製程式碼

可以看到,這裡我們重寫了一個onMenuOpened()方法,當overflow被展開的時候就會回撥這個方法,接著在這個方法的內部通過返回反射的方法將MenuBuilder的setOptionalIconsVisible變數設定為true就可以了。

現在重新執行一下程式碼,結果如下圖所示:  

9.新增Action Provider

和Action View有點類似,Action Provider也可以將一個Action按鈕替換成一個自定義的佈局。但不同的是,Action Provider能夠完全控制事件的所有行為,並且還可以在點選的時候顯示子選單。

為了新增一個Action Provider,我們需要在<item>標籤中指定一個actionViewClass屬性,在裡面填入Action Provider的完整類名。我們可以通過繼承ActionProvider類的方式來建立一個自己的Action Provider,同時,Android也提供好了幾個內建的Action Provider,比如說ShareActionProvider。

由於每個Action Provider都可以自由地控制事件響應,所以它們不需要在onOptionsItemSelected()方法中再去監聽點選事件,而是應該在onPerformDefaultAction()方法中去執行相應的邏輯。

那麼我們就先來看一下ShareActionProvider的簡單用法吧,編輯menu資原始檔,在裡面加入ShareActionProvider的宣告,如下所示:

複製程式碼

複製程式碼

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" >

    <item 
        android:id="@+id/action_share" 
        android:actionProviderClass="android.widget.ShareActionProvider" 
        android:showAsAction="ifRoom" 
        android:title="分享" /> 
...
</menu>

複製程式碼

複製程式碼

注意,ShareActionProvider會自己處理它的顯示和事件,但我們仍然要記得給它新增一個title,以防止它會在overflow當中出現。

接著剩下的事情就是通過Intent來定義出你想分享哪些東西了,我們只需要在onCreateOptionsMenu()中呼叫MenuItem的getActionProvider()方法來得到該ShareActionProvider物件,再通過setShareIntent()方法去選擇構建出什麼樣的一個Intent就可以了。程式碼如下所示:

複製程式碼

複製程式碼

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.menu_main, menu);
        MenuItem shareItem = menu.findItem(R.id.action_share);
        ShareActionProvider provider = (ShareActionProvider) shareItem
                .getActionProvider();
        provider.setShareIntent(getDefaultIntent());
        return super.onCreateOptionsMenu(menu);
    }

    private Intent getDefaultIntent() {
        Intent intent = new Intent(Intent.ACTION_SEND);
        intent.setType("image/*");
        return intent;
    }

複製程式碼

複製程式碼

可以看到,這裡我們通過getDefaultIntent()方法來構建了一個Intent,該Intent表示會將所有可以共享圖片的程度都列出來。重新執行一下程式,效果如下圖所示:

細心的你一定觀察到了,這個ShareActionProvider點選之後是可以展開的,有點類似於overflow的效果,這就是Action Provider的子選單。

10.新增導航Tabs

Tabs的應用可以算是非常廣泛了,它可以使得使用者非常輕鬆地在你的應用程式中切換不同的檢視。而Android官方更加推薦使用ActionBar中提供的Tabs功能,因為它更加的智慧,可以自動適配各種螢幕的大小。比如說,在平板上螢幕的空間非常充足,Tabs會和Action按鈕在同一行顯示,如下圖所示:

而如果是在手機上,螢幕的空間不夠大的話,Tabs和Action按鈕則會分為兩行顯示,如下圖所示:

下面我們就來看一下如何使用ActionBar提供的Tab功能,大致可以分為以下幾步:

1. 實現ActionBar.TabListener介面,這個介面提供了Tab事件的各種回撥,比如當用戶點選了一個Tab時,你就可以進行切換Tab的操作。

2.為每一個你想新增的Tab建立一個ActionBar.Tab的例項,並且呼叫setTabListener()方法來設定ActionBar.TabListener。除此之外,還需要呼叫setText()方法來給當前Tab設定標題。

3.最後呼叫ActionBar的addTab()方法將建立好的Tab新增到ActionBar中。

看起來並不複雜,總共就只有三步,那麼我們現在就來嘗試一下吧。首先第一步需要建立一個實現ActionBar.TabListener介面的類,程式碼如下所示:

複製程式碼

複製程式碼

package com.yanis.yc_ui_actionbar_tab;

import android.app.ActionBar;
import android.app.ActionBar.Tab;  
import android.app.Activity;  
import android.app.Fragment;  
import android.app.FragmentTransaction; 

public class TabListener<T extends Fragment> implements ActionBar.TabListener {
    private Fragment mFragment;
    private final Activity mActivity;
    private final String mTag;
    private final Class<T> mClass;

    /** Constructor used each time a new tab is created.
      * @param activity  The host Activity, used to instantiate the fragment
      * @param tag  The identifier tag for the fragment
      * @param clz  The fragment's Class, used to instantiate the fragment
      */
    public TabListener(Activity activity, String tag, Class<T> clz) {
        mActivity = activity;
        mTag = tag;
        mClass = clz;
    }

    /* The following are each of the ActionBar.TabListener callbacks */

    public void onTabSelected(Tab tab, FragmentTransaction ft) {
        // Check if the fragment is already initialized
        if (mFragment == null) {
            // If not, instantiate and add it to the activity
            mFragment = Fragment.instantiate(mActivity, mClass.getName());
            ft.add(android.R.id.content, mFragment, mTag);
        } else {
            // If it exists, simply attach it in order to show it
            ft.attach(mFragment);
        }
    }

    public void onTabUnselected(Tab tab, FragmentTransaction ft) {
        if (mFragment != null) {
            // Detach the fragment, because another one is being attached
            ft.detach(mFragment);
        }
    }

    public void onTabReselected(Tab tab, FragmentTransaction ft) {
        // User selected the already selected tab. Usually do nothing.
    }
}

複製程式碼

複製程式碼

這段程式碼並不長,我們簡單分析一下。當Tab被選中的時候會呼叫onTabSelected()方法,在這裡我們先判斷mFragment是否為空,如果為空的話就建立Fragment的例項並呼叫FragmentTransaction的add()方法,如果不會空的話就呼叫FragmentTransaction的attach()方法。

而當Tab沒有被選中的時候,則呼叫FragmentTransaction的detach()方法,將UI資源釋放掉。

當Tab被重新選中的時候會呼叫onTabReselected()方法,如果沒有特殊需求的話,通常是不需要進行處理的。

接下來第二步要給每一個Tab建立一個ActionBar.Tab的例項,在此之前要先準備好每個Tab頁對應的Fragment。比如說這裡我們想建立三個Tab頁,準備好這三個Tab頁對應的Fragment和對應的佈局檔案。

複製程式碼

複製程式碼

package com.yanis.yc_ui_actionbar_tab;

import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

public class Fragment1 extends android.app.Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {

        return inflater.inflate(R.layout.fragment1, container, false);
    }
}

複製程式碼

複製程式碼

沒有什麼實質性的程式碼,只是顯示了指定的佈局檔案。

Fragment都準備好了之後,接下來就可以開始建立Tab例項了,建立好了之後則再呼叫addTab()方法新增到ActionBar當中,這兩步通常都是在Activity的onCreate()方法中執行的,程式碼如下:

複製程式碼

複製程式碼

package com.yanis.actionbar;

import android.app.ActionBar;
import android.app.ActionBar.Tab;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.NavUtils;
import android.support.v4.app.TaskStackBuilder;
import android.view.MenuItem;

public class TabActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_tab);

        initView();
    }

    private void initView() {
        // 提示getActionBar方法一定在setContentView後面
        ActionBar actionBar = getActionBar();
        actionBar.setDisplayHomeAsUpEnabled(true);
        actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);

        // 新增Tab選項
        Tab tab = actionBar
                .newTab()
                .setText("澳門風雲2")
                .setTabListener(
                        new TabListener<Fragment1>(this, "film1",
                                Fragment1.class));
        actionBar.addTab(tab);

        tab = actionBar
                .newTab()
                .setText("五十度灰")
                .setTabListener(
                        new TabListener<Fragment2>(this, "film2",
                                Fragment2.class));
        actionBar.addTab(tab);
        tab = actionBar
                .newTab()
                .setText("爸爸去哪兒2")
                .setTabListener(
                        new TabListener<Fragment3>(this, "film3",
                                Fragment3.class));
        actionBar.addTab(tab);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
        case android.R.id.home:
            Intent upIntent = NavUtils.getParentActivityIntent(this);
            if (NavUtils.shouldUpRecreateTask(this, upIntent)) {
                TaskStackBuilder.create(this)
                        .addNextIntentWithParentStack(upIntent)
                        .startActivities();
            } else {
                upIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
                NavUtils.navigateUpTo(this, upIntent);
            }
            return true;
        default:
            return super.onOptionsItemSelected(item);
        }
    }

}

複製程式碼

複製程式碼

可以看到,這裡是使用連綴的寫法來建立Tab的。首先呼叫ActionBar的newTab()方法來建立一個Tab例項,接著呼叫了setText()方法來設定標題,然後再呼叫setTabListener()方法來設定事件監聽器,最後再呼叫ActionBar的addTab()方法將Tab新增到ActionBar中。

好了,這樣的話程式碼就編寫完了,效果如下圖所示:

11.新增下拉列表導航

1.1 簡單介紹

作為Activity內部的另一種導航(或過濾)模式,操作欄提供了內建的下拉列表。下拉列表能夠提供Activity中內容

的不同排序模式。

啟用下拉式導航的基本過程如下:

<1> 建立一個給下拉提供可選專案的列表,以及描畫列表專案時所使用的佈局;

<2> 實現ActionBar.OnNavigationListener回撥,在這個回撥中定義當用戶選擇列表中一個專案時所發生的行為;

<3> 用setNavigationMode()方法該操作欄啟用導航模式;

<4> 用setListNavigationCallbacks()方法給下拉列表設定回撥方法。

1.2 效果圖如下:

1.3 程式碼實現

①準備列表資料( strings.xml)

    <string-array name="action_list">
        <item>Fragment1</item>
        <item>Fragment2</item>
        <item>Fragment3</item>
    </string-array>

②然後就是主介面程式碼了

複製程式碼

複製程式碼

package com.yanis.actionbar;

import android.app.ActionBar;
import android.app.ActionBar.OnNavigationListener;
import android.app.Activity;
import android.app.Fragment;
import android.app.TaskStackBuilder;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.NavUtils;
import android.view.MenuItem;
import android.widget.ArrayAdapter;
import android.widget.SpinnerAdapter;

public class ListActivity extends Activity {
    private OnNavigationListener mOnNavigationListener;
    private String[] arry_list;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_list);

        initView();
    }

    /**
     * 初始化元件
     */
    private void initView() {
        ActionBar actionBar = getActionBar();
        actionBar.setDisplayHomeAsUpEnabled(true);
        // //導航模式必須設為NAVIGATION_MODE_LIST
        actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);

        // 定義一個下拉列表資料介面卡
        SpinnerAdapter mSpinnerAdapter = ArrayAdapter.createFromResource(this,
                R.array.action_list,
                android.R.layout.simple_spinner_dropdown_item);
        arry_list = getResources().getStringArray(R.array.action_list);
        mOnNavigationListener = new OnNavigationListener() {

            @Override
            public boolean onNavigationItemSelected(int position, long itemId) {
                Fragment newFragment = null;
                switch (position) {
                case 0:
                    newFragment = new Fragment1();
                    break;
                case 1:
                    newFragment = new Fragment2();
                    break;
                case 2:
                    newFragment = new Fragment3();
                    break;
                default:
                    break;
                }
                getFragmentManager()
                        .beginTransaction()
                        .replace(R.id.container, newFragment,
                                arry_list[position]).commit();
                return true;
            }
        };
        actionBar.setListNavigationCallbacks(mSpinnerAdapter,
                mOnNavigationListener);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
        case android.R.id.home:
            Intent upIntent = NavUtils.getParentActivityIntent(this);
            if (NavUtils.shouldUpRecreateTask(this, upIntent)) {
                TaskStackBuilder.create(this)
                        .addNextIntentWithParentStack(upIntent)
                        .startActivities();
            } else {
                upIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
                NavUtils.navigateUpTo(this, upIntent);
            }
            return true;
        default:
            return super.onOptionsItemSelected(item);
        }
    }
}

複製程式碼

複製程式碼

12.自定義ActionBar樣式

雖說ActionBar給使用者提供了一種全域性統一的介面風格和操作方式,但這並不意味著所有應用程式的ActionBar都必須要長得一模一樣。如果你需要修改ActionBar的樣式來更加好地適配你的應用,可以非常簡單地通過Android樣式和主題來實現。

其實Android內建的幾個Activity主題中就已經包含了"dark"或"light"這樣的ActionBar樣式了,同時你也可以繼承這些主題,然後進行更深一步的定製。

1. 使用主題

Android中有兩個最基本的Activity主題可以用於指定ActionBar的顏色,分別是:

深色系主題樣式的效果如下圖所示:

淺色系主題樣式的效果如下圖所示:

你可以將這些主題應用到你的整個應用程式,也可以只應用於某個Activity。通過在AndroidManifest.xml檔案中給<application>或<activity>標籤指定android:theme屬性就可以實現了。比如:

<application android:theme="@android:style/Theme.Holo.Light" ... />

如果你只想讓ActionBar使用深色系的主題,而Activity的內容部分仍然使用淺色系的主題,可以通過宣告Theme.Holo.Light.DarkActionBar這個主題來實現,效果如下圖所示:

2. 自定義背景

如果想要修改ActionBar的背景,我們可以通過建立一個自定義主題並重寫actionBarStyle屬性來實現。這個屬性可以指向另外一個樣式,然後我們在這個樣式中重寫background這個屬性就可以指定一個drawable資源或顏色,從而實現自定義背景的功能。

編輯styles.xml檔案,在裡面加入一個自定義的主題,如下所示:

複製程式碼

複製程式碼

<resources>  
 
    <style name="CustomActionBarTheme" parent="@android:style/Theme.Holo.Light">  
        <item name="android:actionBarStyle">@style/MyActionBar</item>  
    </style>  
 
    <style name="MyActionBar" parent="@android:style/Widget.Holo.Light.ActionBar">  
        <item name="android:background">#f4842d</item>  
    </style>  
 
</resources>  

複製程式碼

複製程式碼

可以看到,這裡我們定義了一個CustomActionBarTheme主題,並讓它繼承自Theme.Holo.Light。然後在其內部重寫了actionBarStyle這個屬性,然後將這個屬性指向了MyActionBar這個樣式,我們在這個樣式中又重寫了background屬性,並給它指定了一個背景色。

現在重新執行一下程式,效果如下圖所示:

這樣我們就成功修改ActionBar的背景色了。不過現在看上去還有點怪怪的,因為只是ActionBar的背景色改變了,Tabs的背景色還是原來的樣子,這樣就感覺不太協調。那麼下面我們馬上就來修改一下Tabs的背景色,編輯styles.xml檔案,如下所示:

複製程式碼

複製程式碼

<resources> 
 
    <style name="CustomActionBarTheme" parent="@android:style/Theme.Holo.Light"> 
        <item name="android:actionBarStyle">@style/MyActionBar</item> 
    </style> 
 
    <style name="MyActionBar" parent="@android:style/Widget.Holo.Light.ActionBar"> 
        <item name="android:background">#f4842d</item> 
        <item name="android:backgroundStacked">#d27026</item> 
    </style> 
 
</resources> 

複製程式碼

複製程式碼

可以看到,這裡又重寫了backgroundStacked屬性,這個屬性就是用於指定Tabs背景色的。那麼再次重新執行程式,效果如下圖所示:

3. 自定義文字顏色

現在整個ActionBar的顏色是屬於偏暗系的,而ActionBar中文字的顏色又偏偏是黑色的,所以看起來並不舒服,那麼接下來我們就學習一下如果自定義文字顏色,將文字顏色改成白色。

修改styles.xml檔案,如下所示:

複製程式碼

複製程式碼

<resources> 
 
    ......  
 
    <style name="MyActionBar" parent="@android:style/Widget.Holo.Light.ActionBar"> 
        ......  
        <item name="android:titleTextStyle">@style/MyActionBarTitleText</item> 
    </style> 
 
    <style name="MyActionBarTitleText" parent="@android:style/TextAppearance.Holo.Widget.ActionBar.Title"> 
        <item name="android:textColor">#fff</item> 
    </style> 
 
</resources> 

複製程式碼

複製程式碼

可以看到,這裡在MyActionBar樣式裡面重寫了titleTextStyle屬性,並將它指向了另一個自定義樣式MyActionBarTitleText,接著我們在這個樣式中指定textColor的顏色是#fff,也就是白色。

現在重新執行一下程式,結果如下圖所示:

OK,ActionBar標題文字的顏色已經成功改成白色了,那Tab標題的文字又該怎麼修改呢?繼續編輯styles.xml檔案,如下所示:

複製程式碼

複製程式碼

<resources> 
 
    <style name="CustomActionBarTheme" parent="@android:style/Theme.Holo.Light"> 
        <item name="android:actionBarStyle">@style/MyActionBar</item> 
        <item name="android:actionBarTabTextStyle">@style/MyActionBarTabText</item> 
    </style> 
      
    <style name="MyActionBarTabText" 
           parent="@android:style/Widget.Holo.ActionBar.TabText"> 
        <item name="android:textColor">#fff</item> 
    </style> 
 
</resources> 

複製程式碼

複製程式碼

這裡我們在CustomActionBarTheme主題中重寫actionBarTabTextStyle屬性,並將它指向一個新建的MyActionBarTabText樣式,然後在這個樣式中重寫textColor屬性,將顏色指定為白色即可。

重新執行一下程式,結果如下圖所示:

4. 自定義Tab Indicator

為了可以明確分辨出我們當前選中的是哪一個Tab項,通常情況下都會在選中Tab的下面加上一條橫線作為標識,這被稱作Tab Indicator。那麼上圖中的Tab Indicator是藍色的,明顯和整體風格不相符,所以我們接下來就學習一下如何自定義Tab Indicator。

首先我們需要重寫actionBarTabStyle這個屬性,然後將它指向一個新建的Tab樣式,然後重寫background這個屬性即可。需要注意的是,background必須要指定一個state-list drawable檔案,這樣在各種不同狀態下才能顯示出不同的效果。

那麼在開始之前,首先我們需要準備四張圖片,分別用於表示Tab的四種狀態,如下所示:

      

這四張圖片分別表示Tab選中未按下,選中且按下,未選中未按下,未選中且按下這四種狀態,那麼接著新建res/drawable/actionbar_tab_indicator.xml檔案,程式碼如下所示:

複製程式碼

複製程式碼

<?xml version="1.0" encoding="utf-8"?> 
<selector xmlns:android="http://schemas.android.com/apk/res/android"> 
 
    <item  android:state_selected="false" 
          android:state_pressed="false" 
          android:drawable="@drawable/tab_unselected" /> 
    <item android:state_selected="true" 
          android:state_pressed="false" 
          android:drawable="@drawable/tab_selected" /> 
    <item android:state_selected="false" 
          android:state_pressed="true" 
          android:drawable="@drawable/tab_unselected_pressed" /> 
    <item android:state_selected="true" 
          android:state_pressed="true" 
          android:drawable="@drawable/tab_selected_pressed" /> 
 
</selector> 

複製程式碼

複製程式碼

四種狀態分別引用了四張圖片,這樣就把state-list drawable檔案寫好了。接著修改style.xml檔案,程式碼如下所示:

複製程式碼

複製程式碼

<resources> 
 
    <style name="CustomActionBarTheme" parent="@android:style/Theme.Holo.Light"> 
        ......  
        <item name="android:actionBarTabStyle">@style/MyActionBarTabs</item> 
    </style> 
      
    <style name="MyActionBarTabs" parent="@android:style/Widget.Holo.ActionBar.TabView"> 
        <item name="android:background">@drawable/actionbar_tab_indicator</item> 
    </style> 
 
</resources> 

複製程式碼

複製程式碼

這裡先是重寫了actionBarTabStyle這個屬性,並將它指向了另一個自定義樣式MyActionBarTabs,接著在這個樣式中重寫background屬性,然後指向我們剛才建立的actionbar_tab_indicator即可。

現在重新執行一下程式,效果如下所示:

可以看到,Tab Indicator的顏色已經變成了白色,這樣看上去就協調得多了。

除此之外,Action Bar還有許許多多的屬性可以進行自定義,這裡我們無法一一涵蓋到本篇文章中,更多的自定義屬性請參考官方文件進行學習。