Action Bar是一個用來標明用戶在app中所處的位置,呈現用戶action和導航模式的窗口功能。Action bar可以讓系統優雅地在不同屏幕配置上進行適配,爲你的用戶提供跨應用的統一使用體驗。
對於API level 11及以上
所有使用Theme.Holo主題(或其繼承者)的activity都會包含action
bar,當targetSdkVersion或
者minSdkVersion屬性設爲“11”或以上時,默認主題就是它。如果不需要某個activity顯示action
bar,可以將
它的主題設置爲Theme.Holo.NoActionBar。
圖1:一個包括【1】app圖標,【2】兩個action項,以及【3】overflow的Action bar
Action bar提供下面幾個主要功能:
提供一個用來展示app身份和用戶在app中所處位置的固定專用空間;
讓重要的action處於明顯的易操作的位置(如搜索);
支持持續的應用中導航及視圖切換(利用tab標籤和下拉菜單)。
要了解更多關於Action bar的互動模式和設計準則,參見Android設計指南:Action Bar。
ActionBar API在Android3.0(API level 11)首次引入,同時爲兼容Android2.1(API level 7)以上,這些API也存在於Support
Library中。
本指南主要介紹support library的action bar使用,不過如果你的app只支持Android3.0以上,那麼你可以直接使用framework提供的ActionBar API,多數API都是一樣的——只不過是存在於不同的包命名空間——除了一些方法名等,後面會提到。
注意:確保你從正確的包import了ActionBar類(和相關API):
如支持API level 11以下:
如只支持API level 11及以上:import android.support.v7.app.ActionBar
注意:如果想查找關於用來顯示情境相關action項的情境相關action bar,見Menu指南。import android.app.ActionBar
添加Action Bar
如上所述,本指南主要講述support library中的ActionBar,因此在添加action bar之前,你必須先在你的project中設置appcompat
v7 support library,參照Support Library Setup。
Project設置好support library之後,按下面的步驟添加action bar:
1. 繼承ActionBarActivity,創建你的activity;
2. 爲你的activity使用(或繼承)Theme.Appcompat中的主題。如下:
這樣你的activity運行在Android2.1(API level 7)及以上時就會有action bar了。<activity android:theme="@style/Theme.AppCompat.Light" ... >
對於API level 11及以上
移除Action Bar
在程序運行時,可以調用hide()隱藏action bar,如下:
對於API level 11及以上ActionBar actionBar =getSupportActionBar()
; actionBar.hide();
Action Bar隱藏時,系統會自動調整頁面佈局來填滿空餘出來的屏幕空間,用show()可以重新顯示出action
bar。
注意隱藏或者移除action bar會使activity重新對頁面進行佈局來補充action bar佔用的部分,如果你的activity經常顯示和隱藏action bar,那麼使用overlay模式會更好。Overlay模式會把action bar繪製在佈局之上,遮擋頂部的一部分,這樣,你的action bar隱藏或者重新出現時,activity的佈局就不會改變。要開啓overlay模式,須要爲你的activity創建一個自定義主題,並將其windowActionBarOverlay屬性設爲true。更多信息參見Styling
the Action Bar。
用logo替換圖標
系統會默認在action bar上使用manifest文件中application或activity元素裏icon屬性指定的圖標,不過如果你同時還指定了logo屬性,action
bar則會使用logo圖片。
通常logo應該比圖標寬一些,但是不應包含不必要的文字。一般來說,應該在需要使用傳統的用戶認可熟悉的商標等標誌時使用logo,YouTube app的logo就是個很好的例子——logo是用戶期望中的標誌,而app圖標則是爲遵循方形圖標而進行過修改的。
添加Action項
Action bar爲用戶提供每個情境下最重要的action的操作,直接以圖標和/或文字形式出現在action bar上的項稱爲action按鈕,action bar無法容納或不那麼重要的action則放在overflow中。用戶可通過點按右側的overflow按鈕(或者設備上的Menu按鍵)來顯現overflow菜單。
圖2:帶有3個action按鈕和overflow按鈕的action bar
Activity啓動時,系統通過調用activity的onCreateOptionsMenu()來生成action項,用這個方法來加載定義action項的菜單資源。下面是個菜單資源的例子:
res/menu/main_activity_actions.xml
然後在activity的onCreateOptionsMenu()方法中,把菜單資源加載到參數傳入的Menu對象,這樣每一項就都被添加到了action bar:<menu xmlns:android="http://schemas.android.com/apk/res/android" > <item android:id="@+id/action_search" android:icon="@drawable/ic_action_search" android:title="@string/action_search"/> <item android:id="@+id/action_compose" android:icon="@drawable/ic_action_compose" android:title="@string/action_compose" /> </menu>
要讓action項直接顯示爲action按鈕,須要在<item>標籤中加入showAsAction="ifRoom",如下:@Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu items for use in the action bar MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.main_activity_actions, menu); return super.onCreateOptionsMenu(menu); }
如果action bar上的空間不夠,該項就會被放進overflow。<menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:yourapp="http://schemas.android.com/apk/res-auto" > <item android:id="@+id/action_search" android:icon="@drawable/ic_action_search" android:title="@string/action_search" yourapp:showAsAction="ifRoom" /> ... </menu>
使用support library中的XML屬性
要注意到上例中的showAsAction屬性用到了<menu>標籤中的一個自定義名字空間,使用support library中定義的任何
XML屬性都必須這樣做,因爲這些屬性在老設備的Android framework中並不存在,所以必須用自己的名字空間來作爲
support library的所有屬性的前綴。
如果你的菜單項既有標題又有圖標——通過title和icon屬性指定——那麼默認會只顯示圖標,如果要顯示文字標題,就須要在showAsAction屬性裏添加"withText",如下:
注意:"withText"只是提示action bar應該顯示文字標題,action bar會在可能的情況下顯示文字,但如果該項有圖標而 且action bar沒有空間,那麼文字還是有可能不顯示。<item yourapp:showAsAction="ifRoom|withText" ... />
但對於每個菜單項都應該定義title,哪怕你不顯示它,原因如下:
如果action bar沒有空間顯示action項,那麼這些項就會出現在overflow中,而在overflow中只會顯示文字標題;
視力障礙用戶使用的屏幕閱讀功能會讀出菜單項的文字標題;
如果action項只顯示爲圖標,用戶可以長按圖標來顯示標題作爲提示。
可以用"always"來聲明action項永遠顯示爲action按鈕,然而,你不應該用這種方式讓一個action項始終顯示爲action按鈕,否則在較窄的屏幕上可能造成佈局問題。最好還是用"ifRoom"請求讓它顯示爲action按鈕,同時也允許系統在空間不足時把它放入overflow。需要用到這個值的情況是當這個項包含一個無法摺疊,並且要永遠可見,提供關鍵功能的action
view。
處理action項的點擊事件
當用戶點按一個action時,系統調用activity的onOptionsItemSelected()方法,用傳入這個方法的MenuItem對象調用getItemId()可以辨別是哪個action,方法返回<item>標籤的id屬性設置的唯一ID,然後就可以根據ID執行相應的action。如下:
注:如果你通過Fragment類的onCreateOptionsMenu()回調來從fragment加載菜單項,用戶選擇action項時系 統會調用該fragment的onOptionsItemSelected()。不過,activity會先得到一個處理這個事件的機會,系統會在 調用fragment的同方法之前先調用activity的onOptionsItemSelected()。爲確保activity的所有fragment都有處 理事件的機會,在自己處理完事件後不要直接返回false,而應該總是默認把調用傳遞給父類。@Override public boolean onOptionsItemSelected(MenuItem item) { // Handle presses on the action bar items switch (item.getItemId()) { case R.id.action_search: openSearch(); return true; case R.id.action_compose: composeMessage(); return true; default: return super.onOptionsItemSelected(item); } }
使用分離式action bar
分離式action bar可以在屏幕比較窄的情況下(比如豎屏的設備)提供一個位於屏幕底部的action bar來放置action項。
這種把action item分離出來的方式確保了在窄屏上能有足夠的空間來顯示action項,同時還在頂部留出了用於頁面導航和標題顯示的空間。
圖3. 模擬圖,帶有標籤的action bar(左);分離式action bar(中);不顯示圖標和標題(右)
在support library中啓用分離式action bar,需要兩個步驟:
1. 在manifest文件中爲每個activity或者爲application添加uiOptions="splitActionBarWhenNarrow",這個屬性只有API
level 14及以上才能解析(老版本上會直接忽略)。
例子:
分離式action bar還允許導航標籤收縮到主action bar,前提是不顯示圖標和標題(如圖3中右側所示),要達到這個效果,必須用setDisplayShowHomeEnabled(false)和setDisplayShowTitleEnabled(false)來禁用圖標和標題。<manifest ...> <activity uiOptions="splitActionBarWhenNarrow" ... > <meta-data android:name="android.support.UI_OPTIONS" android:value="splitActionBarWhenNarrow" /> </activity> </manifest>
用App圖標返回上層界面
把app圖標用作Up按鈕使用戶能夠在層級關係的界面中進行導航,比如,A界面顯示一個list,點擊其中一項進入界面B,這時界面B就應該包含一個能夠返回A界面的Up按鈕。
注:Up導航和系統的back導航有區別,back按鈕用來按用戶最近訪問的事件順序進行逆向回退,基本上基於界面間
的臨時關係,而非app的層級結構(up導航的基礎)。
圖4. Gmail中的Up按鈕
要把app圖標用作Up按鈕,須調用setDisplayHomeAsUpEnabled(),例子:
這樣action bar的圖標旁就多了一個Up括號(如圖4),不過,它默認是不會做任何事情的,要指定用戶點擊時打開的activity,你有兩個選擇:@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_details); ActionBar actionBar = getSupportActionBar(); actionBar.setDisplayHomeAsUpEnabled(true); ... }
在manifest文件中指定父activity
如果父activity永遠是同一個時這是最好的選擇。通過在manifest中聲明哪個是父activity,用戶點擊Up按鈕時,
action bar會自動執行正確的action。
這樣指定後,再用setDisplayHomeAsUpEnabled()來啓用Up按鈕,action bar就已經能實現返回上級的功能了。<application ... > ... <!-- The main/home activity (has no parent activity) --> <activity android:name="com.example.myfirstapp.MainActivity" ...> ... </activity> <!-- A child of the main activity --> <activity android:name="com.example.myfirstapp.DisplayMessageActivity" android:label="@string/title_activity_display_message" android:parentActivityName="com.example.myfirstapp.MainActivity" > <!-- Parent activity meta-data to support API level 7+ --> <meta-data android:name="android.support.PARENT_ACTIVITY" android:value="com.example.myfirstapp.MainActivity" /> </activity> </application>
或者,override你的activity中的getSupportParentActivityIntent()和onCreateSupportNavigateUpTaskStack()
如果由於用戶到達目前界面方式不同而造成父activity不一定相同,那麼這種方法會比較合適。也就是說,如果用戶有多種途徑可以到達當前界面,那麼Up按鈕應該沿着該途徑返回上層。
當用戶在你的app中導航(在你app自己的task中)時按了Up按鈕,系統會調用getSupportParentActivityIntent()。如果Up應該打開的activity由於用戶到達當前界面的途徑不同而不同,那麼就應該override此方法並返回啓動相應activity的Intent。
當你的activity運行在不屬於你的app的task中時,如果用戶點按Up按鈕,系統會爲你的activity調用onCreateSupportNavigateUpTaskStack(),這樣,你必須用傳入此方法的TaskStackBuilder對象來創建相應的back
stack。
即使你通過override getSupportParentActivityIntent()指定了上層activity,你還是可以在manifest文件中聲明“默認”父activity,這樣可以不用重新實現onCreateSupportNavigateUpTaskStack(),它的默認實現即會基於這個聲明的父activity來構建back
stack。
注:如果你用一系列fragment來構建你app的層級結構,而非用多個activity,那麼上面兩個選擇就都不行了,這
時,要在fragment間進行up操作,就需要override onSupportNavigateUp()來進行相應的fragment切換——通
常是調用popBackStack()把fragment從back
stack中彈出。
更多關於實現Up導航,見Providing Up Navigation。
添加Action View
Action View是一個出現在action bar上用來替代action按鈕的部件,它能在不改變activity或者fragment,並且不替換action bar的情況下提供快捷豐富的action。比如,你有一個搜索action,那麼你可以在action bar上添加一個嵌有SearchView的action
view,如圖5。
圖5. 帶有可收起的SearchView的action
bar
要聲明一個action view,使用actionLayout或者actionViewClass屬性分別來指定一個佈局資源或要使用的widget類。下面是添加SearchView widget的例子:
注意showAsAction屬性中還多了"collapseActionView"這樣一個值,這是可選的,聲明瞭該action view可以收起爲一個按鈕。(這個行爲將在下一節處理可收起的action view中詳細解釋)<?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:yourapp="http://schemas.android.com/apk/res-auto" > <item android:id="@+id/action_search" android:title="@string/action_search" android:icon="@drawable/ic_action_search" yourapp:showAsAction="ifRoom|collapseActionView" yourapp:actionViewClass="android.support.v7.widget.SearchView" /> </menu>
如需配置action view(如增加事件監聽),可在onCreateOptionsMenu()回調中進行。你可以調用static方法MenuItemCompat.getActionView()來獲得action
view對象,並傳入相應的MenuItem。下面是獲取這個搜索widget的例子:
對於API level 11及以上@Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.main_activity_actions, menu); MenuItem searchItem = menu.findItem(R.id.action_search); SearchView searchView = (SearchView) MenuItemCompat.getActionView(searchItem); // Configure the search info and add any event listeners ... return super.onCreateOptionsMenu(menu); }
要了解更多搜索widget的信息,見Creating a Search Interface。menu.findItem(R.id.action_search).getActionView()
處理可收起的action view
爲節省action bar空間,可以把你的action view收起成一個action按鈕,收起後,系統可能會把它放進overflow,但是用戶選中它時,action view還是會照樣出現在action bar上。你可以通過在showAsAction屬性中添加"collapseActionView"來使你的action
view能夠摺疊起來,如前面的XML所示。
因爲系統會在用戶選擇action時展開action view,所以你無需響應onOptionsItemSelected()回調。系統仍然會調用onOptionsItemSelected(),但如果你返回true(表明你已經處理了事件),action
view就不會展開了。
系統也會在用戶按Up或Back按鈕時收起你的action view。
如需根據action view的可見性來更新你的activity,可以通過定義一個OnActionExpandListener並傳給setOnActionExpandListener()來接收展開收起時的回調。例子:
@Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.options, menu); MenuItem menuItem = menu.findItem(R.id.actionItem); ... // When using the support library, the setOnActionExpandListener() method is // static and accepts the MenuItem object as an argument MenuItemCompat.setOnActionExpandListener(menuItem, new OnActionExpandListener() { @Override public boolean onMenuItemActionCollapse(MenuItem item) { // Do something when collapsed return true; // Return true to collapse action view } @Override public boolean onMenuItemActionExpand(MenuItem item) { // Do something when expanded return true; // Return true to expand action view } }); }
添加Action Provider
類似於action view,action provider也是用自定義的佈局來替換action按鈕,和action
view不同的是,action provider接管了該action的所有行爲,而且點擊時可以顯示一個子菜單。
你可以繼承ActionProvider類來創建自己的action
provider,不過Android提供了一些預先建好的action provider,比如ShareActionProvider,它簡化了“分享”action,它會在action
bar上展示一個所有可供直接分享的app列表(如圖6)。
圖6. 一個帶有ShareActionProvider的action
bar,顯示了分享目標
因爲每個ActionProvider都定義了自己的action行爲,所以無需在onOptionsItemSelected()方法中監聽action。當然,如果需要,你還是可以在該方法中監聽點擊事件,以便你可以同時執行別的action,但要一定要返回false,確保action
provider還能接收到onPerformDefaultAction()回調來執行既定的action。
不過,如果action provider提供自己的action子菜單,用戶打開子菜單或選中子菜單項時你的activity是不會收到onOptionsItemSelected()調用的。
使用ShareActionProvider
這樣這個action provider就接管了這個action項,包括它的行爲和外觀,但是標題仍然是必須的,以供它在overflow中時使用。<?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:yourapp="http://schemas.android.com/apk/res-auto" > <item android:id="@+id/action_share" android:title="@string/share" yourapp:showAsAction="ifRoom" yourapp:actionProviderClass="android.support.v7.widget.ShareActionProvider" /> ... </menu>
然後唯一需要做的事就是定義用來分享的Intent,在你的onCreateOptionsMenu()中調用MenuItemCompat.getActionProvider(),傳入擁有action
provider的MenuItem,然後在返回的ShareActionProvider對象上調用setShareIntent(),傳入一個附有相應內容的ACTION_SEND intent。
你應該在onCreateOptionsMenu()時調用setShareIntent()來初始化分享action,但由於用戶情境可能會改變,你必須在可分享內容變化時再次調用setShareIntent()來更新intent。
例子:
這樣ShareActionProvider就會處理該action項的所有用戶行爲,你也無需調用onOptionsItemSelected()處理點擊事件。private ShareActionProvider mShareActionProvider; @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.main_activity_actions, menu); // Set up ShareActionProvider's default share intent MenuItem shareItem = menu.findItem(R.id.action_share); mShareActionProvider = (ShareActionProvider) MenuItemCompat.getActionProvider(shareItem); mShareActionProvider.setShareIntent(getDefaultIntent()); return super.onCreateOptionsMenu(menu); } /** Defines a default (dummy) share intent to initialize the action provider. * However, as soon as the actual content to be used in the intent * is known or changes, you must update the share intent by again calling * mShareActionProvider.setShareIntent()
*/ private Intent getDefaultIntent() { Intent intent = new Intent(Intent.ACTION_SEND); intent.setType("image/*"); return intent; }
默認設置下,ShareActionProvider會根據用戶對分享目標的選擇頻度對各分享目標進行排序,最常使用的目標會出現在下拉菜單的頂端,而且會作爲默認分享目標直接顯示在action
bar上。這個排序信息默認會存儲在一個私有文件中,文件名由DEFAULT_SHARE_HISTORY_FILE_NAME指定,如果你只爲一種action使用ShareActionProvider或它的一個子類,那你應該繼續使用默認的歷史文件,不用做任何處理。但如果要爲多個不同含義的action使用,那麼每個ShareActionProvider都應該指定自己的歷史文件,來維護自己的歷史記錄。要爲ShareActionProvider指定不同的歷史文件,要調用setShareHistoryFileName()並提供一個XML文件名(如,"custom_share_history.xml")。
創建自定義Action Provider
創建你自己的action provider允許你以獨立模塊的方式重用和管理動態action項的行爲,而不用在你的fragment或activity代碼中來處理action行爲。如前所述,Android已經爲分享action提供了一個ActionProvider的實現:ShareActionProvider。
要爲其它的action創建你自己的action provider,只需繼承ActionProvider類並實現相應的回調方法,其中最主要的幾個:
此構造方法要傳入application的Context,你應該把它保存在成員field中,以便在其它回調方法中使用。
在這裏爲該action項定義action view,用構造方法中獲得的Context來實例化一個LayoutInflater,並從XML資源中加載你的action
view佈局,然後再設置事件監聽器。例子:
onPerformDefaultAction()public View onCreateActionView(MenuItem forItem) { // Inflate the action view to be shown on the action bar. LayoutInflater layoutInflater = LayoutInflater.from(mContext); View view = layoutInflater.inflate(R.layout.action_provider, null); ImageButton button = (ImageButton) view.findViewById(R.id.button); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // Do something... } }); return view; }
當action overflow中的菜單項被選中時系統會調用此方法,action provider應該爲該菜單項執行一個默認action。
但是,如果你的action provider通過onPrepareSubMenu()提供子菜單,那麼即使action
provider放置在overflow中,子菜單也一樣會彈出。這樣一來,有子菜單時onPerformDefaultAction()就不會調用。
注:實現onOptionsItemSelected()的Activity或fragment可以通過處理選中事件(並返回true)來override
action provider的默認行爲(除非它使用了子菜單),這種情況下,系統不會調用onPerformDefaultAction()。
添加導航tab
圖7. 寬屏上的action bar tab
Action bar上的tab使用戶更方便地在app中的不同視圖間瀏覽切換,ActionBar提供的tab可以理想地適應不同屏幕尺寸。比如,當屏幕足夠寬時,tab和action按鈕在action
bar上一字排開(例如在平板上,如圖7),而當屏幕比較窄時,這些tab就會出現在一個單獨的bar(稱爲stacked action bar,如圖8)上。某些情況下,Android系統爲保證action bar的顯示效果,會把tab顯示爲一個下拉菜單。
圖8. 窄屏上的tab
要使用tab,你的佈局必須有一個ViewGroup,用來放置和tab相關的Fragment。這個ViewGroup一定要有資源ID,這樣你才能在代碼中引用它來切換tab。如果tab的內容會充滿activity,那麼你的activity就完全不需要佈局了(你甚至不用調用setContentView()),你可以把fragment都放在默認的根視圖裏,根視圖可以用android.R.id.content這個ID來引用。
在確定了fragment都出現在佈局的哪些地方後,添加tab的其他基本步驟如下:
1. 實現ActionBar.TabListener接口,該接口提供tab事件的回調,比如用戶點擊其中一個時你可以實現切換。
2. 爲你要添加的每個tab,實例化一個ActionBar.Tab,並調用setTabListener()來設置ActionBar.TabListener,還須用setText()給tab設置標題(可選的,還可以用setIcon()設置圖標)。
3. 調用addTab()把tab添加到action
bar。
注意,ActionBar.TabListener的回調方法不指定哪個fragment是和tab相關聯的,而只是哪個ActionBar.Tab被選中了。你必須自己定義ActionBar.Tab和其代表的Fragment之間的關係,根據你的設計不同,有好幾種方法可以定義這個關係。
例如,下面時一種可能的ActionBar.TabListener實現,每個tab都要其自己的listener實例:
警告:一定不能在這三個回調中爲fragment轉換調用commit()——系統會自動爲你調用,如果你自己再調可能會拋 出異常,另外,也不能將這些fragment轉換加入back stack。public static 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 theActionBar.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被選中時,listener只是把fragment附到(attach())activity佈局中——或者如果fragment還沒有實例化,就創建一個fragment並添加(add())到佈局中(作爲android.R.id.content的子視圖)。當tab不被選擇時,則將fragment分離(detach())。
下面的代碼添加了兩個tab,它們都使用了上面定義的listener:
如果activity進入了stop狀態,你應該用saved instance state來保持當前選中的tab,這樣用戶回來時就能恢復打開相應的tab。該保存狀態時,可以用getSelectedNavigationIndex()來獲知當前tab,該方法返回選中tab的索引。@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Notice that setContentView() is not used, because we use the root // android.R.id.content as the container for each fragment // setup action bar for tabs ActionBar actionBar = getSupportActionBar(); actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS); actionBar.setDisplayShowTitleEnabled(false); Tab tab = actionBar.newTab() .setText(R.string.artist) .setTabListener(new TabListener<ArtistFragment>( this, "artist", ArtistFragment.class)); actionBar.addTab(tab); tab = actionBar.newTab() .setText(R.string.album) .setTabListener(new TabListener<AlbumFragment>( this, "album", AlbumFragment.class)); actionBar.addTab(tab); }
警告:保存每個fragment的狀態至關重要,這樣用戶用tab切換fragment並返回到之前的fragment時,能看上去
和離開時一樣。有些狀態是默認會保存的,但是對於自定義view你可能就得手動保存。要了解關於保存fragment狀
態的信息,見Fragment API指南。
注:上述ActionBar.TabListener的實現只是若干種技術中的一種,另一個廣泛使用的方法是用ViewPager來管理
fragment,這樣用戶就可以用滑動手勢來切換tab,這種情況下,你只要在onTabSelected()回調中告訴
ViewPager當前的tab位置。要了解更多,見Creating
Swipe Views with Tabs。
添加下拉導航
作爲activity導航(或條件過濾)的另一種模式,action bar提供了內建的下拉菜單(也稱爲spinner),例如,下拉菜單能爲activity內容的排序提供不同的方式選擇。
下拉菜單適用於內容的改變比較重要但不需很頻繁的情況,對於內容切換比較頻繁的情況,則應該使用導航tab。
圖9. Action bar上的下拉導航菜單
啓用下拉導航的基本步驟:
1. 創建一個提供下拉菜單選項內容的SpinnerAdapter,和用來繪製每個菜單項的佈局文件。
2. 實現ActionBar.OnNavigationListener來定義用戶選中菜單項時的行爲。
3. 在activity的onCreate()方法中,調用setNavigationMode(NAVIGATION_MODE_LIST)來啓用action
bar的下拉菜單。
4. 用setListNavigationCallbacks()指定下拉菜單的回調方法,例如:
這個方法需要SpinnerAdapter和ActionBar.OnNavigationListener作爲參數。actionBar.setListNavigationCallbacks(mSpinnerAdapter, mNavigationCallback);
步驟雖不多,但實現SpinnerAdapter和ActionBar.OnNavigationListener是其中最花功夫的。有許多爲你的下拉導航實現功能的方法,而且實現各種SpinnerAdapter的方法超出了本文的範疇(參考SpinnerAdapter類瞭解更多信息),不過爲讓你儘快上手,這裏還是提供了一個SpinnerAdapter和ActionBar.OnNavigationListener的例子。
SpinnerAdapter和OnNavigationListener的例子
SpinnerAdapter是爲類似action bar中的下拉菜單這樣的spinner widget提供數據的adapter,它是個接口,你可以直接實現它,但Android已經提供了一些現成的有用實現供你繼承使用,比如ArrayAdapter和SimpleCursorAdapter。下例是用ArrayAdapter的實現簡單地創建一個SpinnerAdapter,數據源是個字符串數組:
createFromResource()方法需要3個參數:應用Context,字符串數組的資源ID,以及列表項的佈局文件。SpinnerAdapter mSpinnerAdapter = ArrayAdapter.createFromResource(this, R.array.action_list, android.R.layout.simple_spinner_dropdown_item);
資源文件中定義的字符串數組如下:
createFromResource()返回的ArrayAdapter是可以傳入setListNavigationCallbacks()直接使用的(上面的第4步),儘管如此,在這之前你還是要先創建OnNavigationListener。<?xml version="1.0" encoding="utf-8"?> <resources> <string-array name="action_list"> <item>Mercury</item> <item>Venus</item> <item>Earth</item> </string-array> </pre>
你的ActionBar.OnNavigationListener實現是在用戶選中下拉列表項時用來處理fragment切換或其它針對activity的操作的,該listener中只有一個回調方法需要實現:onNavigationItemSelected()。
onNavigationItemSelected()會收到list項在list中的位置,還有SpinnerAdapter提供的唯一ID。
這就成了個完整的OnNavigationListener,你可以調用setListNavigationCallbacks()(步驟4),傳入ArrayAdapter和這個OnNavigationListener。mOnNavigationListener = new OnNavigationListener() { // Get the same strings provided for the drop-down's ArrayAdapter String[] strings = getResources().getStringArray(R.array.action_list); @Override public boolean onNavigationItemSelected(int position, long itemId) { // Create new fragment from our own Fragment class ListContentFragment newFragment = new ListContentFragment(); FragmentTransaction ft = openFragmentTransaction(); // Replace whatever is in the fragment container with this fragment // and give the fragment a tag name equal to the string at the position selected ft.replace(R.id.fragment_container, newFragment, strings[position]); // Apply changes ft.commit(); return true; } };
本例中,用戶選中下拉菜單項時,佈局中就會加入一個fragment(替換R.id.fragment_container視圖中的當前fragment)。加入時fragment會帶有一個tag字符串,也就是對應的下拉菜單項的字符串。
下面是本例中定義各fragment的ListContentFragment類:
public class ListContentFragment extends Fragment { private String mText; @Override public void onAttach(Activity activity) { // This is the first callback received; here we can set the text for // the fragment as defined by the tag specified during the fragment transaction super.onAttach(activity); mText = getTag(); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // This is called to define the layout for the fragment; // we just create a TextView and set its text to be the fragment tag TextView text = new TextView(getActivity()); text.setText(mText); return text; } }
Action Bar的風格化
如果想實現能代表品牌風格的視覺設計,action bar允許你對其每個細節進行定製,包括action bar顏色,文字顏色,按鈕風格等等。你須使用Android的style
and theme框架來定義自己的風格屬性。
注意:提供背景圖資源時,確保使用Nine-Patch
drawables來保證拉伸時的效果,nine-patch圖片應小於40dp
高,30dp寬。
總體外觀
指定爲action bar定義各種風格的風格資源文件
默認爲Widget.AppCompat.ActionBar,你的自定義風格應該以它爲父風格
支持的各種風格如下:
定義action bar背景的圖片資源
定義stacked action bar(tab標籤)的圖片資源
定義分離式action bar的圖片資源
爲action按鈕定義風格資源
默認爲Widget.AppCompat.ActionButton,你的自定義風格應該以它爲父風格
定義overflow action項的風格資源
默認爲Widget.AppCompat.ActionButton.Overflow,你的自定義風格應該以它爲父風格
定義一個或多個action bar顯示選項,比如是否使用app logo,是否顯示activity標題,以及是否使用Up
action。displayOptions中列出了所有可以使用的值。
定義action項直接的分隔圖片資源
爲action bar標題定義風格資源
默認爲TextAppearance.AppCompat.Widget.ActionBar.Title,你的自定義風格應該以它爲父風格
聲明action bar是覆蓋activity佈局還是將activity佈局下移(例如,Gallery app使用了覆蓋模式)。默認值
爲false。
通常,action bar需要有自己的屏幕空間,activity的佈局則填滿剩餘的空間。Action bar爲覆蓋模式時,activity
佈局會使用所有的屏幕空間,系統會把action bar繪製在其上,如果你想讓自己的內容在action bar顯示和隱藏時保
持同樣的大小和位置,那麼覆蓋模式會很有用。你也可能單純爲其視覺效果而使用它,因爲你可以給action bar一個
半透明的背景,這樣用戶透過action bar也可以看到下面的內容。
開啓覆蓋模式時,你的activity佈局並不知道action bar位於其上,因此在action bar覆蓋區域不能放置重要信息或
UI組件,要想合理適配,你可以引用actionBarSize的系統值來決定視圖上方應該留出多少空間,例如:
也可以在運行時用getHeight()獲取action bar的高度,這個方法返回調用時的action bar高度,在activity生命周 期的早期方法中調用時返回的高度可能不包括stacked action bar(由於navigation tabs)。要得到運行時action bar包括stacked action bar的總高度,見例子app Honeycomb Gallery中的TitlesFragment。<SomeView ... android:layout_marginTop="?android:attr/actionBarSize" />
Action項
定義action按鈕的風格資源
默認爲Widget.AppCompat.ActionButton,你的自定義風格應該以它爲父風格
定義每個action項的背景資源drawable,應爲一個可以指示不同選擇狀態的state-list
drawable
定義每個overflow項的背景,應爲一個可以指示不同選擇狀態的state-list
drawable
定義action項之間分隔的drawable資源
定義action項文字的顏色
定義action項文字的外觀風格資源
定義action view widgets的主題資源
Navigation tabs
定義action bar的tab標籤的風格資源
默認爲Widget.AppCompat.ActionBar.TabView,你的自定義風格應該以它爲父風格
定義navigation tab下方細條的風格資源
默認爲Widget.AppCompat.ActionBar.TabBar,你的自定義風格應該以它爲父風格
定義navigation tab中的文字風格資源
默認爲Widget.AppCompat.ActionBar.TabText,你的自定義風格應該以它爲父風格
下拉列表
定義下拉導航的風格(如背景和文字風格)
Theme例子
這是個爲activity定義自定義主題的例子,CustomActivityTheme,包含了幾個用來自定義action
bar的風格。
注意,每個action bar風格有兩個版本,第一個屬性名前包含了android:前綴,以支持framework中已包含這些屬性的API
level 11及以上版本;第二個版本沒有android:前綴,針對使用support library中風格屬性的老版本,效果是一樣的。
在manifest文件中,可以爲整個app指定theme:<?xml version="1.0" encoding="utf-8"?> <resources> <!-- the theme applied to the application or activity --> <style name="CustomActionBarTheme" parent="@style/Theme.AppCompat.Light"> <item name="android:actionBarStyle">@style/MyActionBar</item> <item name="android:actionBarTabTextStyle">@style/TabTextStyle</item> <item name="android:actionMenuTextColor">@color/actionbar_text</item> <!-- Support library compatibility --> <item name="actionBarStyle">@style/MyActionBar</item> <item name="actionBarTabTextStyle">@style/TabTextStyle</item> <item name="actionMenuTextColor">@color/actionbar_text</item> </style> <!-- general styles for the action bar --> <style name="MyActionBar" parent="@style/Widget.AppCompat.ActionBar"> <item name="android:titleTextStyle">@style/TitleTextStyle</item> <item name="android:background">@drawable/actionbar_background</item> <item name="android:backgroundStacked">@drawable/actionbar_background</item> <item name="android:backgroundSplit">@drawable/actionbar_background</item> <!-- Support library compatibility --> <item name="titleTextStyle">@style/TitleTextStyle</item> <item name="background">@drawable/actionbar_background</item> <item name="backgroundStacked">@drawable/actionbar_background</item> <item name="backgroundSplit">@drawable/actionbar_background</item> </style> <!-- action bar title text --> <style name="TitleTextStyle" parent="@style/TextAppearance.AppCompat.Widget.ActionBar.Title"> <item name="android:textColor">@color/actionbar_text</item> </style> <!-- action bar tab text --> <style name="TabTextStyle" parent="@style/Widget.AppCompat.ActionBar.TabText"> <item name="android:textColor">@color/actionbar_text</item> </style> </resources>
或者爲單個activity指定:<application android:theme="@style/CustomActionBarTheme" ... />
注意:確保每個theme和風格在<activity android:theme="@style/CustomActionBarTheme" ... />
<style>
標籤中聲明其父theme,這樣它們會從父theme中繼承所有未顯式聲明風格。修改action
bar時,使用父theme很重要,這樣你可以不用重新實現所有的風格屬性(比如文字大小或action項的間距),只需override你要改變的風格。
更多關於app中風格和theme資源的信息,請閱讀Styles and
Themes
原文:Action Bar