Android Floating Context Menu的使用方法

来源:互联网 发布:苏亚星多媒体教学软件 编辑:程序博客网 时间:2024/05/22 06:39

1. 概述

Android Context Menu有两种,即floating context menu和contextual action mode下的context menu。本文讨论前者,后者是在Android 3.0开始引入的。


2. 主要思路

要使用floating context menu,包括如下几个主要步骤(适用于Activity和Fragment):

  • 对于要关联context menu的view,调用registerForContextMenu(View)进行注册;
  • 重载onCreateContextMenu(),其目的在于创建具体的context menu;
  • 重载onContextItemSelected(),其目的在于,选择了某一个context menu item的时候,进行适当的处理。


3. 示例

这里是以为ListView创建一个context menu为例,其中ListView部分的介绍,请参考“ListView的例子”。

为了保持文章或示例的独立性,我们把相关的代码全部贴出来。

3.1 应用的布局文件

<?xml version="1.0" encoding="utf-8"?><!-- hello_context_menu.xml --><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:orientation="vertical" >    <ListView        android:id="@+id/list_view_id"        android:layout_width="match_parent"        android:layout_height="wrap_content" >    </ListView></LinearLayout>

3.2 ListView中每一个元素的 布局文件

<!-- student_item.xml --><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:orientation="horizontal" >    <TextView        android:id="@+id/student_name"        android:layout_width="0dp"        android:layout_height="wrap_content"        android:layout_weight="2" />    <TextView        android:id="@+id/student_score"        android:layout_width="0dp"        android:layout_height="wrap_content"        android:layout_weight="1" /></LinearLayout>

3.3 上下文菜单的资源文件

<?xml version="1.0" encoding="utf-8"?><!-- context_menu.xml --><menu xmlns:android="http://schemas.android.com/apk/res/android" >    <item        android:id="@+id/first_context_menu_item"        android:title="@string/first_context_menu_item"/>    <item        android:id="@+id/second_context_menu_item"        android:title="@string/second_context_menu_item"/></menu>

3.4 字符串资源

<string name="first_context_menu_item">first item</string><string name="second_context_menu_item">second item</string>

3.5 Java代码

