您的位置:首页 > 移动开发 > Android开发

Android Floating Context Menu的使用方法

2014-09-07 18:50 393 查看

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中的示例代码:

@Override
public 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 selected
09-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的概念。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: