操做欄php
操做欄是一種能標識用戶位置、提供用戶操做和導航模式的窗口特徵。使用操做欄能讓你的系統優雅的適配不一樣的屏幕配置,給你的用戶在整個應用中提供熟悉的界面。html
圖1. 一個包含了 [1] 應用圖標,[2] 兩個功能項,[3] 更多操做的操做欄。java
操做欄提供了一些關鍵功能:android
分配一個專用空間爲你的應用提供一致性並標識用戶在應用中的位置。api
用可預測的方式突出重要的操做而且容易訪問(好比搜索框)。數組
在應用中提供一致的導航和視圖切換效果(使用選項卡和下拉列表)。ruby
更多關於操做欄的交互模式和設計指南,請參考 Action Bar 設計指南。app
雖然 ActionBar
API直到Android 3.0(API等級11)才被引入,可是爲了兼容Android 2.1(API等級7)和以上的系統,提供了可用的 Support Library 。框架
這篇指南主要講述如何使用支持庫中的操做欄, 可是若是你的應用只支持Android 3.0或更高的版本,你應該使用框架提供的 ActionBar
。他們大部分的API都是同樣的,可是屬於不一樣的包命名空間,還有些例外,好比下面的章節中提到的方法名和簽名。ide
注意: 請肯定你從合適的包中引入了正確的 ActionBar
類 (和相關的API):
若是支持的API等級小於11: import android.support.v7.app.ActionBar
若是僅支持API等級11或更高的: import android.app.ActionBar
註解: 若是你想知道關於上下文操做欄顯示上下文操做項的信息,請參考 Menu 指南。
就像上面提到的,這篇指南主要講述如何使用支持庫中 ActionBar
的API。因此在你添加操做欄以前,你必須按照 Support Library Setup 中說明的爲你的項目設置 appcompat v7 支持庫。
一旦你的項目設置好支持庫,下面介紹如何添加操做欄:
經過繼承 ActionBarActivity 來建立你的activity。
對你的activity使用或繼承 Theme.AppCompat
主題中的一種。例如:
<activity android:theme="@style/Theme.AppCompat.Light" ... >
如今當你的應用運行在Android 2.1(API等級7)或以上的系統時,你的activity就包含了操做欄。
API等級11或更高時
當 targetSdkVersion
或 minSdkVersion
屬性被設置爲 "11"
或更高時,activity默認狀況下使用 Theme.Holo 主題,任何使用這種主題(或他們的子類)的activity都擁有操做欄。
你能夠在運行時調用 hide()
方法來隱藏操做欄。例如:
ActionBar actionBar = getSupportActionBar(); actionBar.hide();
API等級11或更高時
調用 getActionBar() 方法來獲取 ActionBar
。
當操做欄隱藏時,系統會調整你的佈局去填充可用的屏幕空間。你能夠調用 show() 方法從新顯示操做欄。
請注意當隱藏或移除操做欄時會致使activity爲了填充操做欄佔用的空間去從新排版佈局。若是你的activity常常會隱藏或顯示操做欄,你可能但願去開啓 overlay 模式。Overlay模式模糊化activity佈局的頂部位置並在activity佈局前面繪製操做欄。這樣,你的佈局無論操做欄隱藏或重現都是固定的。爲你的activity建立一個自定義的主題而且把 windowActionBarOverlay
屬性置爲 true 就能啓用overlay模式了。更多詳情,請參考下面關於 Styling the Action Bar 的章節。
默認狀況下,系統在操做欄裏使用的是在 <application>
or <activity>
元素裏 icon
屬性指定的圖標。然而,若是你同時指定了 logo
屬性,那麼操做欄將會使用商標而不是圖標。
商標一般應該比圖標寬,除此以外不該該包括沒必要要的文字。你一般應該在使用用戶能夠識別的傳統方式展現你的品牌時才使用商標。一個很好的事例是YouTube應用的商標,商標表明瞭用戶指望的品牌,然而應用的圖標被修改爲方形的以符合啓動圖標的要求。
圖2. 包含了三個功能項和更多功能按鈕的操做欄。
操做欄爲用戶提供了應用當前上下文相關的最重要功能項的訪問方式。那些在操做欄裏直接顯示的帶或不帶文字的圖標被叫作功能按鈕。沒法安裝在操做欄或不重要的功能被隱藏在更多操做選項中。用戶能夠按下右邊的更多操做按鈕(若是設備有菜單按鈕的話,也能夠按下菜單按鈕)顯示其餘的功能列表。
當你的activity啓動時,系統經過調用activity的 onCreateOptionsMenu() 方法填入功能項。使用這個方法去擴充一個定義了全部功能項的 menu resource 。例如,下面是一個定義了一些菜單項的菜單資源:
res/menu/main_activity_actions.xml
<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>
而後在activity的 onCreateOptionsMenu()
方法裏把菜單資源擴充到給定的 Menu 中以便把每個功能項添加到操做欄中:
@Overridepublic 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);}
要把一個菜單項做爲一個功能按鈕直接顯示在操做欄上,請在 <item> 標籤裏包含 showAsAction="ifRoom"
。 例如:
<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>
若是在操做欄中沒有足夠的空間顯示項目的話,它將在更多操做中顯示。
使用支持庫中的XML屬性
注意上面的 showAsAction
屬性使用的定義在 <menu> 標籤中的自定義命名空間。由於這些屬性在舊設備的安卓框架中並不存在,那麼在使用支持庫裏定義的任何XML屬性時這麼作仍是頗有必要的。因此你必須爲全部支持庫定義的屬性使用你本身的命名空間做爲前綴。
若是你的菜單項同時擁有標題和圖標屬性,那麼默認狀況下功能項只顯示圖標。若是你想顯示文本標題,把 "withText" 添加到 showAsAction
屬性裏便可。例如:
<item yourapp:showAsAction="ifRoom|withText" ... />
註解: 對於操做欄來講 "withText"
意味着文本標題應該顯示出來,操做欄會盡量的顯示出標題,可是當圖標可用而且操做欄沒有剩餘空間時不會顯示標題。
儘管你不想功能項帶着標題顯示出來,你仍是應該老是爲每個功能項定義標題,緣由以下:
若是在操做欄裏沒法爲功能項提供足夠的空間,那麼菜單項將會放在更多操做裏而且只會顯示標題。
視力障礙讀者能夠讀出菜單項的標題。
若是功能項僅顯示圖標的話,那麼當用戶長按功能項時會顯示描述這個功能項的短語。
雖然圖標是可選的,可是咱們建議使用。關於圖標設計建議,請參考 Iconography 設計指南。你也能夠從這個 Downloads 頁面下載一些標準操做欄圖標合集(好比搜索欄和放棄鍵)。
你也可使用 "always"
來聲明一個功能項始終做爲功能按鈕來展現。然而,經過這種方式你並不能使一個功能項強制出如今操做欄裏。 在擁有狹窄屏幕的設備上這麼作會產生一些佈局問題。最好使用 "ifRoom" 去請求系統把一個功能項顯示在操做欄中,這樣能夠容許系統在空間不足的狀況下把功能項移動到更多操做選項裏。然而,若是功能項包含一個不能收縮的 action view 而且這個功能項爲了提供關鍵功能的入口必須老是可見的話,使用 "always" 就很必要了。
當用戶按下一個功能項時,系統將會調用activity裏的 onOptionsItemSelected()
方法。系統會傳遞 MenuItem
對象給這個方法,這樣你能夠經過調用它的 getItemId() 方法來區分功能項。它會返回 <item> 標籤的 id 屬性指定了的惟一的ID,因此你能執行合適的操做。例如:
@Overridepublic boolean onOptionsItemSelected(MenuItem item) { // 處理操做欄功能項的點擊 switch (item.getItemId()) { case R.id.action_search: openSearch(); return true; case R.id.action_compose: composeMessage(); return true; default: return super.onOptionsItemSelected(item); }}
註解: 若是你在fragment中經過 Fragment 類的 onCreateOptionsMenu() 方法擴展菜單項,系統會在用戶選中其中菜單項時調用 onOptionsItemSelected()
回調。然而,activity擁有優先處理事件的機會, 因此係統會在fragment調用 onOptionsItemSelected() 前,優先調用activity裏相同名字的回調。爲了確保activity裏的任何frament都一樣擁有處理回調的機會,當你不想處理這個項目時請老是把調用傳遞給它的父類做爲默認的處理方式而不是返回false
。
圖3. 實物模型展現了帶選項卡的操做欄(左),分離操做欄(中)和不帶應用圖標和標題的操做欄(右)。
當activity運行在狹窄的屏幕上時(好比垂直方向的手機),分離操做欄能夠在屏幕底部提供一個能顯示全部功能項的分離欄。
這樣分離操做欄能夠確保在狹窄的屏幕上用合理的空間去顯示全部的功能項,頂部剩餘的空間能夠顯示導航和標題元素。
當使用支持庫時想開啓分離操做欄,你必須作下面兩件事:
在 <application>
元素或每一個 <activity>
元素裏添加uiOptions="splitActionBarWhenNarrow"
。這個屬性僅在API等級14或更高的系統上支持(舊版本會忽略這個屬性)。
爲了支持舊版本,在每一個 <activity>
元素裏添加 <meta-data>
子元素,而且聲明 "android.support.UI_OPTIONS" 的值爲splitActionBarWhenNarrow 。
例如:
<manifest ...> <activity uiOptions="splitActionBarWhenNarrow" ... > <meta-data android:name="android.support.UI_OPTIONS" android:value="splitActionBarWhenNarrow" /> </activity></manifest>
若是你移除了操做欄的圖標和標題(如圖3右側所示),使用分離操做欄能夠容許 navigation tabs 收縮在主操做欄中。要達到這種效果,請分別調用 setDisplayShowHomeEnabled(false)
和 setDisplayShowTitleEnabled(false)
方法使操做欄的圖標和標題不可用。
圖4. Gmail中的向上按鈕
使用應用圖標做爲向上導航按鈕可使用戶基於界面間的層次關係操做你的應用。舉個例子,若是界面A顯示一個項目列表而且選擇一個項目時會通向界面B,那麼界面B應該包含用來返回界面A的向上的按鈕。
註解: 向上導航與系統回退鍵提供的後退導航功能不一樣。回退鍵被用來在用戶最近使用過的歷史界面裏以倒序的方式進行導航,這是基於界面間的臨時關係而不是應用的層級結構(向上導航的基礎正是應用的層級結構)。
調用 setDisplayHomeAsUpEnabled() 能夠將應用的圖標做爲向上按鈕使用。例如:
@Overrideprotected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_details); ActionBar actionBar = getSupportActionBar(); actionBar.setDisplayHomeAsUpEnabled(true); ...}
如今操做欄裏的圖標旁會出現一個向上的符號(如圖4所示)。然而,默認它不會有任何效果。要指定當用戶按下向上按鈕時應該打開哪一個activity的話,你有下面兩種選擇:
在清單文件中指定它的父級activity。
當 父級activity老是同樣時 這種是最好的選擇。經過在清單文件中聲明父級activity,操做欄能夠在用戶按下向上按鈕時自動執行正確的操做。
從Android 4.1(API等級16)開始,你能夠在 <activity>
元素中使用 parentActivityName
屬性來指明父級activity。
要使用支持庫去支持舊設備,一樣要包含一個用來指定指定父級activity的 <meta-data>
元素,而且指定的父級activity用來做爲android.support.PARENT_ACTIVITY 的值。例如:
<application ... > ... <!-- 主activity(沒有父級activity) --> <activity android:name="com.example.myfirstapp.MainActivity" ...> ... </activity> <!-- 主activity的子級activity --> <activity android:name="com.example.myfirstapp.DisplayMessageActivity" android:label="@string/title_activity_display_message" android:parentActivityName="com.example.myfirstapp.MainActivity" > <!-- 父級activity元數據用來支持API等級7或以上的版本 --> <meta-data android:name="android.support.PARENT_ACTIVITY" android:value="com.example.myfirstapp.MainActivity" /> </activity></application>
一旦像這樣在清單文件中指定了父級activity而且經過 setDisplayHomeAsUpEnabled() 啓用了向上按鈕後,你的工做就完成了,操做欄會正確的向上導航。
或者,重寫activity中的 getSupportParentActivityIntent()
和 onCreateSupportNavigateUpTaskStack()
。
取決於用戶如何到達當前界面的, 父級activity也可能不一樣 ,那麼這麼作就很合適了。也就是說,若是用戶有許多能夠到達當前界面的路徑,那麼向上按鈕應該按照用戶實際到達的路勁回退導航。
當用戶按下向上按鈕時系統會調用 getSupportParentActivityIntent()
去導航你的應用(在你應用本身的任務棧內)。取決於用戶如何到達當前位置的,向上導航上應該打開的activity也會不一樣,因此你應該重寫這個方法以返回能夠打開合適父級activity的 Intent。
當用戶按下向上按鈕而且你的activity也運行在不屬於你應用的任務棧中時,系統會爲你的activity調用 onCreateSupportNavigateUpTaskStack()
。所以,當用戶向上導航時你必須使用 TaskStackBuilder
傳遞到這個方法去創建那個應該合併的合適的回退棧。
雖然你重寫了 getSupportParentActivityIntent()
來具體指定用戶在應用中的具體導航,可是你不想實現 onCreateSupportNavigateUpTaskStack() ,那麼你能夠像上面所示在清單文件中聲明「默認」父級activity達到一樣的效果。而後 onCreateSupportNavigateUpTaskStack() 的默認實現會基於清單中聲明的父級activity去合併回退棧。
註解: 若是你是使用大量的fragment而不是activity去構建你應用層級結構,那麼上面的選項都不會生效。要在fragment裏向上導航,你應該改成重寫 onSupportNavigateUp() ,一般經過調用 popBackStack() 方法把當前fragment從回退棧裏出棧去來執行合適的fragment事務。
更多關於實現向上導航的信息,請閱讀 Providing Up Navigation 。
圖5. 帶可收縮 SearchView 的操做欄。
操做視圖是一種顯示在操做欄中並能夠做爲操做按鈕替代品的小部件。操做視圖能夠在不替換操做欄、不改變activity和fragment的的狀況下提供快速訪問富操做入口。例如,若是你有搜索的操做,你能夠如圖5所示在操做欄裏把一個 SearchView
控件嵌入到操做視圖中。
可使用 actionLayout
或 actionViewClass
屬性去分別指定使用的佈局資源或控件類。例如,下面介紹如何添加 SearchView 控件:
<?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>
注意上面的 showAsAction
屬性裏也包含了 "collapseActionView"
值。這是用來聲明操做視圖應該被收縮到一個按鈕中的可選操做。(在後面關於 Handling collapsible action views 的章節裏會進一步解釋這種用法。)
若是你須要配置操做視圖(例如添加事件監聽器),你能夠在 onCreateOptionsMenu() 回調裏處理。你能夠傳遞相應的 MenuItem 給靜態方法 MenuItemCompat.getActionView() 去獲取操做視圖的對象。例如,能夠這樣獲取上面例子的搜索控件:
@Overridepublic 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); // 配置搜索框和添加事件監聽器 ... return super.onCreateOptionsMenu(menu);}
API等級11或更高
調用對應 MenuItem 的 getActionView() 方法獲取操做視圖:
menu.findItem(R.id.action_search).getActionView()
更多關於使用搜索控件的信息,請參考 Creating a Search Interface 。
爲了維護操做欄的空間,你能夠把你的操做視圖收縮到一個操做按鈕中。當收縮時,系統可能會把操做按鈕放到更多操做內,可是當用戶選擇操做時操做視圖仍是會在操做欄中顯示。如上面XML中所示,你能夠經過在 showAsAction 屬性裏添加"collapseActionView" 來收縮你的操做視圖。
由於系統在用戶選擇操做時會擴展操做視圖,因此你不須要在 onOptionsItemSelected() 回調中響應這個操做。系統仍是會調用 onOptionsItemSelected() ,可是若是你返回了true(代表你已經處理了事件)那麼操做視圖就不會擴展了。
當用戶按下向上按鈕或回退按鈕時系統一樣會收縮你的操做視圖。
若是你須要基於操做視圖的可見性來更新activity,能夠經過定義一個 OnActionExpandListener 並把它傳遞給 setOnActionExpandListener() 方法來在操做視圖擴展和收縮時接收到回調。例如:
@Overridepublic boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.options, menu); MenuItem menuItem = menu.findItem(R.id.actionItem); ... // 當使用支持庫時,setOnActionExpandListener()方法是靜態的而且接受MenuItem對象做爲參數 MenuItemCompat.setOnActionExpandListener(menuItem, new OnActionExpandListener() { @Override public boolean onMenuItemActionCollapse(MenuItem item) { // 當收縮時執行 return true; // 返回ture去收縮操做視圖 } @Override public boolean onMenuItemActionExpand(MenuItem item) { //當擴展時執行 return true; // 返回true去擴展操做視圖 } });}
圖6. 操做欄被 ShareActionProvider 擴充來展現分享目標。
與 action view 類似,操做提供者使用自定義的佈局去替換操做按鈕。然而,不像操做視圖,操做提供者控制全部的操做行爲而且能夠在按下時顯示子菜單。
要想聲明操做提供者,在菜單 <item>
標籤裏使用 ActionProvider 類的徹底限定名填充到 actionProviderClass 屬性裏。
你能夠繼承 ActionProvider 類來構建你本身的操做提供者,不過Android已經提供了一些預構建的操做提供者,例如 ShareActionProvider ,經過直接在操做欄裏(如圖6所示)展現能夠分享的應用列表來提供分享操做。
由於每一個 ActionProvider
類已經定義過它本身的操做行爲了,因此你沒必要在 onOptionsItemSelected() 內監聽這些操做。若是須要的話,好比你須要同時執行另外的操做,你仍是能夠在 onOptionsItemSelected() 中監聽這些點擊事件。可是爲了使操做提供者依舊能接收到 onPerformDefaultAction() 回調來執行它的預約操做請確保返回值的是false。
然而,若是操做提供者提供的是操做子菜單,那麼你的activity在用戶打開這個列表或選擇子菜單中的一項時不會接收到 onOptionsItemSelected() 的調用。
使用 ShareActionProvider 類去定義 <item> 裏的一個名爲actionProviderClass
的標籤,這樣你就能用 ShareActionProvider 去添加一個「分享」操做了。例如:
<?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() 方法,傳遞一個持有操做提供者的 MenuItem
對象給 MenuItemCompat.getActionProvider() 方法,而後傳遞一個帶有合適內容的在返回的 ACTION_SEND intent給 ShareActionProvider 上的 setShareIntent() 方法。
你應該在 onCreateOptionsMenu() 內調用一次 setShareIntent() 去初始化分享操做。可是由於用戶上下文可能會變,因此一旦分享上下文改變時,你必須從新調用 setShareIntent() 去更新intent。
例如:
private ShareActionProvider mShareActionProvider;@Overridepublic boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.main_activity_actions, menu); // 設置ShareActionProvider的默認分享intent MenuItem shareItem = menu.findItem(R.id.action_share); mShareActionProvider = (ShareActionProvider) MenuItemCompat.getActionProvider(shareItem); mShareActionProvider.setShareIntent(getDefaultIntent()); return super.onCreateOptionsMenu(menu);}/** 定義一個默認的(虛擬的)分享intent去初始化操做提供者。 * 然而,一旦在intent裏實際使用的上下文變化了,你必須再次調用mShareActionProvider.setShareIntent()去更新分享intent */private Intent getDefaultIntent() { Intent intent = new Intent(Intent.ACTION_SEND); intent.setType("image/*"); return intent;}
如今 ShareActionProvider
處理全部與操做項的用戶交互而且你沒必要要在 onOptionsItemSelected() 回調方法裏處理點擊事件。
默認狀況下, ShareActionProvider
基於用戶選擇的頻率對每一個分享目標項進行排名。越經常使用的分享目標項出如今下拉列表越靠近頂端的位置,而且最經常使用的直接做爲默認分享目標出如今操做欄上。默認狀況下,排名信息被保存在 DEFAULT_SHARE_HISTORY_FILE_NAME 指定文件名的私有文件中。若是你僅僅爲一種操做類型使用 ShareActionProvider
或它的擴展,那麼你應該繼續使用默認的歷史文件而不用作任何事。然而,若是你爲語義不一樣的多樣的操做類型使用 ShareActionProvider
或它額擴展,那麼爲了維持他們各自的歷史須要爲每一種 ShareActionProvider
指定它們本身的歷史文件。調用 setShareHistoryFileName() 並傳遞一個XML文件名(例如,"custom_share_history.xml" )就能爲 ShareActionProvider 指定一個不一樣的歷史文件。
註解: 儘管 ShareActionProvider 是基於使用分享目標項的頻率進行排名,這種處理行爲仍是能夠擴展的, ShareActionProvider 的擴展能夠執行不一樣的處理方式並基於歷史文件進行排名(在適當狀況下)。
建立你本身的操做提供者可使你在獨立的模塊裏重用和管理動態的操做項,而不是在activity或fragment代碼裏處理操做項的轉變和行爲。就像在前面章節中展現的,安卓已經爲分享操做提供了一個 ActionProvider 的擴展:ShareActionProvider
。
爲不一樣的操做建立你本身的操做提供者,你只須要繼承 ActionProvider 類並實現適當的回調方法便可。最重要的是,你應該繼承下列方法:
這個構造方法傳遞給你應用的 Context
,你應該把它保存在成員變量裏以便在其餘回調方法裏使用。
在這裏爲這個項目定義操做視圖。使用從構造方法裏得到的 Context
去實例化 LayoutInflater
並使用XML資源去擴充你的操做視圖佈局,而後掛載事件監聽器。例如:
public View onCreateActionView(MenuItem forItem) { // 擴充要在操做欄展現的操做視圖。 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) { // 一些處理... } }); return view;}
當用戶選擇了更多操做裏的菜單項時系統將會調用這個方法,而且操做提供者應該爲菜單項執行默認的操做。
然而,若是你的操做提供者支持子菜單,那麼經過 onPrepareSubMenu()
回調,當操做提供者被放置在更多操做內時,子菜單實際上仍是顯示的。所以,當有子菜單時 onPerformDefaultAction()
絕對不會被調用。
註解: 實現 onOptionsItemSelected() 的activity或fragment能夠經過處理菜單項被選中事件(而且返回true)來重寫操做提供者的默認行爲(除非它使用了子菜單),在這種狀況下,系統不會調用 onPerformDefaultAction() 。
ActionProvider 擴展的例子,請參考 ActionBarSettingsActionProviderActivity 。
圖7 . 寬屏上的操做欄選項卡
圖 8. 窄屏上的選項卡
操做欄上的選項卡可使用戶很方便的在你應用裏不一樣的頁面間瀏覽與切換。ActionBar 支持選項卡是很是明智的,由於他們能夠適配不一樣的屏幕尺寸。例如,當屏幕足夠寬時,選項卡出如今操做欄裏的操做按鈕旁邊(如圖7所示,好比在平板上),然而當在一個狹窄的屏幕上時,他們出如今一個分離的操做欄上(如圖8所示,被稱爲「堆疊的操做欄」) 。在某些狀況下,安卓系統爲了保證選項卡在操做欄上的最佳擺放會如下拉列表形式去展現選項卡。
首先,你的佈局必須包含一個 ViewGroup ,用來放置選項卡相關的 Fragment。確保 ViewGroup
擁有一個資源ID以便你能在代碼裏引用它並在裏面切換選項卡。另外一種狀況,若是選項卡的內容將會填充整個activity佈局,那麼activity徹底不須要設置佈局(你甚至不須要調用 setContentView() ), 也就是說你能夠把每一個fragment放置在默認根視圖(經過資源ID android.R.id.content 能夠獲取跟視圖的引用)中。
一旦你決定了fragment在佈局文件中的位置,那麼添加選項卡的基本過程就是下面這些步驟:
繼承 ActionBar.TabListener
接口。這個接口提供選項卡事件的回調,好比用戶按下時你能夠切換選項卡。
爲每一個你想添加的選項卡,實例化一個 ActionBar.Tab 實例而且調用 setTabListener() 去設置 ActionBar.TabListener
,同時別忘了使用 setText()
設置選項卡的標題(視須要也可使用 setIcon() 設置圖標)。
而後調用 addTab() 把每一個選項卡添加到操做欄中。
注意, ActionBar.TabListener
回調方法沒有說明哪一個fragment與這個選項卡關聯,僅僅指出了哪一個 ActionBar.Tab 被選中。你必須本身把每一個 ActionBar.Tab 與它表明的合適的 Fragment 關聯起來。取決於你的設計,你可使用不少方式去關聯。
例如,下面的例子介紹瞭如何繼承 ActionBar.TabListener
使每一個選項卡使用它本身的監聽器實例:
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; /** 每次調用構造方法會建立一個新的選項卡 * @param activity 宿主Activity,用來實例化fragment * @param tag fragment的標識符 * @param clz fragment的類,被用來實例化frament */ public TabListener(Activity activity, String tag, Class<T> clz) { mActivity = activity; mTag = tag; mClass = clz; } /* 下面是 ActionBar.TabListener 的回調 */ public void onTabSelected(Tab tab, FragmentTransaction ft) { // 檢查fragment是否已經實例化 if (mFragment == null) { // 若是沒有,實例化而且添加到activity中 mFragment = Fragment.instantiate(mActivity, mClass.getName()); ft.add(android.R.id.content, mFragment, mTag); } else { // 若是已經存在,爲了顯示出來只需把fragment依附到activity上 ft.attach(mFragment); } } public void onTabUnselected(Tab tab, FragmentTransaction ft) { if (mFragment != null) { // 取消fragment的依附,由於另一個須要依附上 ft.detach(mFragment); } } public void onTabReselected(Tab tab, FragmentTransaction ft) { // 用戶從新選擇已經選擇過的標籤欄,一般不須要作任何事 }}
警告: 這這些回調裏你 絕對不能 調用 commit()
去提交fragment事務—由於系統會爲你調用它而且若是你本身調用時可能會拋出異常。一樣你也 不能 把這些fragment事務添加到回退棧中。
在這個事例裏,當各自的選項卡被選中時,監聽器僅僅把fragment依附(attach() )到activity佈局中,或者若是fragment沒有實例化,建立fragment並把它添加( add() )到佈局中(做爲 android.R.id.content 視圖組的一個子視圖),當選項卡取消選中時取消fragment的依附( detach() )。
剩下須要作的就是建立每一個 ActionBar.Tab
並添加到 ActionBar
中。另外,你必須調用 setNavigationMode(NAVIGATION_MODE_TABS)
使選項卡可見。
例如,下面的代碼使用上面定義的監聽器添加兩個選項卡:
@Overrideprotected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 注意沒用使用setContentView(),由於咱們使用根節點android.R.id.content做爲fragment的容器 // 設置操做欄上的選項卡 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);}
若是activity中止了,你應該在 saved instance state 中保存當前選中的選項卡信息以便在用戶返回時能夠打開合適的選項卡。當保存狀態時,你可使用 getSelectedNavigationIndex() 查詢當前選中的選項卡,它會返回選中選項卡的索引位置。
注意: 當用戶使用選項卡在fragment之間切換,爲了能返回到前一個fragment的狀態,保存每一個fragment的狀態就顯得很是重要了,這樣它們看起來還像它們離開時那樣。雖然某些狀態默認會被保存了,可是你可能須要手動保存自定義視圖的狀態。更多關於保存fragment狀態的信息,請參考 Fragments API指南。
註解: 上面繼承 ActionBar.TabListener
的方式只是許多合適的技巧中的一種。另一種經常使用的方式是使用 ViewPager
去管理fragment,這樣用戶可使用滑動手勢去切換選項卡。在這種狀況下,你只需在 onTabSelected() 回調裏告訴 ViewPager
當前選項卡的索引位置。更多詳細的信息,請閱讀 Creating Swipe Views with Tabs 。
圖9 . 操做欄中的下拉導航列表。
activity還有另一種導航模式(或稱爲過濾),在操做欄中嵌入下拉列表(也被稱爲「spinner」)。例如,下拉列表能夠根據activity中內容的分類提供不一樣的模式。
當改變內容的很重要可是又不頻繁發生時使用下拉列表就很是有用了。假如切換內容會更頻繁的話,你應該使用 navigation tabs 來替代。
開啓下拉導航的基本過程:
建立一個 SpinnerAdapter
爲下拉列表提供可選項目列表和佈局以便在繪製列表裏的項目時使用。
繼承 ActionBar.OnNavigationListener
來定義用戶選中列表裏項目時的操做。
在activity的 onCreate()
方法裏調用 setNavigationMode(NAVIGATION_MODE_LIST) 啓用操做欄的下拉列表。
使用 setListNavigationCallbacks() 設置下拉列表的回調。例如:
actionBar.setListNavigationCallbacks(mSpinnerAdapter, mNavigationCallback);
這個方法須要傳入 SpinnerAdapter
和 ActionBar.OnNavigationListener
。
這個過程是很是簡短的,可是實現 SpinnerAdapter
和 ActionBar.OnNavigationListener
須要作大量的工做。在這個文檔的範圍外還有有許多方式能夠實現它們來爲你的下拉導航定義功能並實現不一樣類別的 SpinnerAdapter (你應該查閱 SpinnerAdapter 類獲取更多信息)。下面只是個讓你 入手 SpinnerAdapter
和 ActionBar.OnNavigationListener
的簡單事例。
SpinnerAdapter 和 OnNavigationListener
SpinnerAdapter
是爲spinner控件提供數據的適配器,例如操做欄中的下拉列表。 SpinnerAdapter
是一個你能夠繼承的接口,然而安卓提供了一些有用的實現你能夠直接拿來擴展,好比 ArrayAdapter
和 SimpleCursorAdapter
。例如,經過使用 ArrayAdapter 實現能夠很輕鬆的建立一個使用字符數組做爲數據源的 SpinnerAdapter
實例:
SpinnerAdapter mSpinnerAdapter = ArrayAdapter.createFromResource(this, R.array.action_list, android.R.layout.simple_spinner_dropdown_item);
createFromResource()
方法接收3個參數:應用的 Context
,字符數組的資源ID和每一個列表項目使用的佈局。
資源文件中的字符輸入定義以下:
<?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>
經過 createFromResource()
方法返回 ArrayAdapter 後你就能夠直接把他傳遞給 setListNavigationCallbacks() (上面的步驟4)。在這麼作以前,你須要建立 OnNavigationListener
。
你能夠實現 ActionBar.OnNavigationListener
在用戶選擇下拉列表裏的項目時來處理fragment的變化或activity的修改。繼承這個監聽器只須要實現一個回調方法: onNavigationItemSelected()
。
onNavigationItemSelected()
方法接受列表中項目的位置和 SpinnerAdapter 提供的項目惟一ID做爲參數。
下面是實例化一個 OnNavigationListener 匿名實現的事例,把一個 Fragment
插入到 R.id.fragment_container 標識的佈局容器中:
mOnNavigationListener = new OnNavigationListener() { // 獲取字符串給下拉列表的適配器使用 String[] strings = getResources().getStringArray(R.array.action_list); @Override public boolean onNavigationItemSelected(int position, long itemId) { // 建立咱們定製的fragment事例 ListContentFragment newFragment = new ListContentFragment(); FragmentTransaction ft = openFragmentTransaction(); // 無論fragment是否在fragment容器裏都直接替換掉,而且用選中位置的字符串做爲fragment的標籤名 ft.replace(R.id.fragment_container, newFragment, strings[position]); // 提交改變 ft.commit(); return true; }};
OnNavigationListener
實例化後你就能夠把 ArrayAdapter
和這個 OnNavigationListener
傳遞給 setListNavigationCallbacks()
(步驟4)並調用。
在這個事例中,當用戶選中下拉列表中的項目時,把一個fragment添加到佈局中(替換 R.id.fragment_container 視圖中的當前fragment)。被添加的fragment被指定一個標籤來做爲它的惟一標識,而且標籤名與下拉列表中標識fragment的字符串同樣。
在這個事例中, ListContentFragment
類定義了每一個fragment:
public class ListContentFragment extends Fragment { private String mText; @Override public void onAttach(Activity activity) { // 這是接收到的第一個回調;咱們能夠在fragment事務期間在此用指定的標籤名爲fragment設置文本內容 super.onAttach(activity); mText = getTag(); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // 這裏爲fragment定義佈局 // 咱們只建立一個文本而且文本內容設置爲fragment標籤名 TextView text = new TextView(getActivity()); text.setText(mText); return text; }}
若是你想創造能夠表明你應用品牌的視覺設計,那麼你能夠經過定製操做欄的每一個顯示細節達到效果,包括操做欄的顏色,文本的顏色,按鈕的樣式等等。這麼作的話,你須要使用安卓的 style and theme 框架的專門的樣式屬性爲操做欄重塑樣式。
注意: 確保全部你使用到的背景圖都是能夠拉伸的 Nine-Patch drawables 。Nine-Patch圖的高度應該小於40dp,寬小於30dp。
指定一個定義操做欄不一樣樣式屬性的樣式資源。
默認樣式是 Widget.AppCompat.ActionBar
,你應該把它做爲父樣式來使用。
支持的樣式包括:
爲操做欄的背景定義圖片資源。
爲堆疊的操做欄(標籤欄)定義圖片資源。
爲 split action bar 定義圖片資源。
爲操做按鈕定義樣式資源。
默認樣式是 Widget.AppCompat.ActionButton
,你應該把它做爲父樣式來使用。
爲更多操做裏的操做項定義樣式資源。
默認樣式是 Widget.AppCompat.ActionButton.Overflow
, 你應該把它當作父樣式來使用。
定義一個或多個操做欄顯示選項,好比是否使用應用商標,顯示activity標題或開啓向上操做。參考 displayOptions
瞭解全部可能的值。
爲操做項間的分割線定義圖片資源。
爲操做欄標題定義樣式資源。
默認樣式是 TextAppearance.AppCompat.Widget.ActionBar.Title
,你應該把它當作父樣式來使用。
聲明操做欄是否應該覆蓋在activity佈局上,而不是平移activity佈局的位置(例如:Gallery應用使用的是覆蓋模式)。默認是 false
。
一般操做欄在屏幕上有本身的空間且activity佈局填充剩下的空間。當操做欄是覆蓋模式時,activity佈局使用全部可用的空間,而後系統在頂部繪製出操做欄。若是你想在操做欄隱藏和顯示時使內容保持固定的大小和位置,使用覆蓋模式就頗有用了。由於你能夠爲操做欄設置半透明背景以便用戶始終能夠看到操做欄後的某些activity佈局,因此你也能夠純粹想把它做爲一種視覺效果來使用。
註解: Holo
主題家族默認使用半透明背景繪製操做欄。然而,你能夠在你的樣式裏修改它而且在不一樣設備上的 DeviceDefault
主題可能默認使用的是不透明的背景。
當覆蓋模式開啓時,你的activity佈局不會意識到操做欄在它的頂部趴着。因此你必須注意不要去替換操做欄覆蓋區域裏重要的信息或UI組建。在適當狀況下,你能夠在XML裏從新指定 actionBarSize 在平臺上的值來決定操做欄的高度。例如:
<SomeView ... android:layout_marginTop="?android:attr/actionBarSize" />
你也能夠在運行時經過 getHeight()
獲取操做欄的高度。 在它被調用時能夠反射出操做欄的高度,但是若是調用發生在activity的早期生命週期裏可能獲取的高度不包含堆疊操做欄(導航標籤欄)。要了解如何在運行時肯定包含堆疊操做欄的總高度,請參考 Honeycomb Gallery 事例應用中的 TitlesFragment
類。
爲操做項按鈕定義樣式資源。
默認樣式是 Widget.AppCompat.ActionButton
,你應該把它當作父樣式來使用。
爲每一個操做項的背景定義圖片資源。爲了標明不一樣選中狀態應該使用 state-list drawable 。
爲每一個更多操做裏的操做項背景定義圖片資源。爲了標明不一樣選中狀態應該使用 state-list drawable 。
爲操做項之間的分割線定義圖片資源。
爲操做項裏顯示的文本定義顏色。
爲操做項裏顯示的文本定義樣式資源。
爲擴充到操做欄裏的 action views 組件定義主題資源。
爲操做欄裏的標籤欄定義樣式資源。
默認樣式是 Widget.AppCompat.ActionBar.TabView
,你應該把它當作父樣式來使用。
爲導航標籤欄下面的細條定義樣式資源。
默認樣式是 Widget.AppCompat.ActionBar.TabBar
,你應該把它當作父樣式來使用。
爲導航標籤欄中的文本定義樣式資源。
默認樣式是 Widget.AppCompat.ActionBar.TabText
, 你應該把它當作父樣式來使用。
爲下拉導航定義樣式(好比背景和文本樣式)。
默認樣式是 Widget.AppCompat.Spinner.DropDown.ActionBar
, 你應該把它當作父樣式來使用。
下面是關於爲activity定義自定義主題的事例, CustomActivityTheme
包含了自定義操做欄的一些樣式。
注意每一個操做欄的樣式屬性有兩種版本。第一種版本在屬性名裏包含了 android:前綴的能夠支持API等級11或更高框架裏的全部屬性。第二種版本不包含 android:前綴的是用來支持舊版本平臺的,系統使用的支持庫裏的樣式屬性。每種效果都是同樣的。
<?xml version="1.0" encoding="utf-8"?><resources> <!-- 應用或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> <!-- 支持庫兼容模式 --> <item name="actionBarStyle">@style/MyActionBar</item> <item name="actionBarTabTextStyle">@style/TabTextStyle</item> <item name="actionMenuTextColor">@color/actionbar_text</item> </style> <!-- 操做欄的大致樣式 --> <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> <!-- 支持庫兼容模式 --> <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> <!-- 操做欄標題文本 --> <style name="TitleTextStyle" parent="@style/TextAppearance.AppCompat.Widget.ActionBar.Title"> <item name="android:textColor">@color/actionbar_text</item> </style> <!-- 操做欄標籤欄文本 --> <style name="TabTextStyle" parent="@style/Widget.AppCompat.ActionBar.TabText"> <item name="android:textColor">@color/actionbar_text</item> </style></resources>
在清單文件中,你能夠爲整個應用應用這個主題:
<application android:theme="@style/CustomActionBarTheme" ... />
或者只爲個別的activity應用:
<activity android:theme="@style/CustomActionBarTheme" ... />
注意: 請確保每一個在 <style> 標籤裏的主題和樣式都聲明瞭父主題,它從中繼承了全部你的主題裏沒有明確聲明的樣式。在修改操做欄時使用父主題是很重要的,這樣你就只需重寫你想要改變的操做欄樣式,而不用重寫那些你不想修改的樣式(好比操做項中的文本大小或邊距)。
更多關於在應用中使用主題和樣式資源的信息,請閱讀 Styles and Themes 。