ZetCode

Android 菜单

最后修改于 2012年11月26日

在本章 Android 开发教程中,我们将学习菜单。菜单用于组织应用程序可用的命令。在传统的桌面应用程序中,菜单是菜单栏的一部分,通常位于应用程序的顶部区域。上下文菜单或弹出菜单是菜单的特殊情况。

Android 中有三种类型的菜单:选项菜单、上下文菜单和弹出菜单。选项菜单是 activity 的主要菜单项集合。在选项菜单中,我们应该包含对应用程序有全局影响的命令。例如,“设置”菜单。它显示在 activity 的顶部或底部。上下文菜单在特定上下文中显示菜单项。例如,用于 ListView 项目。当用户长按某个元素时显示。弹出菜单以垂直列表的形式显示项目列表,该列表固定在调用菜单的视图上。如果空间允许,它会显示在锚定视图的下方,否则显示在视图的上方。它应该与 activity 中的内容区域相关联。

菜单可以通过手动编码创建,也可以在 XML 文件中定义。如果我们在 XML 文件中定义菜单,我们会使用 MenuInflater 对象从 XML 文件创建菜单。

选项菜单

我们的选项菜单将包含两个菜单项。当我们选择一个菜单项时,会显示一个 Toast 窗口,其中包含所选菜单项的名称。选项菜单在我们单击菜单按钮后显示。

在此示例中,清单文件未被修改。

Menu button
图:菜单按钮

main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
    
  <TextView
      android:layout_width="fill_parent"
      android:layout_height="wrap_content"
      android:text="@string/message" />
    
</LinearLayout>

main.xml 布局文件中,我们有一个 TextView 小部件。它将显示一个欢迎消息。

strings.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="app_name">OptionsMenu</string>
    <string name="message">Demonstrating Options Menu</string>
    <string name="om1">Settings</string>
    <string name="om2">Tools</string>
</resources>

这是 strings.xml 文件。

options_menu.xml
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
  <item android:id="@+id/settings"
    android:title="@string/om1" />
  <item android:id="@+id/tools"
    android:title="@string/om2" />
</menu>

这是 options_menu.xml 文件。它定义了两个菜单项。该文件位于 res/menu/ 子目录中。

MainActivity.java
package com.zetcode.opmenu;

import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.widget.Toast;

public class MainActivity extends Activity
{
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) 
    {
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.options_menu, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) 
    {
        switch (item.getItemId()) 
        {
            case R.id.settings:
                Toast.makeText(MainActivity.this, "Settings menu selected.", 
                    Toast.LENGTH_SHORT).show();
                return true;

            case R.id.tools:
                Toast.makeText(MainActivity.this, "Tools menu selected.", 
                    Toast.LENGTH_SHORT).show();
                return true;

            default:
                return super.onOptionsItemSelected(item);
        }
    }
}

要在 activity 中启用选项菜单,我们需要覆盖 onCreateOptionsMenu()onOptionsItemSelected() 方法。

@Override
public boolean onCreateOptionsMenu(Menu menu) 
{
    MenuInflater inflater = getMenuInflater();
    inflater.inflate(R.menu.options_menu, menu);
    return true;
}

onCreateOptionsMenu() 方法中,我们从 options_menu.xml 文件构建选项菜单。我们使用 MenuInflater 类来完成此工作。

@Override
public boolean onOptionsItemSelected(MenuItem item) 
{
...
}

onOptionsItemSelected() 方法处理菜单项上的单击事件。

case R.id.settings:
    Toast.makeText(MainActivity.this, "Settings menu selected.", 
        Toast.LENGTH_SHORT).show();
    return true;

在选择“设置”菜单项的情况下,我们会显示一个 Toast 窗口,上面写着“已选择设置菜单”。

Options menu at the bottom of the activity
图:activity 底部的选项菜单

上下文菜单

我们有一个 ListView,其中包含我们行星的名称。长按某个项目将显示一个上下文菜单,其中包含三个选项:删除、大写和 [小写]。

清单文件未被修改。

main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
    
    <ListView 
      android:id="@+id/lvId"
      android:layout_width="fill_parent"   
      android:layout_height="fill_parent" />  
      
</LinearLayout>

这是 main.xml 文件。它包含一个 ListView 小部件。

strings.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="app_name">ContextMenu</string>
    <string-array name="planets">
        <item>Mercury</item>
        <item>Venus</item>
        <item>Earth</item>
        <item>Mars</item>
        <item>Jupiter</item>
        <item>Saturn</item>
        <item>Uranus</item>
        <item>Neptune</item>
        <item>Pluto</item>
    </string-array>     
</resources>

这是 strings.xml 资源文件。

row.xml
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:padding="10dp"
    android:textSize="20sp">
</TextView>

这是 row.xml 资源文件。ListView 的每一行都包含一个单独的 TextView

MainActivity.java
package com.zetcode.conmenu;

import android.app.Activity;
import android.os.Bundle;
import android.widget.ArrayAdapter;  
import android.widget.ListView;  
import android.view.View;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.widget.AdapterView.AdapterContextMenuInfo;

import java.util.Arrays;
import java.util.ArrayList;


