7. 自定義選單和動作
7.1 問題
應用程式需要為使用者提供一個動作集,但又不想佔用檢視結果的螢幕空間。
7.2 解決方案
(API Level 7)
使用框圖中的選項選單功能在Action Bar內提供常用動作,以及在溢位的彈出式選單中提供額外的選項。此外,通過使用PopMenu,可以將選單附加到現有的檢視並顯示為浮動下拉選單。此功能用於在應用程式中除了ActionBar之外的任意位置放置選單,但在使用者需要這些選單之前使它們保持脫離檢視。
根據裝置平臺的版本的不同,Android的選單功能也有所不同。在早期的版本中,所有的Android都有一個物理的MENU鍵可以觸發這個功能。從Android3.0開始,出現了沒有物理按鈕的裝置,選單功能也變成了ActionBar的一部分。
駐留在ActionBar中的動作項還可以展開以顯示稱為動作檢視的自定義小部件。這可以用於提供搜尋欄位等功能,該功能需要額外的使用者輸入,但要將其隱藏在某個動作項的後面,直到使用者點選時才會顯示。
注意:
該例使用來自Android支援庫的一些相容類來向後相容執行Android 2.1 (API Level 7)的裝置。關於在專案中包括支援庫的更多資訊,請參閱 ofollow,noindex">http://developer.android.com/tools/support-library/index.html 。
7.3 實現機制
以下程式碼清單定義了將在XML中使用的選項選單。
res/menu/options.xml
<menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:appcompat="http://schemas.android.com/apk/res-auto"> <item android:id="@+id/menu_add" android:title="Add Item" android:icon="@android:drawable/ic_menu_add" appcompat:showAsAction="always|collapseActionView" appcompat:actionLayout="@layout/view_action" /> <item android:id="@+id/menu_remove" android:title="Remove Item" android:icon="@android:drawable/ic_menu_delete" appcompat:showAsAction="ifRoom" /> <item android:id="@+id/menu_edit" android:title="Edit Item" android:icon="@android:drawable/ic_menu_edit" appcompat:showAsAction="ifRoom" /> <item android:id="@+id/menu_settings" android:title="Settings" android:icon="@android:drawable/ic_menu_preferences" appcompat:showAsAction="never" /> </menu>
title和icon屬性定義了每個條目該如何顯示,舊版本平臺會顯示這兩個值,而新版本中會顯示一個或根據位置顯示另一個。只有Android 3.0及以後的裝置可以識別showAsAction屬性,該屬性定義了條目是否應該變成Action Bar中的一個動作或者放到溢位選單中。這個屬性最常用的值如下:
- always : 總是作為一個動作顯示其圖示。
- never : 總是顯示在溢位選單中並顯示其名稱。
- ifRoom : 如果Action Bar上有空間,就作為動作顯示,否則顯示在溢位選單中。
選單中的第一個條目還定義了android:actionLayout資源,該資源指向在點選此條目時要展開到其中的小部件;此外還定義了額外的顯示標誌collapseActionView,該標誌告訴框架,此條目有可摺疊的動作檢視以供顯示。此程式碼清單顯示了動作檢視佈局,這是帶有兩個CheckBox例項的簡單佈局。
res/layout/view_action.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal"> <CheckBox android:id="@+id/option_first" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="First"/> <CheckBox android:id="@+id/option_second" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="Second"/> </LinearLayout>
以下的程式碼清單顯示了完整的Activity,其中將選項選單填充到Action Bar中,同時在某個動作項中放置可展開的動作檢視。
覆寫選單動作的Activity
public class OptionsActivity extends ActionBarActivity implements PopupMenu.OnMenuItemClickListener, CompoundButton.OnCheckedChangeListener { private MenuItem mOptionsItem; private CheckBox mFirstOption, mSecondOption; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //此例中無附加工作 } @Override public boolean onCreateOptionsMenu(Menu menu) { //使用此回撥建立選單並執行任何必要的初始化設定 getMenuInflater().inflate(R.menu.options, menu); //查詢並初始化動作項 mOptionsItem = menu.findItem(R.id.menu_add); MenuItemCompat.setOnActionExpandListener(mOptionsItem, new MenuItemCompat.OnActionExpandListener() { @Override public boolean onMenuItemActionExpand(MenuItem item) { //必須返回true以使項展開 return true; } @Override public boolean onMenuItemActionCollapse(MenuItem item) { mFirstOption.setChecked(false); mSecondOption.setChecked(false); //必須返回true以使項摺疊 return true; } }); mFirstOption = (CheckBox) MenuItemCompat.getActionView(mOptionsItem).findViewById(R.id.option_first); mFirstOption.setOnCheckedChangeListener(this); mSecondOption = (CheckBox) MenuItemCompat.getActionView(mOptionsItem).findViewById(R.id.option_second); mSecondOption.setOnCheckedChangeListener(this); return true; } /* 複選框回撥方法 */ @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { if (mFirstOption.isChecked() && mSecondOption.isChecked()) { //隱藏動作檢視 MenuItemCompat.collapseActionView(mOptionsItem); } } @Override public boolean onPrepareOptionsMenu(Menu menu) { //使用此回撥執行每次選單開啟時需要呼叫的設定 return super.onPrepareOptionsMenu(menu); } //通過PopupMenu點選的回撥 public boolean onMenuItemClick(MenuItem item) { menuItemSelected(item); return true; } //通過標準選項選單點選的回撥 @Override public boolean onOptionsItemSelected(MenuItem item) { menuItemSelected(item); return true; } //私有輔助方法,讓每個回撥可以觸發相同的動作 private void menuItemSelected(MenuItem item) { //按id獲得選擇的選項 switch (item.getItemId()) { case R.id.menu_add: //執行新增動作 break; case R.id.menu_remove: //執行刪除動作 break; case R.id.menu_edit: //執行編輯動作 break; case R.id.menu_settings: //執行設定動作 break; default: break; } } }
在使用者按下裝置上的MENU鍵後(或者Activity在載入時顯示了Action Bar),就會呼叫onCreateOptionsMenu()方法來構建選單。有一個名為MenuInflater的特殊LayoutInflater物件,可以用來根據XML建立選單。這裡我們使用Activity中已有的getMenuInflater()方法獲得MenuInflater例項來建立XML選單。
如果需要在使用者每次開啟選單時傳遞一些動作,可以在onPrepareOptionsMenu()中完成。這裡有一個建議,就是所有變成位於ActionBar中的動作在使用者選擇它們時將不會觸發這個回撥方法,但在溢位選單中的動作還會觸發該方法。
使用者做了選擇後,onOptionsItemSelected()回撥方法將會觸發同時傳入選擇的選單條目。因為我們在XML中為每個條目都定義了唯一的ID,所以可以通過switch語句判斷使用者的選擇項並進行相應的操作。
最後,在onCreateOptionsMenu()中有一些用於可展開動作檢視的額外設定。在此獲得指向包含動作檢視佈局的選單項的引用,並且附加一個onActionExpandListener回撥。此外使用回撥只是為了在條目摺疊時清除動作檢視中的已選元素。
要點:
如果提供onActionExpandListener,就需要在onMenuItemActionExpand()內返回true,否則永遠不會展開!
可以使用MenuItem中的getActionView()方法獲得一個引用,該引用指向在選單XML中設定的已填充動作佈局。在我們的例項中,使用此方法對佈局內的每個CheckBox設定選擇的偵聽器。在動作檢視內同時選擇這兩個條目時,呼叫collapseActionView()將檢視轉變回單個動作項的圖示。
下圖顯示了不同版本和配置的裝置上這個選單的樣子。依然帶有物理按鍵的裝置會在Action Bar上顯示提示的動作,但溢位選單還是通過MENU鍵觸發的。帶有軟鍵的裝置會挨著Action Bar動作顯示一個溢位選單按鈕。

帶有物理按鍵的Android裝置

帶有軟鍵的Android裝置
下圖顯示了展開的動作檢視,單擊Action Bar中的Add動作時就會顯示該檢視。

帶有Add動作的Android裝置

自定義動作檢視