Android ActionBar徹底解析(上)

  1. }  

這部分代碼很簡單,僅僅是調用了MenuInflater的inflate()方法來加載menu資源就能夠了。如今從新運行一下程序,結果以下圖所示:html

能夠看到,action_compose和action_delete這兩個按鈕已經在ActionBar中顯示出來了,而action_settings這個按鈕因爲showAsAction屬性設置成了never,因此被隱藏到了overflow當中,只要點擊一下overflow按鈕就能夠看到它了。java

這裏咱們注意到,顯示在ActionBar上的按鈕都只有一個圖標而已,咱們在title中指定的文字並無顯示出來。沒錯,title中的內容一般狀況下只會在overflow中顯示出來,ActionBar中因爲屏幕空間有限,默認是不會顯示title內容的。可是出於如下幾種因素考慮,即便title中的內容沒法顯示出來,咱們也應該給每一個item中都指定一個title屬性:android

  • 當ActionBar中的剩餘空間不足的時候,若是Action按鈕指定的showAsAction屬性是ifRoom的話,該Action按鈕就會出如今overflow當中,此時就只有title可以顯示了。
  • 若是Action按鈕在ActionBar中顯示,用戶可能經過長按該Action按鈕的方式來查看到title的內容。

下載Action按鈕圖標

Google爲咱們繪製好了不少Action按鈕,你能夠在這裏下載到:https://developer.android.com/design/downloads/index.html#action-bar-icon-pack

響應Action按鈕的點擊事件

當用戶點擊Action按鈕的時候,系統會調用Activity的onOptionsItemSelected()方法,經過方法傳入的MenuItem參數,咱們能夠調用它的getItemId()方法和menu資源中的id進行比較,從而辨別出用戶點擊的是哪個Action按鈕,好比:[java] view plaincopy在CODE上查看代碼片派生到個人代碼片ide

  1. @Override  
  2. public boolean onOptionsItemSelected(MenuItem item) {  
  3.     switch (item.getItemId()) {  
  4.     case R.id.action_compose:  
  5.         Toast.makeText(this, "Compose", Toast.LENGTH_SHORT).show();  
  6.         return true;  
  7.     case R.id.action_delete:  
  8.         Toast.makeText(this, "Delete", Toast.LENGTH_SHORT).show();  
  9.         return true;  
  10.     case R.id.action_settings:  
  11.         Toast.makeText(this, "Settings", Toast.LENGTH_SHORT).show();  
  12.         return true;  
  13.     default:  
  14.         return super.onOptionsItemSelected(item);  
  15.     }  
  16. }  

能夠看到,咱們讓每一個Action按鈕被點擊的時候都彈出一個Toast,如今從新運行一下代碼,結果以下圖所示:ui

 

經過Action Bar圖標進行導航

啓用ActionBar圖標導航的功能,能夠容許用戶根據當前應用的位置來在不一樣界面之間切換。好比,A界面展現了一個列表,點擊某一項以後進入了B界面,這時B界面就應該啓用ActionBar圖標導航功能,這樣就能夠回到A界面。this

咱們能夠經過調用setDisplayHomeAsUpEnabled()方法來啓用ActionBar圖標導航功能,好比:[java] view plaincopy在CODE上查看代碼片派生到個人代碼片spa

  1. @Override  
  2. protected void onCreate(Bundle savedInstanceState) {  
  3.     super.onCreate(savedInstanceState);  
  4.     setTitle("天氣");  
  5.     setContentView(R.layout.activity_main);  
  6.     ActionBar actionBar = getActionBar();  
  7.     actionBar.setDisplayHomeAsUpEnabled(true);  
  8. }  

如今從新運行一下程序,結果以下圖所示:.net

能夠看到,在ActionBar圖標的左側出現了一個向左的箭頭,一般狀況下這都表示返回的意思,所以最簡單的實現就是在它的點擊事件裏面加入finish()方法就能夠了,以下所示:[java] view plaincopy在CODE上查看代碼片派生到個人代碼片設計

  1. @Override  
  2. public boolean onOptionsItemSelected(MenuItem item) {  
  3.     switch (item.getItemId()) {  
  4.     case android.R.id.home:  
  5.         finish();  
  6.         return true;  
  7.     ……  
  8.     }  
  9. }  

