工具欄可以提供應(yīng)用導(dǎo)航、放置菜單選項,也可以統(tǒng)一風(fēng)格設(shè)計等,這里有個簡單的例子,如圖所示:

這里我們創(chuàng)建工具欄菜單,分以下幾點介紹:
- 在 XML 文件中定義菜單
- 讓菜單顯示出來
- 響應(yīng)菜單的選擇
- 子標(biāo)題的顯示
- 數(shù)據(jù)的同步
在 XML 文件中定義菜單
菜單及菜單選項需要用到一些字符串資源,我們首先添加字符串資源
values/strings.xml
<resources>
<string name="new_crime">New Crime</string>
<string name="show_subtitle">Show Subtitle</string>
<string name="hide_subtitle">Hide Subtitle</string>
<string name="subtitle_format">%1$d crimes</string>
</resources>
右鍵點擊 res 目錄, 選擇 New->Android resource file 菜單項。在彈出窗口界面,選擇 Menu 資源按鈕,命名資源文件為 fragment_crime_list,點擊 OK 確認(rèn)按鈕。

打開創(chuàng)建的 fragment_crime_list.xml 文件,添加以下代碼
res/menu/fragment_crime_list.xml
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/new_crime"
android:icon="@drawable/ic_menu_add"
android:title="@string/new_crime"
app:showAsAction="ifRoom|withText"/>
</menu>
showAsAction 屬性是指定菜單顯示在工具欄上還是溢出菜單,這里設(shè)置的是 ifRoom 和 withText 的組合值。因此,只要空間足夠,就會菜單項就會顯示圖標(biāo)及其文字。如果空間僅夠顯示菜單項圖標(biāo),文字描述就不會顯示。如果空間大小不夠顯示任何項,菜單就會溢出到溢出菜單中,以三個點為表示。showAsAction 屬性還有兩個可選值:always 和 never。不推薦用 always,盡量使用 ifRoom。對于很少用到的菜單,可選擇 never 屬性。
讓菜單顯示出來
在代碼中,Acrivity 類提供了管理菜單的回調(diào)函數(shù)。需要選項菜單時,Android 會調(diào)用 Activity 的 onCreateOptionsMenu(Menu) 函數(shù)。但是在本例中,與選項菜單相關(guān)的回調(diào)函數(shù)需在 fragment 而非 activity 里實現(xiàn)。不用擔(dān)心,fragment 有一套自己的處理機制。
實例化選項菜單
CrlmeListFragment.java
public class CrimeListFragment extends Fragment {
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.frigment_crime_list,menu);
}
}
在上面代碼中,我們調(diào)用 MenuInflater.inflate(int,Menu) 方法傳入菜單文件的資源 ID,將布局文件中定義的菜單項目填充到 Menu 實例中。
Fragment.onCreateOptionMenu(Menu,MenuInflater) 是由 FragmentManager 調(diào)用的。因此,當(dāng) activity 接收到操作系統(tǒng)的 onCreateOptionsMenu(...) 方法請求回調(diào)時,我們必須明確告訴 FragmentManager:其管理的 fragment 應(yīng)該接收 onCreateOptionsMenu(...) 方法調(diào)用指令。
需調(diào)用以下方法:
public void setHsaOptionsMenu(boolean hsaMenu)
CrimeListFragment.java
public class CrimeListFragment extends Fragment {
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
}
運行程序就能看到創(chuàng)建的菜單欄了

在豎屏模式下因為空間有限,默認(rèn)隱藏菜單項標(biāo)題,長按可以看到。在橫屏模式下就可以直接看到。

響應(yīng)菜單的選擇
為了響應(yīng)用戶的菜單項,需要向 Crime 列表中添加新的 Crime。還有刪除隨機生成的數(shù)據(jù)。
CrimeLab.java
public void addCrime(Crime c){
mCrimes.add(c);
}
private CrimeLab(Context context) {
mCrimes = new ArrayList<>();
/*for (int i = 0; i < 100; i++) {
Crime crime = new Crime();
crime.setTitle("Crime #" + i);
crime.setSolved(i % 2 == 0);
mCrimes.add(crime);
}*/
}
當(dāng)用戶點擊菜單中的菜單項時,fragment 會收到 onOptionsItemSelectde(MenuItem) 方法的回調(diào)請求。傳入該方法的是一個描述用戶選擇的 MenuItem 實例。
CrimeListFragment.java
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()){
case R.id.new_crime:
Crime c = new Crime();
CrimeLab.get(getActivity()).addCrime(c);
Intent intent = CrimePagerActivity.newIntent(getActivity(),c.getId());
startActivity(intent);
return true;
default:
return super.onOptionsItemSelected(item);
}
}
目前應(yīng)用主要是靠后退鍵導(dǎo)航,現(xiàn)在向應(yīng)用添加層級式導(dǎo)航。
AndroidManifest.xml
<activity
android:name=".CrimePagerActivity"
android:parentActivityName=".CrimeListActivity">
</activity>
現(xiàn)在運行就能看到向上按鈕了

