Android中有各种各样的菜单,Android的菜单栏(也叫操作栏):ActionBar。先来看下Menu大汇总。
具体可看该网址:https://developer.android.com/guide/topics/ui/menus.html#xml
下面菜单Demo的github:https://github.com/LxxCaroline/SampleApplication/tree/master/menusample
普通菜单
- 在Android3.0(API级别11)以下版本,当用户按“菜单”按钮时,选项菜单的内容会出现在屏幕底部,如图 1 所示。打开时,第一个可见部分是图标菜单,其中包含多达 6 个菜单项。 如果菜单包括 6 个以上项目,则 Android 会将第六项和其余项目放入溢出菜单。用户可以通过选择“更多”打开该菜单。
- 在Android3.0及更高版本的系统中,选项菜单中的项目将出现在操作栏中。默认情况下,系统会将所有项目均放入操作溢出菜单中。用户可以使用操作栏右侧的操作溢出菜单图标(或者,通过按设备“菜单”按钮(如有))显示操作溢出菜单。 要支持快速访问重要操作,您可以将android:showAsAction=”ifRoom” 添加到对应的
<item>
元素,从而将几个项目提升到操作栏中。从 Android 3.0 开始,“菜单”按钮已弃用(某些设备没有该按钮),因此您应改为使用操作栏,来提供对操作和其他选项的访问。
对于上面两种形式的菜单,示例代码如下:
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.game_menu, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle item selection
switch (item.getItemId()) {
case R.id.new_game:
newGame();
return true;
case R.id.help:
showHelp();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
关于这种菜单的其他API,具体请自行上网查找。
上下文菜单
基于上面的菜单,Android出了新的菜单,也就是上下文菜单,先看下上下文菜单的效果图。
提供上下文操作的方法有两种:
- 使用浮动上下文菜单。用户长按(按住)一个声明支持上下文菜单的视图时,菜单显示为菜单项的浮动列表(类似于对话框)。 用户一次可对一个项目执行上下文操作。(图中左边的菜单)
- 使用上下文操作模式。此模式是 ActionMode 的系统实现,它将在屏幕顶部显示上下文操作栏,其中包括影响所选项的操作项目。当此模式处于活动状态时,用户可以同时对多项执行操作(如果应用允许)。(图中右边的菜单)
注:上下文操作模式可用于 Android 3.0(API 级别 11)及更高版本的系统,是显示上下文操作(如果可用)的首选方法。如果应用支持低于 3.0 版本的系统,则应在这些设备上回退到浮动上下文菜单。
创建浮动上下文菜单:
要提供浮动上下文菜单,请执行以下操作:
通过调用 registerForContextMenu(),注册应与上下文菜单关联的 View 并将其传递给 View。如果 Activity 使用 ListView 或 GridView 且您希望每个项目均提供相同的上下文菜单,请通过将 ListView 或 GridView 传递给registerForContextMenu(),为上下文菜单注册所有项目。
在 Activity 或 Fragment 中实现 onCreateContextMenu() 方法。当注册后的视图收到长按事件时,系统将调用您的 onCreateContextMenu() 方法。在此方法中,您通常可通过扩充菜单资源来定义菜单项。例如:
@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);
}
MenuInflater 允许您通过菜单资源扩充上下文菜单。回调方法参数包括用户所选的 View,以及一个提供有关所选项的附加信息的ContextMenu.ContextMenuInfo 对象。如果 Activity 有多个视图,每个视图均提供不同的上下文菜单,则可使用这些参数确定要扩充的上下文菜单。
- 实现 onContextItemSelected()。
用户选择菜单项时,系统将调用此方法,以便您能够执行适当的操作。 例如:
@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);
}
}
getItemId() 方法将查询所选菜单项的 ID,您应使用 android:id 属性将此 ID 分配给 XML 中的每个菜单项,如使用 XML 定义菜单部分所示。
成功处理菜单项后,系统将返回 true。如果未处理菜单项,则应将菜单项传递给超类实现。 如果 Activity 包括片段,则 Activity 将先收到此回调。 通过在未处理的情况下调用超类,系统将事件逐一传递给每个片段中相应的回调方法(按照每个片段的添加顺序),直到返回 true 或 false 为止。(Activity 和 android.app.Fragment 的默认实现返回 false,因此您始终应在未处理的情况下调用超类。)
示例代码如下:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
registerForContextMenu(findViewById(R.id.btn_menu));
}
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.main, menu);
}
@Override
public boolean onContextItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_home:
Toast.makeText(MainActivity.this, "menu_home", Toast.LENGTH_SHORT).show();
break;
case R.id.menu_recyclerview:
Toast.makeText(MainActivity.this, "menu_recyclerview", Toast.LENGTH_SHORT).show();
break;
}
return true;
}
}
使用上下文操作模式
上下文操作模式是 ActionMode 的一种系统实现,它将用户交互的重点转到执行上下文操作上。用户通过选择项目启用此模式时,屏幕顶部将出现一个“上下文操作栏”,显示用户可对当前所选项执行的操作。
注:上下文操作栏(ActionBar)不一定与操作栏(ToolBar)相关联。 尽管表面上看来上下文操作栏取代了操作栏的位置,但事实上二者独立运行。
这里需要解释ActionBar和ToolBar,在早期Android版本中,使用的是ActionBar,是ToolBar是其延伸版本,功能效果都比ActionBar好,具体可以看这篇文章:Android 一步步教你从ActionBar迁移到ToolBar。
当你使用ActionMode来显示ActionBar的时候,并且你的应用有toolBar的话,你可以设置ActionBar是不是覆盖在ToolBar上面。因为ActionBar是固定显示在屏幕顶端(在状态栏下面,应用的顶端),但是toolBar是可以改变位置的,但是默认也是显示在应用顶端。如果没有修改应用的style,那么当你显示ActionBar的时候,ActionBar会显示在ToolBar的上面(如图所示)。
当然对于用户来说,他并不能区分两个Bar,同时显示两个Bar对于用户来说体验并不好,所以最好是将ActionBar覆盖在ToolBar上,如果ActionBar消失,则正常显示ToolBar,至于如何设置覆盖,后面会讲到。
为单个视图启用上下文操作模式如果希望仅当用户选择特定视图时才调用上下文操作模式,则应:
- 实现 ActionMode.Callback 接口。在其回调方法中,您既可以为上下文操作栏指定操作,又可以响应操作项目的点击事件,还可以处理操作模式的其他生命周期事件。
- 当需要显示操作栏时(例如,用户长按视图),请调用 startActionMode()。
示例代码:
实现 ActionMode.Callback 接口,这里需要解释的是,ActionMode有support包和android.View包中,两个都可以。:
private ActionMode.Callback mActionModeCallback = new ActionMode.Callback() {
// Called when the action mode is created; startActionMode() was called
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
// Inflate a menu resource providing context menu items
MenuInflater inflater = mode.getMenuInflater();
inflater.inflate(R.menu.context_menu, menu);
return true;
}
// Called each time the action mode is shown. Always called after onCreateActionMode, but
// may be called multiple times if the mode is invalidated.
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
return false; // Return false if nothing is done
}
// Called when the user selects a contextual menu item
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_share:
shareCurrentItem();
mode.finish(); // Action picked, so close the CAB
return true;
default:
return false;
}
}
// Called when the user exits the action mode
@Override
public void onDestroyActionMode(ActionMode mode) {
mActionMode = null;
}
};
请注意,这些事件回调与选项菜单的回调几乎完全相同,只是其中每个回调还会传递与事件相关联的 ActionMode 对象。您可以使用 ActionMode API 对 CAB 进行各种更改,例如:使用 setTitle() 和 setSubtitle()(这对指示要选择多少个项目非常有用)修改标题和副标题。
另请注意,操作模式被销毁时,上述示例会将 mActionMode 变量设置为 null。在下一步中,您将了解如何初始化该变量,以及保存 Activity 或片段中的成员变量有何作用。
调用 startActionMode() 以便适时启用上下文操作模式,例如:响应对 View 的长按操作:
someView.setOnLongClickListener(new View.OnLongClickListener() {
// Called when the user long-clicks on someView
public boolean onLongClick(View view) {
if (mActionMode != null) {
return false;
}
// Start the CAB using the ActionMode.Callback defined above
mActionMode = getActivity().startActionMode(mActionModeCallback);
view.setSelected(true);
return true;
}
});
当您调用 startActionMode() 时,系统将返回已创建的 ActionMode。通过将其保存在成员变量中,您可以更改上下文操作栏来响应其他事件。 在上述示例中, ActionMode 用于在启动操作模式之前检查成员是否为空,以确保当 ActionMode 实例已激活时不再重建该实例。
完整的示例代码:
public class MainActivity extends AppCompatActivity implements android.view.ActionMode.Callback {
private android.view.ActionMode mActionMode;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.btn_menu_context).setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
if (mActionMode != null) {
return false;
}
mActionMode = startActionMode(MainActivity.this);
v.setSelected(true);
return true;
}
});
}
@Override
public boolean onCreateActionMode(android.view.ActionMode mode, Menu menu) {
MenuInflater inflater = mode.getMenuInflater();
inflater.inflate(R.menu.main, menu);
return true;
}
@Override
public boolean onPrepareActionMode(android.view.ActionMode mode, Menu menu) {
return false;
}
@Override
public boolean onActionItemClicked(android.view.ActionMode mode, MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_home:
Toast.makeText(MainActivity.this, "menu_home", Toast.LENGTH_SHORT).show();
break;
case R.id.menu_recyclerview:
Toast.makeText(MainActivity.this, "menu_recyclerview", Toast.LENGTH_SHORT).show();
break;
}
return true;
}
@Override
public void onDestroyActionMode(android.view.ActionMode mode) {
mActionMode = null;
}
}
看上面的效果图可以看到,ActionBar的颜色为黑色,并且是显示在ToolBar的上面,那么如何修改呢?主要是通过修改AppTheme来达到效果。这是示例代码:
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
<!--当显示ActionBar的时候,覆盖在ToolBar上面-->
<item name="windowActionModeOverlay">true</item>
<!--修改ActionBar返回按钮的图片-->
<item name="android:actionModeCloseDrawable">@mipmap/ic_launcher</item>
<!--修改ActionBar背景颜色-->
<item name="android:actionModeBackground">@color/colorPrimary</item>
</style>
效果如下
在 ListView 或 GridView 中启用批处理上下文操作如果您在 ListView 或 GridView 中有一组项目(或 AbsListView 的其他扩展),且需要允许用户执行批处理操作,则应:
实现 AbsListView.MultiChoiceModeListener 接口,并使用 setMultiChoiceModeListener() 为视图组设置该接口。在侦听器的回调方法中,您既可以为上下文操作栏指定操作,也可以响应操作项目的点击事件,还可以处理从 ActionMode.Callback 接口继承的其他回调。 使用 CHOICE_MODE_MULTIPLE_MODAL 参数调用 setChoiceMode()。
例如:
ListView listView = getListView();
listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);
listView.setMultiChoiceModeListener(new MultiChoiceModeListener() {
@Override
public void onItemCheckedStateChanged(ActionMode mode, int position,
long id, boolean checked) {
// Here you can do something when items are selected/de-selected,
// such as update the title in the CAB
}
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
// Respond to clicks on the actions in the CAB
switch (item.getItemId()) {
case R.id.menu_delete:
deleteSelectedItems();
mode.finish(); // Action picked, so close the CAB
return true;
default:
return false;
}
}
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
// Inflate the menu for the CAB
MenuInflater inflater = mode.getMenuInflater();
inflater.inflate(R.menu.context, menu);
return true;
}
@Override
public void onDestroyActionMode(ActionMode mode) {
// Here you can make any necessary updates to the activity when
// the CAB is removed. By default, selected items are deselected/unchecked.
}
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
// Here you can perform updates to the CAB due to
// an invalidate() request
return false;
}
});
在Android6.0版本中,新加了一种浮动的上下文操作菜单,就是结合了上面两种菜单的菜单,这种菜单在文本选择时出现的最多,看下效果:
看下如何调用:
- 用startActionMode(Callback, ActionMode.TYPE_FLOATING)方法来代替原来的startActionMode(Callback)。
- 用ActionMode.Callback2来代替原来的ActionMode.Callback(android.View包下面的),Callback2和Callback相比多了一个onGetContentRect()函数,该函数只要是对菜单位置的定位。
看下代码:
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private ContextualMenu mContextualMenu = new ContextualMenu();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
//创建Floating上下文操作菜单
findViewById(R.id.btn_context_floating).setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_context_floating:
if (Build.VERSION.SDK_INT < 23) {
return;
}
FloatingContextualMenu mFloatingContextualMenu = new FloatingContextualMenu();
mFloatingContextualMenu.startActionMode(MainActivity.this);
break;
}
}
}
@TargetApi(23)
public class FloatingContextualMenu extends ActionMode.Callback2 {
private static ActionMode mFloatingActionMode;
private Activity actv;
void startActionMode(Activity actv) {
this.actv = actv;
if (mFloatingActionMode != null) {
return;
}
mFloatingActionMode = actv.startActionMode(this, ActionMode.TYPE_FLOATING);
}
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
mode.getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
return false;
}
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
Toast.makeText(actv, item.getTitle(), Toast.LENGTH_SHORT).show();
mode.finish();
return true;
}
@Override
public void onDestroyActionMode(ActionMode mode) {
mFloatingActionMode = null;
}
//控制floating contextual menu显示的位置,默认显示在tool bar的位置
@Override
public void onGetContentRect(ActionMode mode, View view, Rect outRect) {
outRect.set(200, 500, 200 + view.getWidth(), 500 + view.getHeight());
}
}
针对TextView有专门的方法去显示这种菜单,看代码:
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
((TextView) findViewById(R.id.tv_context_floating)).setCustomSelectionActionModeCallback(new TextFloatingCallback(MainActivity.this));
}
}
public class TextFloatingCallback extends ActionMode.Callback2 {
private Activity actv;
TextFloatingCallback(Activity actv) {
this.actv = actv;
}
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
mode.getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
return false;
}
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
Toast.makeText(actv, item.getTitle(), Toast.LENGTH_SHORT).show();
mode.finish();
return false;
}
@Override
public void onDestroyActionMode(ActionMode mode) {
}
}
PopupMenu
PopupMenu 是锚定到 View 的模态菜单。如果空间足够,它将显示在定位视图下方,否则显示在其上方。
提供类似于 Spinner 且不保留永久选择的下拉菜单。
注:PopupMenu 在 API 级别 11 及更高版本中可用。
如果使用 XML 定义菜单,则显示弹出菜单的方法如下:
- 实例化 PopupMenu 及其构造函数,该函数将提取当前应用的 Context 以及菜单应锚定到的 View。
- 使用 MenuInflater 将菜单资源扩充到 PopupMenu.getMenu() 返回的 Menu 对象中。在 API 级别 14 及更高版本中,您可以改为使用 PopupMenu.inflate()。
例如,以下是一个使用 android:onClick 属性显示弹出菜单的按钮:
<ImageButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_overflow_holo_dark"
android:contentDescription="@string/descr_overflow_button"
android:onClick="showPopup" />
稍后,Activity 可按照如下方式显示弹出菜单:
public void showPopup(View v) {
PopupMenu popup = new PopupMenu(this, v);
MenuInflater inflater = popup.getMenuInflater();
inflater.inflate(R.menu.actions, popup.getMenu());
popup.show();
}
在 API 级别 14 及更高版本中,您可以将两行合在一起,使用 PopupMenu.inflate() 扩充菜单。
当用户选择项目或触摸菜单以外的区域时,系统即会清除此菜单。 您可使用 PopupMenu.OnDismissListener 侦听清除事件。
要在用户选择菜单项时执行操作,您必须实现 PopupMenu.OnMenuItemClickListener 接口,并通过调用 setOnMenuItemclickListener() 将其注册到 PopupMenu。用户选择项目时,系统会在接口中调用 onMenuItemClick() 回调。
例如:
public void showMenu(View v) {
PopupMenu popup = new PopupMenu(this, v);
// This activity implements OnMenuItemClickListener
popup.setOnMenuItemClickListener(this);
popup.inflate(R.menu.actions);
popup.show();
}
@Override
public boolean onMenuItemClick(MenuItem item) {
switch (item.getItemId()) {
case R.id.archive:
archive(item);
return true;
case R.id.delete:
delete(item);
return true;
default:
return false;
}
}
效果的话,就是和微信首页的加号按钮一样。
下面菜单Demo的github:https://github.com/LxxCaroline/SampleApplication/tree/master/menusample