本篇文章主要內(nèi)容來自于Android Doc,我翻譯之后又做了些加工,英文好的朋友也可以直接去讀原文。
http://developer.android.com/guide/topics/ui/actionbar.html
Action Bar是一種新増的導(dǎo)航欄功能,在Android 3.0之后加入到系統(tǒng)的API當中,它標識了用戶當前操作界面的位置,并提供了額外的用戶動作、界面導(dǎo)航等功能。使用ActionBar的好處是,它可以給提供一種全局統(tǒng)一的UI界面,使得用戶在使用任何一款軟件時都懂得該如何操作,并且ActionBar還可以自動適應(yīng)各種不同大小的屏幕。下面是一張使用ActionBar的界面截圖:
其中,[1]是ActionBar的圖標,[2]是兩個action按鈕,[3]是overflow按鈕。
由于Action Bar是在3.0以后的版本中加入的,如果想在2.x的版本里使用ActionBar的話則需要引入Support Library,不過3.0之前版本的市場占有率已經(jīng)非常小了,這里簡單起見我們就不再考慮去做向下兼容,而是只考慮4.0以上版本的用法。
添加和移除Action Bar
ActionBar的添加非常簡單,只需要在AndroidManifest.xml中指定Application或Activity的theme是Theme.Holo或其子類就可以了,而使用Eclipse創(chuàng)建的項目自動就會將Application的theme指定成Theme.Holo,所以ActionBar默認都是顯示出來的。新建一個空項目并運行,效果如下圖所示:
而如果想要移除ActionBar的話通常有兩種方式,一是將theme指定成Theme.Holo.NoActionBar,表示使用一個不包含ActionBar的主題,二是在Activity中調(diào)用以下方法:
[java] view plaincopy
ActionBar?actionBar?=?getActionBar();??actionBar.hide();??
現(xiàn)在重新運行一下程序,就可以看到ActionBar不再顯示了,如下圖所示:
修改Action Bar的圖標和標題
默認情況下,系統(tǒng)會使用<application>或者<activity>中icon屬性指定的圖片來作為ActionBar的圖標,但是我們也可以改變這一默認行為。如果我們想要使用另外一張圖片來作為ActionBar的圖標,可以在<application>或者<activity>中通過logo屬性來進行指定。比如項目的res/drawable目錄下有一張weather.png圖片,就可以在AndroidManifest.xml中這樣指定:
[html] view plaincopy
<activity??????android:name="com.example.actionbartest.MainActivity"??????android:logo="@drawable/weather"?>??</activity>?? 現(xiàn)在重新運行一下程序,效果如下圖所示:
OK,ActionBar的圖標已經(jīng)修改成功了,那么標題中的內(nèi)容該怎樣修改呢?其實也很簡單,使用label屬性來指定一個字符串就可以了,如下所示:
[html] view plaincopy
<activity??????android:name="com.example.actionbartest.MainActivity"??????android:label="天氣"??????android:logo="@drawable/weather"?>??</activity>??
現(xiàn)在重新運行一下程序,結(jié)果如下圖所示:
添加Action按鈕
ActionBar還可以根據(jù)應(yīng)用程序當前的功能來提供與其相關(guān)的Action按鈕,這些按鈕都會以圖標或文字的形式直接顯示在ActionBar上。當然,如果按鈕過多,ActionBar上顯示不完,多出的一些按鈕可以隱藏在overflow里面(最右邊的三個點就是overflow按鈕),點擊一下overflow按鈕就可以看到全部的Action按鈕了。
當Activity啟動的時候,系統(tǒng)會調(diào)用Activity的onCreateOptionsMenu()方法來取出所有的Action按鈕,我們只需要在這個方法中去加載一個menu資源,并把所有的Action按鈕都定義在資源文件里面就可以了。
那么我們先來看下menu資源文件該如何定義,代碼如下所示:
[html] view plaincopy
<menu?xmlns:android="http://schemas.android.com/apk/res/android"??????xmlns:tools="http://schemas.android.com/tools"??????tools:context="com.example.actionbartest.MainActivity"?>????????<item??????????android:id="@+id/action_compose"??????????android:icon="@drawable/ic_action_compose"??????????android:showAsAction="always"??????????android:title="@string/action_compose"/>??????<item??????????android:id="@+id/action_delete"??????????android:icon="@drawable/ic_action_delete"??????????android:showAsAction="always"??????????android:title="@string/action_delete"/>??????<item??????????android:id="@+id/action_settings"??????????android:icon="@drawable/ic_launcher"??????????android:showAsAction="never"??????????android:title="@string/action_settings"/>????</menu>?? 可以看到,這里我們通過三個<item>標簽定義了三個Action按鈕。<item>標簽中又有一些屬性,其中id是該Action按鈕的唯一標識符,icon用于指定該按鈕的圖標,title用于指定該按鈕可能顯示的文字(在圖標能顯示的情況下,通常不會顯示文字),showAsAction則指定了該按鈕顯示的位置,主要有以下幾種值可選:always表示永遠顯示在ActionBar中,如果屏幕空間不夠則無法顯示,ifRoom表示屏幕空間夠的情況下顯示在ActionBar中,不夠的話就顯示在overflow中,never則表示永遠顯示在overflow中。
接著,重寫Activity的onCreateOptionsMenu()方法,代碼如下所示:
[java] view plaincopy
@Override??public?boolean?onCreateOptionsMenu(Menu?menu)?{??????MenuInflater?inflater?=?getMenuInflater();??????inflater.inflate(R.menu.main,?menu);??????return?super.onCreateOptionsMenu(menu);??}?? 這部分代碼很簡單,僅僅是調(diào)用了MenuInflater的inflate()方法來加載menu資源就可以了。現(xiàn)在重新運行一下程序,結(jié)果如下圖所示:
可以看到,action_compose和action_delete這兩個按鈕已經(jīng)在ActionBar中顯示出來了,而action_settings這個按鈕由于showAsAction屬性設(shè)置成了never,所以被隱藏到了overflow當中,只要點擊一下overflow按鈕就可以看到它了。
這里我們注意到,顯示在ActionBar上的按鈕都只有一個圖標而已,我們在title中指定的文字并沒有顯示出來。沒錯,title中的內(nèi)容通常情況下只會在overflow中顯示出來,ActionBar中由于屏幕空間有限,默認是不會顯示title內(nèi)容的。但是出于以下幾種因素考慮,即使title中的內(nèi)容無法顯示出來,我們也應(yīng)該給每個item中都指定一個title屬性:
- 當ActionBar中的剩余空間不足的時候,如果Action按鈕指定的showAsAction屬性是ifRoom的話,該Action按鈕就會出現(xiàn)在overflow當中,此時就只有title能夠顯示了。
- 如果Action按鈕在ActionBar中顯示,用戶可能通過長按該Action按鈕的方式來查看到title的內(nèi)容。
響應(yīng)Action按鈕的點擊事件
當用戶點擊Action按鈕的時候,系統(tǒng)會調(diào)用Activity的onOptionsItemSelected()方法,通過方法傳入的MenuItem參數(shù),我們可以調(diào)用它的getItemId()方法和menu資源中的id進行比較,從而辨別出用戶點擊的是哪一個Action按鈕,比如:
[java] view plaincopy
@Override??public?boolean?onOptionsItemSelected(MenuItem?item)?{??????switch?(item.getItemId())?{??????case?R.id.action_compose:??????????Toast.makeText(this,?"Compose",?Toast.LENGTH_SHORT).show();??????????return?true;??????case?R.id.action_delete:??????????Toast.makeText(this,?"Delete",?Toast.LENGTH_SHORT).show();??????????return?true;??????case?R.id.action_settings:??????????Toast.makeText(this,?"Settings",?Toast.LENGTH_SHORT).show();??????????return?true;??????default:??????????return?super.onOptionsItemSelected(item);??????}??}?? 可以看到,我們讓每個Action按鈕被點擊的時候都彈出一個Toast,現(xiàn)在重新運行一下代碼,結(jié)果如下圖所示:
通過Action Bar圖標進行導(dǎo)航
啟用ActionBar圖標導(dǎo)航的功能,可以允許用戶根據(jù)當前應(yīng)用的位置來在不同界面之間切換。比如,A界面展示了一個列表,點擊某一項之后進入了B界面,這時B界面就應(yīng)該啟用ActionBar圖標導(dǎo)航功能,這樣就可以回到A界面。
我們可以通過調(diào)用setDisplayHomeAsUpEnabled()方法來啟用ActionBar圖標導(dǎo)航功能,比如:
[java] view plaincopy
@Override??protected?void?onCreate(Bundle?savedInstanceState)?{??????super.onCreate(savedInstanceState);??????setTitle("天氣");??????setContentView(R.layout.activity_main);??????ActionBar?actionBar?=?getActionBar();??????actionBar.setDisplayHomeAsUpEnabled(true);??}?? 現(xiàn)在重新運行一下程序,結(jié)果如下圖所示:
可以看到,在ActionBar圖標的左側(cè)出現(xiàn)了一個向左的箭頭,通常情況下這都表示返回的意思,因此最簡單的實現(xiàn)就是在它的點擊事件里面加入finish()方法就可以了,如下所示:
[java] view plaincopy
@Override??public?boolean?onOptionsItemSelected(MenuItem?item)?{??????switch?(item.getItemId())?{??????case?android.R.id.home:??????????finish();??????????return?true;??????……??????}??}?? 當點擊ActionBar圖標的時候,系統(tǒng)同樣會調(diào)用onOptionsItemSelected()方法,并且此時的itemId是android.R.id.home,所以finish()方法也就是加在這里的了。
現(xiàn)在看上去,ActionBar導(dǎo)航和Back鍵的功能貌似是一樣的。沒錯,如果我們只是簡單地finish了一下,ActionBar導(dǎo)航和Back鍵的功能是完全一樣的,但ActionBar導(dǎo)航的設(shè)計初衷并不是這樣的,它和Back鍵的功能還是有一些區(qū)別的,舉個例子吧。
上圖中的Conversation List是收件箱的主界面,現(xiàn)在我們點擊第一封郵件會進入到Conversation1 details界面,然后點擊下一封郵件會進入到Conversation 2 details界面,再點擊下一封郵箱會進入到Conversation3 details界面。好的,這個時候如果我們按下Back鍵,應(yīng)該會回到Conversation 2 details界面,再按一次Back鍵應(yīng)該回到Conversation1 details界面,再按一次Back鍵才會回到Conversation List。而ActionBar導(dǎo)航則不應(yīng)該表現(xiàn)出這種行為,無論我們當前在哪一個Conversation details界面,點擊一下導(dǎo)航按鈕都應(yīng)該回到Conversation List界面才對。
這就是ActionBar導(dǎo)航和Back鍵在設(shè)計上的區(qū)別,那么該怎樣才能實現(xiàn)這樣的功能呢?其實并不復(fù)雜,實現(xiàn)標準的ActionBar導(dǎo)航功能只需三步走。
第一步我們已經(jīng)實現(xiàn)了,就是調(diào)用setDisplayHomeAsUpEnabled()方法,并傳入true。
第二步需要在AndroidManifest.xml中配置父Activity,如下所示:
[html] view plaincopy
<activity??????android:name="com.example.actionbartest.MainActivity"??????android:logo="@drawable/weather"?>??????<meta-data??????????android:name="android.support.PARENT_ACTIVITY"??????????android:value="com.example.actionbartest.LaunchActivity"?/>??</activity>?? 可以看到,這里通過meta-data標簽指定了MainActivity的父Activity是LaunchActivity,在Android 4.1版本之后,也可以直接使用android:parentActivityName這個屬性來進行指定,如下所示:
[html] view plaincopy
<activity??????android:name="com.example.actionbartest.MainActivity"??????android:logo="@drawable/weather"??????android:parentActivityName="com.example.actionbartest.LaunchActivity"?>??</activity>?? 第三步則需要對android.R.id.home這個事件進行一些特殊處理,如下所示:
[java] view plaincopy
@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;??????????......??????}??}?? 其中,調(diào)用NavUtils.getParentActivityIntent()方法可以獲取到跳轉(zhuǎn)至父Activity的Intent,然后如果父Activity和當前Activity是在同一個Task中的,則直接調(diào)用navigateUpTo()方法進行跳轉(zhuǎn),如果不是在同一個Task中的,則需要借助TaskStackBuilder來創(chuàng)建一個新的Task。
這樣,就按照標準的規(guī)范成功實現(xiàn)ActionBar導(dǎo)航的功能了。
添加Action View
ActionView是一種可以在ActionBar中替換Action按鈕的控件,它可以允許用戶在不切換界面的情況下通過ActionBar完成一些較為豐富的操作。比如說,你需要完成一個搜索功能,就可以將SeachView這個控件添加到ActionBar中。
為了聲明一個ActionView,我們可以在menu資源中通過actionViewClass屬性來指定一個控件,例如可以使用如下方式添加SearchView:
[html] view plaincopy
<menu?xmlns:android="http://schemas.android.com/apk/res/android"?>????????<item??????????android:id="@+id/action_search"??????????android:icon="@drawable/ic_action_search"??????????android:actionViewClass="android.widget.SearchView"??????????android:showAsAction="ifRoom|collapseActionView"??????????android:title="@string/action_search"?/>??????......????</menu>?? 注意在showAsAction屬性中我們還聲明了一個collapseActionView,這個值表示該控件可以被合并成一個Action按鈕。
現(xiàn)在重新運行一下程序,效果如下圖所示:
OK,果然有一個搜索樣式的Action按鈕出現(xiàn)了,現(xiàn)在點擊一下這個搜索按鈕,效果如下圖所示:
可以看到,這時SearchView就會展開占滿整個ActionBar,而其它的Action按鈕由于將showAsAction屬性設(shè)置成了ifRoom,此時都會隱藏到overflow當中。
如果你還希望在代碼中對SearchView的屬性進行配置(比如添加監(jiān)聽事件等),完全沒有問題,只需要在onCreateOptionsMenu()方法中獲取該ActionView的實例就可以了,代碼如下所示:
[java] view plaincopy
@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();????????????......??????return?super.onCreateOptionsMenu(menu);??}?? 在得到了SearchView的實例之后,就可以任意地配置它的各種屬性了。關(guān)于SearchView的更多詳細用法,可以參考官方文檔?http://developer.android.com/guide/topics/search/search-dialog.html?。
除此之外,有些程序可能還希望在ActionView展開和合并的時候顯示不同的界面,其實我們只需要去注冊一個ActionView的監(jiān)聽器就能實現(xiàn)這樣的功能了,代碼如下所示:
[java] view plaincopy
@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);??}?? 可以看到,調(diào)用MenuItem的setOnActionExpandListener()方法就可以注冊一個監(jiān)聽器了,當SearchView展開的時候就會回調(diào)onMenuItemActionExpand()方法,當SearchView合并的時候就會調(diào)用onMenuItemActionCollapse()方法,我們在這兩個方法中進行相應(yīng)的UI操作就可以了。
Overflow按鈕不顯示的情況
雖然現(xiàn)在我們已經(jīng)掌握了不少ActionBar的用法,但是當你真正去使用它的時候還是可能會遇到各種各樣的問題,比如很多人都會碰到overflow按鈕不顯示的情況。明明是同樣的一份代碼,overflow按鈕在有些手機上會顯示,而在有些手機上偏偏就不顯示,這是為什么呢?后來我總結(jié)了一下,overflow按鈕的顯示情況和手機的硬件情況是有關(guān)系的,如果手機沒有物理Menu鍵的話,overflow按鈕就可以顯示,如果有物理Menu鍵的話,overflow按鈕就不會顯示出來。比如我們啟動一個有Menu鍵的模擬器,然后將代碼運行到該模擬器上,結(jié)果如下圖所示:
可以看到,ActionBar最右邊的overflow按鈕不見了!那么此時我們?nèi)绾尾榭措[藏在overflow中的Action按鈕呢?其實非常簡單,按一下Menu鍵,隱藏的內(nèi)容就會從底部出來了,如下圖所示:
看到這里相信不少朋友都想吐槽一下了,這顯然是一種非常蛋疼的設(shè)計,在不同手機上竟然顯示了不同的界面,而且操作方法也完全不一樣,這樣會給用戶一種非常不習(xí)慣的感覺。話說Google為什么要把ActionBar的overflow設(shè)計成這樣我也不太理解,但是我們還是有辦法改變這一默認行為的。
實際上,在ViewConfiguration這個類中有一個叫做sHasPermanentMenuKey的靜態(tài)變量,系統(tǒng)就是根據(jù)這個變量的值來判斷手機有沒有物理Menu鍵的。當然這是一個內(nèi)部變量,我們無法直接訪問它,但是可以通過反射的方式修改它的值,讓它永遠為false就可以了,代碼如下所示:
[java] view plaincopy
@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()方法的最后調(diào)用了setOverflowShowingAlways()方法,而這個方法的內(nèi)部就是使用反射的方式將sHasPermanentMenuKey的值設(shè)置成false,現(xiàn)在重新運行一下代碼,結(jié)果如下圖所示:
可以看到,即使是在有Menu鍵的手機上,也能讓overflow按鈕顯示出來了,這樣就可以大大增加我們軟件界面和操作的統(tǒng)一性。
讓Overflow中的選項顯示圖標
如果你點擊一下overflow按鈕去查看隱藏的Action按鈕,你會發(fā)現(xiàn)這部分Action按鈕都是只顯示文字不顯示圖標的,如下圖所示:
這是官方的默認效果,Google認為隱藏在overflow中的Action按鈕都應(yīng)該只顯示文字。當然,如果你認為這樣不夠美觀,希望在overflow中的Action按鈕也可以顯示圖標,我們?nèi)匀豢梢韵朕k法來改變這一默認行為。
其實,overflow中的Action按鈕應(yīng)不應(yīng)該顯示圖標,是由MenuBuilder這個類的setOptionalIconsVisible方法來決定的,如果我們在overflow被展開的時候給這個方法傳入true,那么里面的每一個Action按鈕對應(yīng)的圖標就都會顯示出來了。調(diào)用的方法當然仍然是用反射了,代碼如下所示:
[java] view plaincopy
@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被展開的時候就會回調(diào)這個方法,接著在這個方法的內(nèi)部通過返回反射的方法將MenuBuilder的setOptionalIconsVisible變量設(shè)置為true就可以了。
現(xiàn)在重新運行一下代碼,結(jié)果如下圖所示:
好了,目前為止我們已經(jīng)把ActionBar的基礎(chǔ)知識介紹完了,那么今天的講解就到這里,下篇文章中我會帶領(lǐng)大家一起更深入地了解ActionBar,感興趣的朋友請繼續(xù)閱讀?
Android ActionBar完全解析,使用官方推薦的最佳導(dǎo)航欄(下)。
第一時間獲得博客更新提醒,以及更多技術(shù)信息分享,歡迎關(guān)注我的微信公眾號,掃一掃下方二維碼或搜索微信號guolin_blog,即可關(guān)注。
總結(jié)
以上是生活随笔為你收集整理的Android ActionBar完全解析,使用官方推荐的最佳导航栏(上)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。