菜單:Menu
作為 Android 用戶,你一定見過類似這樣的頁面:
它就是我們今天的主角——菜單,它的使用場景和作用不用多說,幾乎每個 App 都會用到它,今天我們就一起來看看 Android 提供的幾種菜單類型及用法。
1. 菜單的幾種類型
根據(jù)不同的業(yè)務(wù)場景和不同的樣式,Android 提供了以下 3 種菜單:
- Option Menu: 選項(xiàng)菜單
- Context Menu: 上下文菜單
- Pop-up Menu: 彈窗菜單
下面來分別介紹這 3 種菜單類型
1.1 Option Menu
選項(xiàng)文菜單是最常用的 Menu,可以直接通過 Android 的“菜單鍵”喚出,通常直接為當(dāng)前 Activity 服務(wù)。在高版本的 Android 系統(tǒng)上是從右上角彈出,可以在里面放置一些常用的功能入口或者設(shè)置項(xiàng)等等高頻選項(xiàng)。
1.2 Context Menu
上下文菜單需要綁定在一個控件之上,當(dāng)我們長按這個控件的時候就會出現(xiàn)一個懸浮窗式的菜單,通常用于設(shè)置某個控件的屬性或內(nèi)容。
1.3 Popup Menu
從字面上看和上一節(jié)學(xué)的 PopupWindow 很像,沒錯,它的樣式確實(shí)和 PopupWindow 是一樣一樣的。同時它也需要綁定到一個 View 上面,然后會以一個豎直列表的形式彈出一個懸浮窗,非常適合對 View 進(jìn)行設(shè)置或者提供一些相關(guān)的附加選項(xiàng)。
注意: Context Menu 和 Popup Menu 都需要和一個 View 綁定,但 Popup Menu 里面的選項(xiàng)點(diǎn)擊不應(yīng)該直接影響到 View的內(nèi)容,否則應(yīng)該使用 Context Menu,Popup Menu 更多的是用于 View 相關(guān)操作的擴(kuò)展。
2. Menu 資源的創(chuàng)建方法
對于以上提到的 3 種類型的菜單,你都可以通過 Java 代碼或者 XML 資源文件兩種方式創(chuàng)建,但大多數(shù)情況下我強(qiáng)烈推薦使用XML 的形式。用 XML 可以對菜單結(jié)構(gòu)一目了然,并且和邏輯代碼物理隔離,更有利于我們維護(hù)。在編寫完 XML 菜單資源之后,在 Java 代碼中直接 inflate 加載資源文件即可。
創(chuàng)建菜單資源需要以下步驟:
- 右鍵點(diǎn)擊“res”目錄,依次選擇:new -> Android resource directory ,如下:
- 在彈出的窗口中輸入“menu”并選擇 Resource Type 為“menu”,點(diǎn)擊 OK:
- 右鍵點(diǎn)擊“menu”文件夾,依次選擇“New -> Menu resource file”,在 menu 目錄新增一個名為“menu.xml”的菜單資源:
創(chuàng)建完成之后,就可以開始編寫 menu.xml 文件了,一個菜單資源文件通常包含以下標(biāo)簽:
-
menu:
必選標(biāo)簽。用來定義一個菜單,菜單內(nèi)所有的選項(xiàng)(item)都需要寫在<menu/>
標(biāo)簽內(nèi),同時它也是整個 menu 資源文件的根節(jié)點(diǎn)。 -
item:
必選標(biāo)簽。用來創(chuàng)建一個菜單項(xiàng),每一個<item/>
標(biāo)簽代表 menu 中的一個選項(xiàng),另外在<item/>
中我們還可以嵌套定義<menu/>
節(jié)點(diǎn),以此來創(chuàng)建一個子菜單。 -
group:
可選標(biāo)簽。用來將多個<item/>
標(biāo)簽做分組,它用來對菜單里的選項(xiàng)進(jìn)行分類,這樣同類型的選項(xiàng)可以共享一些屬性,增強(qiáng)選項(xiàng)類別。
在了解了菜單資源標(biāo)簽之后,我們就可以簡單編寫一個菜單資源了,代碼非常簡單如下:
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/main_menu"
android:title="我要學(xué)習(xí)客戶端開發(fā)"
android:icon="@drawable/ic_launcher" >
<!-- 添加客戶端子菜單 -->
<menu>
<item android:id="@+id/submenu1"
android:title="學(xué)習(xí) Android"
android:icon="@drawable/ic_launcher"/>
<item android:id="@+id/submenu2"
android:title="學(xué)習(xí) iOS"
android:icon="@drawable/apple" />
</menu>
</item>
</menu>
其中<item/>
標(biāo)簽支持幾種屬性來配置樣式或者行為,常用的屬性比較好理解,主要有以下 2 種:
- android:id:
菜單項(xiàng)的資源 ID,用來唯一標(biāo)識某個選項(xiàng),后續(xù)可以通過 ID 來判斷用戶點(diǎn)擊的是哪個菜單項(xiàng)。 - android:icon:
設(shè)置菜單項(xiàng)對應(yīng)的圖標(biāo) - android:title:
設(shè)置菜單項(xiàng)的內(nèi)容
3. 幾種菜單的使用
在第 2 小節(jié)我們已經(jīng)通過 XML 的形式完成了菜單內(nèi)容的設(shè)置,接著需要在 Activity 中編寫邏輯并加載菜單資源,以下就根據(jù)不同的類型分別演示如何完成菜單的加載及使用。
3.1 Option Menu 示例
3.1.1 加載 Option Menu 資源
為了使用 Option Menu,我們需要在 Activity 中復(fù)寫onCreateOptionsMenu()
方法:
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.menu_file, menu);
return true;
}
當(dāng) Activity 創(chuàng)建 Option Menu 的時候系統(tǒng)會回調(diào)此函數(shù),我們只需要在里面 inflate 我們的菜單資源即可,其中getMenuInflater()
用來獲取一個“MenuInflater”對象,我們可以用它來加載一個 menu 資源文件——menu.xml。
3.1.2 處理菜單項(xiàng)的點(diǎn)擊事件
當(dāng)用戶在菜單中點(diǎn)擊了某個選項(xiàng)之后,Android 系統(tǒng)會回調(diào)onOptionsItemSelected()
方法,并傳入被選菜單項(xiàng)的 Menu 實(shí)例。我們可以通過 Menu 實(shí)例的getItemId()
方法拿到菜單項(xiàng)對應(yīng)的唯一 ID(通過<item/>
標(biāo)簽的 android:id 屬性設(shè)置的),從而判斷用戶選擇的是哪一項(xiàng),進(jìn)而執(zhí)行相應(yīng)的邏輯,代碼如下:
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// 根據(jù)點(diǎn)擊的選項(xiàng)處理不同的邏輯
switch (item.getItemId()) {
case R.id.menu:
// 點(diǎn)擊主菜單
return true;
case R.id.submenu1:
// 點(diǎn)擊子菜單1
return true;
case R.id.submenu2:
// 點(diǎn)擊子菜單2
return true;
default:
return super.onOptionsItemSelected(item);
}
}
**注意:**在你成功處理了菜單項(xiàng)的點(diǎn)擊事件之后(我們通常稱之為消費(fèi)),你需要在函數(shù)的末尾返回“true”,如果沒有消費(fèi)那么可以返回false,不過建議調(diào)用
super.onOptionsItemSelected(item)
將本次點(diǎn)擊事件交給上層處理(上層的默認(rèn)實(shí)現(xiàn)也是false)。
3.2 Context Menu 示例
3.2.1 加載 ContextMenu
加載一個 ContextMenu 通常需要以下步驟:
- 調(diào)用
registerForContextMenu()
傳入一個 View,來為該 View 注冊一個Context Menu,從此該 View 就和一個 OptionMenu 綁定; - 在 Activity 中復(fù)寫
onCreateContextMenu()
方法,當(dāng)用戶長按你注冊過的 View,Android 系統(tǒng)就會回調(diào)此方法,我們可以在這里進(jìn)行 menu 資源的加載。
其實(shí)邏輯和 Option Menu 類似,但是因?yàn)樾枰壎?View 所以多了一個注冊操作,加載代碼如下:
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.menu_file, menu);
}
onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo)
方法需要傳入 3 個參數(shù),分別是:
- ContextMenu menu: 菜單對象,類似 OptionMenu 里面的 Menu 對象
- View v: 與 Context Menu 綁定的 View 對象
- ContextMenuInfo menuInfo: 包含與被選項(xiàng)的一些附加信息
注意: 如果當(dāng)前 Activity 有多個 View 都有 Context Menu,那么需要通過這幾個參數(shù)來判斷當(dāng)前觸發(fā)的是哪個 View 相關(guān)的 Context Menu
3.2.2 處理 Context Menu 選項(xiàng)的點(diǎn)擊事件
當(dāng)用戶點(diǎn)擊上下文菜單項(xiàng)的時候,系統(tǒng)會回調(diào)onContextItemSelected()
方法,所以我們可以在方法里實(shí)現(xiàn)相應(yīng)的處理邏輯。如下:
@Override
public boolean onContextItemSelected(MenuItem item) {
// 處理 Context Menu 選項(xiàng)的點(diǎn)擊事件
}
}
3.3 Popup Menu 示例
3.3.1 展示 Popup Menu
和 Context Menu 類似,Popup Menu 也需要和一個 View 綁定,但二者的加載過程有些不同。加載一個 Popup Menu 需要經(jīng)過 3 個步驟:
- 調(diào)用 PopupMenu 的構(gòu)造器,傳入當(dāng)前 Application 的上下文對象,待綁定的 View;
- 調(diào)用
getMenuInflater()
獲取 MenuInflater對象,通過它將菜單資源裝載入 PopupMenu 的 Menu 實(shí)例中; - 調(diào)用 PopupMenu 對象的
show()
彈出菜單。
加載代碼如下:
PopupMenu popupMenu = new PopupMenu(this,view);
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.menu,popupMenu .getMenu());
popup.show();
3.3.2 監(jiān)聽 Popup Menu 的點(diǎn)擊事件
為了監(jiān)聽 Popup Menu 的點(diǎn)擊事件,我們需要在 Activity 中實(shí)現(xiàn)PopupMenu.OnMenuItemClickListener
接口并通過setOnMenuItemclickListener()
方法注冊 Popup Menu。這樣一來,當(dāng)用戶點(diǎn)擊菜單項(xiàng)的時候,Android 系統(tǒng)會回調(diào) Activity 的onMenuItemClick()
方法,在當(dāng)中處理點(diǎn)擊事件即可。
4. 完整示例代碼
通過以上針對每個類型 Menu 的講解,大家對菜單的創(chuàng)建和使用應(yīng)該都比較清楚了,下面我們通過一個完整的示例來演示一下 3 種菜單的使用。
4.1 編寫 menu 資源
在第 2 小節(jié)中我們詳細(xì)介紹了 menu 資源,它包括<menu/>
、<item/>
、<group/>
三種標(biāo)簽,為了演示方便我們直接采用第 2 小節(jié)中的菜單資源。
4.2 編寫布局
菜單本身并不涉及到布局的編寫,我們只需要兩個 View,一個綁定給 Context Menu,一個給 Popup Menu:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<TextView
android:id="@+id/tv_context"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingBottom="30dp"
android:text="我這里有 Context Menu"
android:textSize="20sp" />
<Button
android:id="@+id/bt_popup"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="pop"
android:text="我這里有 Popup Menu" />
</LinearLayout>
4.3 編寫 Activity
最后就可以編寫 Activity 了,其中要做的就是為 Menu 做資源加載,并接收點(diǎn)擊回調(diào)即可:
package com.emercy.myapplication;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.PopupMenu;
import android.view.ContextMenu;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.Toast;
public class MainActivity extends AppCompatActivityimplements PopupMenu.OnMenuItemClickListener {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 為 TextView 注冊 Context Menu
registerForContextMenu(findViewById(R.id.tv_context));
}
// 加載 Option Menu
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater menuInflater = getMenuInflater();
menuInflater.inflate(R.menu.menu, menu);
return true;
}
// 接收 Option Menu 的點(diǎn)擊
@Override
public boolean onOptionsItemSelected(MenuItem item) {
return onItemClick(item);
}
// 加載 Context Menu
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
MenuInflater menuInflater = getMenuInflater();
menuInflater.inflate(R.menu.menu, menu);
}
// 接收 Context Menu 的點(diǎn)擊
@Override
public boolean onContextItemSelected(MenuItem item) {
return onItemClick(item);
}
// 加載 Popup Menu
public void pop(View v){
PopupMenu popup = new PopupMenu(this, v);
MenuInflater menuInflater = getMenuInflater();
menuInflater.inflate(R.menu.menu, popup.getMenu());
popup.show();
}
// 接收 Popup Menu 的點(diǎn)擊
@Override
public boolean onMenuItemClick(MenuItem item) {
return onItemClick(item);
}
private boolean onItemClick(MenuItem item) {
switch (item.getItemId()) {
case R.id.main_menu:
Toast.makeText(this, "選擇了客戶端開發(fā)", Toast.LENGTH_SHORT).show();
break;
case R.id.submenu1:
Toast.makeText(this, "選擇學(xué)習(xí) Android", Toast.LENGTH_SHORT).show();
break;
case R.id.submenu2:
Toast.makeText(this, "選擇學(xué)習(xí) iOS", Toast.LENGTH_SHORT).show();
break;
}
return true;
}
}
針對每個菜單都分別有“加載資源”和“處理點(diǎn)擊”兩種操作,另外由于每個菜單的處理邏輯都一樣,為了增強(qiáng)代碼復(fù)用性我單獨(dú)拎出了一個函數(shù)onItemClick()
專門用于統(tǒng)一處理點(diǎn)擊事件。
最終樣式如下(在不同的設(shè)備上可能會有所不同):
- Option Menu 的子菜單:
- Context Menu 的子菜單:
- Popup Menu 的主菜單:
5. 小結(jié)
本節(jié)介紹了 Android 提供的幾種菜單:Option Menu 通常用來提供 Activity 相關(guān)的選項(xiàng),Context Menu 通常用來針對某個 View 進(jìn)行設(shè)置,而 Popup Menu 用來設(shè)置某個 View 的屬性或者展示一些附加功能。使用的步驟大體相同,在一個完整 App 的開發(fā)中,Menu 是必不可少的部分,希望大家能夠很好的掌握本節(jié)內(nèi)容。