當點擊ActionBar圖標的時候,系統一樣會調用onOptionsItemSelected()方法,而且此時的itemId是android.R.id.home,因此finish()方法也就是加在這裏的了。3d

如今看上去,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,以下所示:[html] view plaincopy在CODE上查看代碼片派生到個人代碼片

  1. <activity  
  2.     android:name="com.example.actionbartest.MainActivity"  
  3.     android:logo="@drawable/weather" >  
  4.     <meta-data  
  5.         android:name="android.support.PARENT_ACTIVITY"  
  6.         android:value="com.example.actionbartest.LaunchActivity" />  
  7. </activity>  

能夠看到,這裏經過meta-data標籤指定了MainActivity的父Activity是LaunchActivity,在Android 4.1版本以後,也能夠直接使用android:parentActivityName這個屬性來進行指定,以下所示:[html] view plaincopy在CODE上查看代碼片派生到個人代碼片

  1. <activity  
  2.     android:name="com.example.actionbartest.MainActivity"  
  3.     android:logo="@drawable/weather"  
  4.     android:parentActivityName="com.example.actionbartest.LaunchActivity" >  
  5. </activity>  

第三步則須要對android.R.id.home這個事件進行一些特殊處理,以下所示:[java] view plaincopy在CODE上查看代碼片派生到個人代碼片

  1. @Override  
  2. public boolean onOptionsItemSelected(MenuItem item) {  
  3.     switch (item.getItemId()) {  
  4.     case android.R.id.home:  
  5.         Intent upIntent = NavUtils.getParentActivityIntent(this);  
  6.         if (NavUtils.shouldUpRecreateTask(this, upIntent)) {  
  7.             TaskStackBuilder.create(this)  
  8.                     .addNextIntentWithParentStack(upIntent)  
  9.                     .startActivities();  
  10.         } else {  
  11.             upIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);  
  12.             NavUtils.navigateUpTo(this, upIntent);  
  13.         }  
  14.         return true;  
  15.         ......  
  16.     }  
  17. }  

其中,調用NavUtils.getParentActivityIntent()方法能夠獲取到跳轉至父Activity的Intent,而後若是父Activity和當前Activity是在同一個Task中的,則直接調用navigateUpTo()方法進行跳轉,若是不是在同一個Task中的,則須要藉助TaskStackBuilder來建立一個新的Task。

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

 

添加Action View

ActionView是一種能夠在ActionBar中替換Action按鈕的控件,它能夠容許用戶在不切換界面的狀況下經過ActionBar完成一些較爲豐富的操做。好比說,你須要完成一個搜索功能,就能夠將SeachView這個控件添加到ActionBar中。

爲了聲明一個ActionView,咱們能夠在menu資源中經過actionViewClass屬性來指定一個控件,例如可使用以下方式添加SearchView:[html] view plaincopy在CODE上查看代碼片派生到個人代碼片

  1. <menu xmlns:android="http://schemas.android.com/apk/res/android" >  
  2.   
  3.     <item  
  4.         android:id="@+id/action_search"  
  5.         android:icon="@drawable/ic_action_search"  
  6.         android:actionViewClass="android.widget.SearchView"  
  7.         android:showAsAction="ifRoom|collapseActionView"  
  8.         android:title="@string/action_search" />  
  9.     ......  
  10.   
  11. </menu>  

注意在showAsAction屬性中咱們還聲明瞭一個collapseActionView,這個值表示該控件能夠被合併成一個Action按鈕。

如今從新運行一下程序,效果以下圖所示:

OK,果真有一個搜索樣式的Action按鈕出現了,如今點擊一下這個搜索按鈕,效果以下圖所示:

能夠看到,這時SearchView就會展開佔滿整個ActionBar,而其它的Action按鈕因爲將showAsAction屬性設置成了ifRoom,此時都會隱藏到overflow當中。