雖然后退鍵導(dǎo)航和向上按鈕導(dǎo)航執(zhí)行的操作是一樣的,但是各自實現(xiàn)的機制大不相同。向上按鈕時,會在回退棧中尋找指定的 activity,如果實例存在,則彈出棧內(nèi)所有的其他 activity,讓啟動的目標(biāo) activity 出現(xiàn)在棧頂。
子標(biāo)題的顯示
這里我們添加一個菜單項來顯示或者隱藏 CrimelistActivity 工具欄的子標(biāo)題。先來添加視圖的代碼
res/menu/fragment_crime_list.xml
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/new_crime"
android:icon="@drawable/ic_menu_add"
android:title="@string/new_crime"
app:showAsAction="ifRoom|withText"/>
<item
android:id="@+id/show_subtitle"
android:title="@string/show_subtitle"
app:showAsAction="ifRoom"/>
</menu>
創(chuàng)建方法實現(xiàn)這個功能
CrimeListFragment.java
private void updateSubtitle(){
CrimeLab crimeLab = CrimeLab.get(getActivity());
int crimeCount = crimeLab.getCrimes().size();
String subtitle = getString(R.string.subtitle_format,crimeCount);
AppCompatActivity activity = (AppCompatActivity) getActivity();
activity.getSupportActionBar().setSubtitle(subtitle);
}
getString(...) 方法接收字符串資源中的占位符的替換值,然后在 onOptionsItemSelected (...) 點擊響應(yīng)此方法。
響應(yīng) SHOW SHBTITLE 的點擊
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()){
case R.id.new_crime:
Crime c = new Crime();
CrimeLab.get(getActivity()).addCrime(c);
Intent intent = CrimePagerActivity.newIntent(getActivity(),c.getId());
startActivity(intent);
return true;
case R.id.show_subtitle:
updateSubtitle();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
現(xiàn)在點擊 SHOW SHBTITLE 按鈕,就可以顯示出來了。但是菜單項標(biāo)題依然顯示為 SHOW SHBTITLE,顯然,菜單項標(biāo)題的切換與子標(biāo)題的顯示或隱藏需要聯(lián)動。
調(diào)用 onOptionsItemSelected(...) 方法時,可以更新 SHOW SUBTITLE 的文字,但是設(shè)備旋轉(zhuǎn)時,子標(biāo)題的變化就會丟失,比較好的解決方法是在 onCreateOptionsMenu(...) 方法內(nèi)更新 SHOW SUBTITLE 菜單項,并在用戶點擊的時候重建工具欄。
CrimeListFragment.java
public class CrimeListFragment extends Fragment {
private boolean mSubtitleVisible;
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.frigment_crime_list,menu);
MenuItem subtitleItem = menu.findItem(R.id.show_subtitle);
if (mSubtitleVisible){
subtitleItem.setTitle(R.string.hide_subtitle);
}else {
subtitleItem.setTitle(R.string.show_subtitle);
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()){
case R.id.new_crime:
Crime c = new Crime();
CrimeLab.get(getActivity()).addCrime(c);
Intent intent = CrimePagerActivity.newIntent(getActivity(),c.getId());
startActivity(intent);
return true;
case R.id.show_subtitle:
mSubtitleVisible = !mSubtitleVisible;
getActivity().invalidateOptionsMenu();
updateSubtitle();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
}
最后,根據(jù) mSubtitleVisible 變量值,聯(lián)動菜單項標(biāo)題與子標(biāo)題
CrimeListFragment.java
private void updateSubtitle(){
CrimeLab crimeLab = CrimeLab.get(getActivity());
int crimeCount = crimeLab.getCrimes().size();
String subtitle = getString(R.string.subtitle_format,crimeCount);
if (!mSubtitleVisible){
subtitle = null;
}
AppCompatActivity activity = (AppCompatActivity) getActivity();
activity.getSupportActionBar().setSubtitle(subtitle);
}
數(shù)據(jù)的同步
接下來解決數(shù)據(jù)同步的問題,新建 Crime 后,使用后退鍵回到 CrimeListActivity,總次數(shù)不會更新,在 onResume() 方法中調(diào)用 updateSubtitle() 就能解決這個問題。因為 onResume() 和 onCreatView() 都會調(diào)用 updateUI() 方法,那就在 updateUI() 中調(diào)用 updateSubtitle().
CrimeListFragment.java
private void updateUI() {
CrimeLab crimeLab = CrimeLab.get(getActivity());
List<Crime> crimes = crimeLab.getCrimes();
if (mAdapter==null){
mAdapter = new CrimeAdapter(crimes);
mCrimeRecyclerView.setAdapter(mAdapter);
}else {
mAdapter.notifyItemChanged(mPosition);
}
updateSubtitle();
}
但是點擊向上按鈕,子標(biāo)題被重置了,這又是什么情況呢?這是 Android 實現(xiàn)層級導(dǎo)航帶來的問題:導(dǎo)航回退到的目標(biāo) activity 會被完全重建。既然父 activity 是全新的。實例變量值以及保存的狀態(tài)顯示會徹底丟失。有兩種解決方案,在本例中,調(diào)用 CrimePagerActivity 的 finlsh() 方法直接回退到前一個 activity 的界面。但是這樣智能回退一個層級,而實際中絕大多數(shù)都需要多層級導(dǎo)航。第二種是在啟動 CrimePagerActivity 時,把子標(biāo)題狀態(tài)作為 extra 信息傳給它,然后在 CrimePagerActivity 中覆蓋 getParentActivityIntent() 方法,用附帶的 extra 信息的 intent 重建 CrimeListActivity。
還有一個問題,旋轉(zhuǎn)設(shè)備后,子標(biāo)題會消失,只要用實例狀態(tài)保存機制,就能解決問題。
CrimeListFragment.java
public class CrimeListFragment extends Fragment {
private static final String SAVED_SUBTITLE_VISIBLE = "subtitle";
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_crime_list, container, false);
mCrimeRecyclerView = (RecyclerView) view
.findViewById(R.id.crime_recycler_view);
mCrimeRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
if (savedInstanceState != null){
mSubtitleVisible = savedInstanceState.getBoolean(SAVED_SUBTITLE_VISIBLE);
}
updateUI();
return view;
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean(SAVED_SUBTITLE_VISIBLE,mSubtitleVisible);
}
}