package com.example.hellocontextmenu;import java.util.ArrayList;import java.util.HashMap;import android.app.Activity;import android.os.Bundle;import android.util.Log;import android.view.ContextMenu;import android.view.MenuInflater;import android.view.MenuItem;import android.view.View;import android.view.ContextMenu.ContextMenuInfo;import android.widget.ListView;import android.widget.SimpleAdapter;public class MainActivity extends Activity {    private static final String TAG = "MainActivity";@Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.hello_context_menu);        ListView listView = (ListView) this.findViewById(R.id.list_view_id);        setData(listView);                this.registerForContextMenu(listView);            }    private void setData(ListView listView) {          // each item's layout resource id          int student_item_id = R.layout.student_item;            // columns names          String[] columnNames = new String[] { "name", "score" };            // resource ids          int[] ids = new int[] { R.id.student_name, R.id.student_score };            // the data to be displayed          ArrayList<HashMap<String, Object>> students = new ArrayList<HashMap<String, Object>>();          HashMap<String, Object> map = null;          for (int i = 1; i <= 10; i++) {              map = new HashMap<String, Object>();              map.put(columnNames[0], "student-" + i);              map.put(columnNames[1], String.valueOf(i * 10));              students.add(map);          }                    listView.setAdapter(new SimpleAdapter(this, students, student_item_id,                  columnNames, ids));        }      @Override    public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {    super.onCreateContextMenu(menu, v, menuInfo);        MenuInflater inflater = this.getMenuInflater();    inflater.inflate(R.menu.context_menu, menu);    }                @Override    public boolean onContextItemSelected(MenuItem item) {    switch (item.getItemId()) {    case R.id.first_context_menu_item:    Log.d(TAG, "first context menu item selected");    return true;        case R.id.second_context_menu_item:    Log.d(TAG, "second context menu item selected");    return true;    default:return super.onContextItemSelected(item);    }        }}

3.6 运行效果



4. 关于ContextMenuInfo

在Android官网的Menu Guide中,提到ContextMenuInfo可用于标识当前所选择的view,以及view中的item,从而做一些特殊的处理。原文如下:

 The callback method parameters include the View that the user selected and a ContextMenu.ContextMenuInfo object that provides additional information about the item selected. If your activity has several views that each provide a different context menu, you might use these parameters to determine which context menu to inflate.

本节就来分析这个ContextMenuInfo,并通过一些例子进行说明。


4.1 ContextMenuInfo接口

先大概了解这个对象,查看ContextMenuInfo这个class,发现有个toString()方法。增加调试打印:

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

运行结果如下:

android.widget.AdapterView$AdapterContextMenuInfo@41d217b8

实际上,这个toString()并不是ContextMenuInfo重载的,而是缺省的继承Object的该方法。ContextMenuInfo本身是一个接口:

    /**     * Additional information regarding the creation of the context menu.  For example,     * {@link AdapterView}s use this to pass the exact item position within the adapter     * that initiated the context menu.     */    public interface ContextMenuInfo {    }

4.2 AdapterView

本例使用的是ListView,而它是(间接)继承自AdapterView。在AdapterView中,定义了ContextMenuInfo的一个实现,即AdapterContextMenuInfo:

    /**     * Extra menu information provided to the     * {@link android.view.View.OnCreateContextMenuListener#onCreateContextMenu(ContextMenu, View, ContextMenuInfo) }     * callback when a context menu is brought up for this AdapterView.     *     */    public static class AdapterContextMenuInfo implements ContextMenu.ContextMenuInfo {        public AdapterContextMenuInfo(View targetView, int position, long id) {            this.targetView = targetView;            this.position = position;            this.id = id;        }        /**         * The child view for which the context menu is being displayed. This         * will be one of the children of this AdapterView.         */        public View targetView;        /**         * The position in the adapter for which the context menu is being         * displayed.         */        public int position;        /**         * The row id of the item for which the context menu is being displayed.         */        public long id;    }

这里的targetView、position、id就是典型的各种适配器中的概念。由此,我们就可以确定前面提到的onCreateContextMenu()及onContextItemSelected()中menuInfo对象的详细数据了。


4.3 分析ContextMenuInfo对象

根据前面的分析,我们可以根据所选择的ListView中的具体数据,来加载不同的context menu,——对应onCreateContextMenu()。还可以在onContextItemSelected()中对具体的选中对象做特殊的处理。下面的就是Android官网Menu Guide中的示例代码:

@Overridepublic boolean onContextItemSelected(MenuItem item) {    AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();    switch (item.getItemId()) {        case R.id.edit:            editNote(info.id);            return true;        case R.id.delete:            deleteNote(info.id);            return true;        default:            return super.onContextItemSelected(item);    }}


5. ContextMenuInfo的使用示例

我们接下来在之前的代码增加一些处理:根据选择的不同的对象,inflater不同的context menu:共3种,除了之前的一种之外,另增加了更新学生分数的两个上下文菜单。

5.1 增加学生分数的menu资源

<?xml version="1.0" encoding="utf-8"?><!-- add_context_menu.xml --><menu xmlns:android="http://schemas.android.com/apk/res/android" >        <item        android:id="@+id/add_score"        android:title="@string/add_score"/></menu>

5.2 减少学生分数的menu资源

<?xml version="1.0" encoding="utf-8"?><!-- minus_context_menu.xml --><menu xmlns:android="http://schemas.android.com/apk/res/android" >    <item        android:id="@+id/minus_score"        android:title="@string/minus_score"/></menu>

5.3 新增的字符串资源

<string name="add_score">Add Score</string><string name="minus_score">Minus Score</string>

5.4 Java代码

package com.example.hellocontextmenu;import java.util.ArrayList;import java.util.HashMap;import android.app.Activity;import android.os.Bundle;import android.util.Log;import android.view.ContextMenu;import android.view.MenuInflater;import android.view.MenuItem;import android.view.View;import android.view.ContextMenu.ContextMenuInfo;import android.widget.AdapterView.AdapterContextMenuInfo;import android.widget.BaseAdapter;import android.widget.ListView;import android.widget.SimpleAdapter;import android.widget.Toast;public class MainActivity extends Activity {    private static final String TAG = "MainActivity";    private ListView listView = null;    private BaseAdapter adapter = null;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.hello_context_menu);        listView = (ListView) this.findViewById(R.id.list_view_id);        setData();                this.registerForContextMenu(listView);            }    private void setData() {          // each item's layout resource id          int student_item_id = R.layout.student_item;            // columns names          String[] columnNames = new String[] { "name", "score" };            // resource ids          int[] ids = new int[] { R.id.student_name, R.id.student_score };            // the data to be displayed          ArrayList<HashMap<String, Object>> students = new ArrayList<HashMap<String, Object>>();          HashMap<String, Object> map = null;          for (int i = 1; i <= 10; i++) {              map = new HashMap<String, Object>();              map.put(columnNames[0], "student-" + i);              map.put(columnNames[1], String.valueOf(i * 10));              students.add(map);        }                adapter = new SimpleAdapter(this, students, student_item_id,                  columnNames, ids);        listView.setAdapter(adapter);     }      @Override    public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {    super.onCreateContextMenu(menu, v, menuInfo);        AdapterContextMenuInfo info = (AdapterContextMenuInfo)menuInfo;    dumpInfo(info);        MenuInflater inflater = this.getMenuInflater();        if (info.position > 8) {    inflater.inflate(R.menu.context_menu, menu);    } else {    if (info.position % 2 == 0) {    inflater.inflate(R.menu.add_context_menu, menu);    } else {    inflater.inflate(R.menu.minus_context_menu, menu);    }    }    }        private void dumpInfo(AdapterContextMenuInfo info) {    HashMap<String, Object> student = (HashMap<String, Object>) adapter.getItem(info.position);        Toast.makeText(this, student.toString(),Toast.LENGTH_LONG).show();}    private void updateScore(ContextMenuInfo menuInfo, int incremental) {    AdapterContextMenuInfo info = (AdapterContextMenuInfo)menuInfo;HashMap<String, Object> student = (HashMap<String, Object>) adapter.getItem(info.position);Log.d(TAG, "old: " + student.toString());String studentName = (String) student.get("name");String studentScore = (String) student.get("score");int score = Integer.parseInt(studentScore);student.put("score", String.valueOf(score + incremental));adapter.notifyDataSetChanged();Log.d(TAG, "new: " + student.toString());}    @Override    public boolean onContextItemSelected(MenuItem item) {    switch (item.getItemId()) {    case R.id.first_context_menu_item:    Log.d(TAG, "first context menu item selected");    return true;        case R.id.second_context_menu_item:    Log.d(TAG, "second context menu item selected");    return true;        case R.id.add_score:    updateScore(item.getMenuInfo(), 1);    return true;        case R.id.minus_score:    updateScore(item.getMenuInfo(), -1);    return true;    default:return super.onContextItemSelected(item);    }        }}

5.5 运行效果

logcat日志:

09-07 20:17:29.835: D/MainActivity(13101): old: {score=20, name=student-2}09-07 20:17:29.835: D/MainActivity(13101): new: {score=19, name=student-2}09-07 20:17:40.745: D/MainActivity(13101): old: {score=40, name=student-4}09-07 20:17:40.745: D/MainActivity(13101): new: {score=39, name=student-4}09-07 20:18:02.755: D/MainActivity(13101): second context menu item selected09-07 20:18:11.265: D/MainActivity(13101): old: {score=10, name=student-1}09-07 20:18:11.265: D/MainActivity(13101): new: {score=11, name=student-1}

上下文菜单的截图:




6. 多个View的情况

现在前面代码的基础上,增加一个View。此时在onCreateContextMenu()中,通过传入的View参数来区分不同的View对象,从而加载不同的context menu。

6.1 布局文件

增加一个Button:

<?xml version="1.0" encoding="utf-8"?><!-- hello_context_menu.xml --><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:orientation="vertical" >    <Button        android:id="@+id/button_id"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:text="@string/button_text"/>            <ListView        android:id="@+id/list_view_id"        android:layout_width="match_parent"        android:layout_height="wrap_content" >    </ListView></LinearLayout>

6.2 字符串资源

<string name="button_text">Try to Click Me</string>

6.3 Java代码

主要修改两个地方:

  • 为Button注册上下文菜单;
  • 创建上下文菜单的时候,要区分不同的view,做差异化处理

package com.example.hellocontextmenu;import java.util.ArrayList;import java.util.HashMap;import android.app.Activity;import android.os.Bundle;import android.util.Log;import android.view.ContextMenu;import android.view.MenuInflater;import android.view.MenuItem;import android.view.View;import android.view.ContextMenu.ContextMenuInfo;import android.widget.AdapterView.AdapterContextMenuInfo;import android.widget.BaseAdapter;import android.widget.Button;import android.widget.ListView;import android.widget.SimpleAdapter;import android.widget.Toast;public class MainActivity extends Activity {    private static final String TAG = "MainActivity";    private ListView listView = null;    private BaseAdapter adapter = null;    private Button button = null;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.hello_context_menu);        listView = (ListView) this.findViewById(R.id.list_view_id);        setData();                button = (Button) this.findViewById(R.id.button_id);        this.registerForContextMenu(button);                this.registerForContextMenu(listView);                Log.d(TAG, "onCreate()");    }    private void setData() {          // each item's layout resource id          int student_item_id = R.layout.student_item;            // columns names          String[] columnNames = new String[] { "name", "score" };            // resource ids          int[] ids = new int[] { R.id.student_name, R.id.student_score };            // the data to be displayed          ArrayList<HashMap<String, Object>> students = new ArrayList<HashMap<String, Object>>();          HashMap<String, Object> map = null;          for (int i = 1; i <= 10; i++) {              map = new HashMap<String, Object>();              map.put(columnNames[0], "student-" + i);              map.put(columnNames[1], String.valueOf(i * 10));              students.add(map);        }                adapter = new SimpleAdapter(this, students, student_item_id,                  columnNames, ids);        listView.setAdapter(adapter);     }      @Override    public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {    super.onCreateContextMenu(menu, v, menuInfo);        MenuInflater inflater = this.getMenuInflater();        if (v == button) {    inflater.inflate(R.menu.context_menu, menu);    return;    }        // Now the view is the ListView object    AdapterContextMenuInfo info = (AdapterContextMenuInfo)menuInfo;    dumpInfo(info);    if (info.position % 2 == 0) {inflater.inflate(R.menu.add_context_menu, menu);} else {inflater.inflate(R.menu.minus_context_menu, menu);}}        private void dumpInfo(AdapterContextMenuInfo info) {    HashMap<String, Object> student = (HashMap<String, Object>) adapter.getItem(info.position);        Toast.makeText(this, student.toString(),Toast.LENGTH_LONG).show();}    private void updateScore(ContextMenuInfo menuInfo, int incremental) {    AdapterContextMenuInfo info = (AdapterContextMenuInfo)menuInfo;HashMap<String, Object> student = (HashMap<String, Object>) adapter.getItem(info.position);Log.d(TAG, "old: " + student.toString());String studentName = (String) student.get("name");String studentScore = (String) student.get("score");int score = Integer.parseInt(studentScore);student.put("score", String.valueOf(score + incremental));adapter.notifyDataSetChanged();Log.d(TAG, "new: " + student.toString());}    @Override    public boolean onContextItemSelected(MenuItem item) {    switch (item.getItemId()) {    case R.id.first_context_menu_item:    Log.d(TAG, "first context menu item selected");    return true;        case R.id.second_context_menu_item:    Log.d(TAG, "second context menu item selected");    return true;        case R.id.add_score:    updateScore(item.getMenuInfo(), 1);    return true;        case R.id.minus_score:    updateScore(item.getMenuInfo(), -1);    return true;    default:return super.onContextItemSelected(item);    }        }}

6.4 使用习惯

以上我们为Button注册上下文菜单,仅仅是一种示例,用于说明如何区分不同的View对象。

虽然可以给各种View注册上下文菜单,但从用户使用习惯上来讲,我们通常只会为特定的一些类别注册这种行为。比如给编辑框注册,长按之后可以Select/SelectAll/Copy/Paste;另外更常见的是ListView, GridView等。


7. 小结

从本文示例来看,floating context menu使用起来还是很方便的。当然,其功能还是有一定的局限性,为此Android3.0引入了contextual action mode的概念。

0 0
原创粉丝点击