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的概念。相关文章推荐
- Android中Menu的基本使用方法
- Android CircularFloatingActionMenu:作为系统级按钮悬浮桌面弹出菜单使用(3)
- AndroidAnnotations框架@Ebean,@RootContext,@Background,@UiThread,@AfterInject,@AfterTextChange标签的使用方法
- Android CircularFloatingActionMenu:作为系统级按钮悬浮桌面弹出菜单使用(3)
- Android开源组件SlidingMenu的基本使用方法和SlidingMenuDemo
- Android 中Context的使用方法详解
- Android使用onCreateOptionsMenu()创建菜单Menu的方法详解
- Android Contextual Menus之一:floating context menu
- Android CircularFloatingActionMenu在ScrollView这样的滚动View中使用(2)
- 之前账号的文章2:Context-menu.Android库的使用(修改显示位置)
- Android编程实现全局获取Context及使用Intent传递对象的方法详解
- Android CircularFloatingActionMenu:作为系统级按钮悬浮桌面弹出菜单使用(3)
- Context-Menu.Android库的使用(修改显示位置)
- Android FloatingActionButton使用方法及小技巧-design
- Android Context的startService方法如何使用?
- Android menu的使用方法
- Android编程 - Option Menu选项菜单&& Context Menu上下文菜单基本使用
- Android CircularFloatingActionMenu在ScrollView这样的滚动View中使用(2)
- Android ImageShow的使用方法(1)【转】
- Android中两种使用Animation的方法