若是你還但願在代碼中對SearchView的屬性進行配置(好比添加監聽事件等),徹底沒有問題,只須要在onCreateOptionsMenu()方法中獲取該ActionView的實例就能夠了,代碼以下所示:

 
  1. @Override

  2. public boolean onCreateOptionsMenu(Menu menu) {

  3. // Inflate the menu; this adds items to the action bar if it is present.

  4. getMenuInflater().inflate(R.menu.menu_main, menu);

  5. final MenuItem searchItem = menu.findItem(R.id.action_search);

  6. final SearchView searchView = (SearchView) searchItem.getActionView();

  7. // Configure the search info and add any event listeners

  8. if (searchView != null) {

  9. searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {

  10. @Override

  11. public boolean onQueryTextSubmit(String query) {

  12. Toast.makeText(MainActivity.this, query, Toast.LENGTH_SHORT).show();

  13. searchItem.collapseActionView();

  14. return true;

  15. }

  16.  
  17. @Override

  18. public boolean onQueryTextChange(String newText) {

  19. return false;

  20. }

  21. });

  22. }

  23. return true;

  24. }


 

在獲得了SearchView的實例以後,就能夠任意地配置它的各類屬性了。關於SearchView的更多詳細用法,能夠參考官方文檔 http://developer.android.com/guide/topics/search/search-dialog.html 。

除此以外,有些程序可能還但願在ActionView展開和合並的時候顯示不一樣的界面,其實咱們只須要去註冊一個ActionView的監聽器就能實現這樣的功能了,代碼以下所示:[java] view plaincopy在CODE上查看代碼片派生到個人代碼片

  1. @Override  
  2. public boolean onCreateOptionsMenu(Menu menu) {  
  3.     MenuInflater inflater = getMenuInflater();  
  4.     inflater.inflate(R.menu.main, menu);  
  5.     MenuItem searchItem = menu.findItem(R.id.action_search);  
  6.     searchItem.setOnActionExpandListener(new OnActionExpandListener() {  
  7.         @Override  
  8.         public boolean onMenuItemActionExpand(MenuItem item) {  
  9.             Log.d("TAG", "on expand");  
  10.             return true;  
  11.         }  
  12.           
  13.         @Override  
  14.         public boolean onMenuItemActionCollapse(MenuItem item) {  
  15.             Log.d("TAG", "on collapse");  
  16.             return true;  
  17.         }  
  18.     });  
  19.     return super.onCreateOptionsMenu(menu);  
  20. }  

能夠看到,調用MenuItem的setOnActionExpandListener()方法就能夠註冊一個監聽器了,當SearchView展開的時候就會回調onMenuItemActionExpand()方法,當SearchView合併的時候就會調用onMenuItemActionCollapse()方法,咱們在這兩個方法中進行相應的UI操做就能夠了。

 

Overflow按鈕不顯示的狀況

雖然如今咱們已經掌握了很多ActionBar的用法,可是當你真正去使用它的時候仍是可能會遇到各類各樣的問題,好比不少人都會碰到overflow按鈕不顯示的狀況。明明是一樣的一份代碼,overflow按鈕在有些手機上會顯示,而在有些手機上恰恰就不顯示,這是爲何呢?後來我總結了一下,overflow按鈕的顯示狀況和手機的硬件狀況是有關係的,若是手機沒有物理Menu鍵的話,overflow按鈕就能夠顯示,若是有物理Menu鍵的話,overflow按鈕就不會顯示出來。好比咱們啓動一個有Menu鍵的模擬器,而後將代碼運行到該模擬器上,結果以下圖所示:

能夠看到,ActionBar最右邊的overflow按鈕不見了!那麼此時咱們如何查看隱藏在overflow中的Action按鈕呢?其實很是簡單,按一下Menu鍵,隱藏的內容就會從底部出來了,以下圖所示:

看到這裏相信很多朋友都想吐槽一下了,這顯然是一種很是蛋疼的設計,在不一樣手機上居然顯示了不一樣的界面,並且操做方法也徹底不同,這樣會給用戶一種很是不習慣的感受。話說Google爲何要把ActionBar的overflow設計成這樣我也不太理解,可是咱們仍是有辦法改變這一默認行爲的。