public class MainActivity extends Activity
{
    private ListView lv;  
    private ArrayAdapter<String> la; 

    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        setupUI();
    }

    public void setupUI()
    { 
        lv = (ListView) findViewById(R.id.lvId);  
        String[] planets = getResources().getStringArray(R.array.planets); 
        ArrayList<String> lst = new ArrayList<String>();
        lst.addAll(Arrays.asList(planets));

        la = new ArrayAdapter<String>(this, R.layout.row, lst);
        lv.setAdapter(la);
        registerForContextMenu(lv);
    }

    @Override
    public void onCreateContextMenu(ContextMenu menu, View v, 
        ContextMenuInfo menuInfo) 
    {
        super.onCreateContextMenu(menu, v, menuInfo);
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.context_menu, menu);
    }

    @Override
    public boolean onContextItemSelected(MenuItem item) 
    {
        AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
        
        int pos = info.position;
        String i = la.getItem(pos);

        switch (item.getItemId()) 
        {
            case R.id.delId:
                la.remove(i);
                return true;

            case R.id.upId:               
                String upln = i.toUpperCase();
                la.remove(i);
                la.insert(upln, pos); 
                return true;

            case R.id.loId:
                String lpln = i.toLowerCase();
                la.remove(i);
                la.insert(lpln, pos);          
                return true;

            default:
                return super.onContextItemSelected(item);
        }
    }
}

为了实现上下文菜单,我们必须覆盖 onCreateContextMenu()onContextItemSelected() 方法。我们还需要为特定视图调用 registerForContextMenu() 方法。

String[] planets = getResources().getStringArray(R.array.planets); 
ArrayList<String> lst = new ArrayList<String>();
lst.addAll(Arrays.asList(planets));

我们将删除 ListView 中的项目。因此,我们需要使用 ArrayList。否则列表将是只读的。

registerForContextMenu(lv);

上下文菜单已为 ListView 小部件注册。

@Override
public void onCreateContextMenu(ContextMenu menu, View v, 
    ContextMenuInfo menuInfo) 
{
    super.onCreateContextMenu(menu, v, menuInfo);
    MenuInflater inflater = getMenuInflater();
    inflater.inflate(R.menu.context_menu, menu);
}

onCreateContextMenu() 方法中,我们从 context_menu.xml 文件构建上下文菜单。

@Override
public boolean onContextItemSelected(MenuItem item) 
{
...
}

onContextItemSelected() 对列表项选择事件做出反应。

AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();

int pos = info.position;
String i = la.getItem(pos);

为了了解所选项目的详细信息,我们使用 AdapterContextMenuInfo 类。我们可以获取所选项目的索引和文本。

case R.id.delId:
    la.remove(i);
    return true;

如果我们选择“删除”上下文菜单选项,我们会从 ArrayAdapter 中删除该项目。

case R.id.upId:               
    String upln = i.toUpperCase();
    la.remove(i);
    la.insert(upln, pos); 
    return true;

对于“大写”选项,我们修改字符串,删除原始字符串,然后插入新字符串。

Context menu with three options
图:带有三个选项的上下文菜单

弹出菜单

该示例显示了单击按钮后的 PopupMenu

清单文件未被修改。

main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
    
  <Button
      android:id="@+id/btnId"
      android:layout_height="wrap_content"
      android:layout_width="wrap_content"
      android:layout_marginTop="10dip"
      android:text="@string/btn_label"
      android:onClick="onClick" />
      
  <TextView
      android:id="@+id/tvId"
      android:layout_width="fill_parent"
      android:layout_height="wrap_content" 
      android:layout_marginTop="10dip" />
      
</LinearLayout>

这是 main.xml 文件。我们有一个 Button 小部件和一个 TextView 小部件。按钮将显示一个 PopupMenu

strings.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="app_name">PopupMenu</string>
    <string name="btn_label">Show menu</string>
    <string name="pm1">Item 1</string>
    <string name="pm2">Item 2</string>
</resources>

这是 strings.xml 资源文件。

popup_menu.xml
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
  <item android:id="@+id/settings"
    android:title="@string/pm1" />
  <item android:id="@+id/tools"
    android:title="@string/pm2" />
</menu>

这是 popup_menu.xml 文件。它定义了两个菜单项。该文件位于 res/menu/ 子目录中。

MainActivity.java
package com.zetcode.popmenu;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.MenuItem;
import android.widget.TextView;
import android.widget.PopupMenu;
import android.widget.PopupMenu.OnMenuItemClickListener;

public class MainActivity extends Activity
    implements OnMenuItemClickListener
{   
    private TextView tv;

    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        tv = (TextView) findViewById(R.id.tvId);        
    }

   public void onClick(View v) 
   {   
       showPopupMenu(v);
   }

   public void showPopupMenu(View v)
   {
       PopupMenu pm = new PopupMenu(MainActivity.this, v);
       pm.getMenuInflater().inflate(R.menu.popup_menu, pm.getMenu()); 
       pm.setOnMenuItemClickListener(this);          
       pm.show();
   }

   @Override
   public boolean onMenuItemClick(MenuItem item) 
   {           
      tv.setText(item.toString() + " selected");
      return true;  
   }
}

单击按钮小部件后,会显示一个 PopupMenu

public void onClick(View v) 
{   
    showPopupMenu(v);
}

此方法是按钮单击的回调。通过 main.xml 文件中的属性设置了此关联。该方法调用 showPopupMenu() 方法。

public void showPopupMenu(View v)
{
    PopupMenu pm = new PopupMenu(MainActivity.this, v);
    pm.getMenuInflater().inflate(R.menu.popup_menu, pm.getMenu()); 
    pm.setOnMenuItemClickListener(this);          
    pm.show();
}

我们创建 PopupMenu 类的实例。它构建菜单、设置 OnMenuItemClickListener 并显示 PopupMenu

@Override
public boolean onMenuItemClick(MenuItem item) 
{           
    tv.setText(item.toString() + " selected");
    return true;  
}

选择菜单项后,会调用 onMenuItemClick() 方法。它将项目的标题设置为 TextView 小部件。

在本章 Android 开发教程中,我们学习了菜单。