實際上,在ViewConfiguration這個類中有一個叫作sHasPermanentMenuKey的靜態變量,系統就是根據這個變量的值來判斷手機有沒有物理Menu鍵的。固然這是一個內部變量,咱們沒法直接訪問它,可是能夠經過反射的方式修改它的值,讓它永遠爲false就能夠了,代碼以下所示:[java] view plaincopy在CODE上查看代碼片派生到個人代碼片

  1. @Override  
  2. protected void onCreate(Bundle savedInstanceState) {  
  3.     ......  
  4.     setOverflowShowingAlways();  
  5. }  
  6.   
  7. private void setOverflowShowingAlways() {  
  8.     try {  
  9.         ViewConfiguration config = ViewConfiguration.get(this);  
  10.         Field menuKeyField = ViewConfiguration.class.getDeclaredField("sHasPermanentMenuKey");  
  11.         menuKeyField.setAccessible(true);  
  12.         menuKeyField.setBoolean(config, false);  
  13.     } catch (Exception e) {  
  14.         e.printStackTrace();  
  15.     }  
  16. }  

這裏咱們在onCreate()方法的最後調用了setOverflowShowingAlways()方法,而這個方法的內部就是使用反射的方式將sHasPermanentMenuKey的值設置成false,如今從新運行一下代碼,結果以下圖所示:

能夠看到,即便是在有Menu鍵的手機上,也能讓overflow按鈕顯示出來了,這樣就能夠大大增長咱們軟件界面和操做的統一性。

 

讓Overflow中的選項顯示圖標

若是你點擊一下overflow按鈕去查看隱藏的Action按鈕,你會發現這部分Action按鈕都是隻顯示文字不顯示圖標的,以下圖所示:

這是官方的默認效果,Google認爲隱藏在overflow中的Action按鈕都應該只顯示文字。固然,若是你認爲這樣不夠美觀,但願在overflow中的Action按鈕也能夠顯示圖標,咱們仍然能夠想辦法來改變這一默認行爲。

其實,overflow中的Action按鈕應不該該顯示圖標,是由MenuBuilder這個類的setOptionalIconsVisible方法來決定的,若是咱們在overflow被展開的時候給這個方法傳入true,那麼裏面的每個Action按鈕對應的圖標就都會顯示出來了。調用的方法固然仍然是用反射了,代碼以下所示:[java] view plaincopy在CODE上查看代碼片派生到個人代碼片

  1. @Override  
  2. public boolean onMenuOpened(int featureId, Menu menu) {  
  3.     if (featureId == Window.FEATURE_ACTION_BAR && menu != null) {  
  4.         if (menu.getClass().getSimpleName().equals("MenuBuilder")) {  
  5.             try {  
  6.                 Method m = menu.getClass().getDeclaredMethod("setOptionalIconsVisible", Boolean.TYPE);  
  7.                 m.setAccessible(true);  
  8.                 m.invoke(menu, true);  
  9.             } catch (Exception e) {  
  10.             }  
  11.         }  
  12.     }  
  13.     return super.onMenuOpened(featureId, menu);  
  14. }  

能夠看到,這裏咱們重寫了一個onMenuOpened()方法,當overflow被展開的時候就會回調這個方法,接着在這個方法的內部經過返回反射的方法將MenuBuilder的setOptionalIconsVisible變量設置爲true就能夠了。

如今從新運行一下代碼,結果以下圖所示:

使用split action bar
即將除Up Button、圖標和標題以外的全部item移到底欄上

 
  1. <manifest ...>

  2. <activity uiOptions="splitActionBarWhenNarrow" ... >

  3. <meta-data

  4. android:name="android.support.UI_OPTIONS"

  5. android:value="splitActionBarWhenNarrow" />

  6. </activity>

  7. </manifest>


 

好了,目前爲止咱們已經把ActionBar的基礎知識介紹完了,那麼今天的講解就到這裏,下篇文章中我會帶領你們一塊兒更深刻地瞭解ActionBar,感興趣的朋友請繼續閱讀 Android ActionBar徹底解析(下)

相關文章
相關標籤/搜索