Android的菜单分为两类:选项菜单和上下文菜单,默认使用选项菜单。菜单的布局文件存放在res/menu目录下,使用ADT新建一个Android工程,首页代码MainActivity中会自动生成onMenuOpened和onMenuItemSelected函数代码。 展示选项菜单的途径有三种: 1、按下菜单键; 2、在代码中手动打开选项菜单,即调用函数openOptionsMenu; 3、按下导航栏右侧溢出菜单按钮,溢出菜单参见《Android开发笔记(二十)顶部导航栏ActionBar》; 下面是选项菜单需要重写的方法: onMenuOpened : 在菜单弹出时调用,一般无需重写 onMenuItemSelected : 在菜单项选择时调用,查看该方法的源码,会发现该方法内部做分支处理,判断如果是选项菜单则调用onOptionsItemSelected,如果是上下文菜单则调用onContextItemSelected。一般无需重写 onCreateOptionsMenu : 在页面打开时调用,需要重写指定菜单项目 onOptionsItemSelected : 在选项菜单的菜单项选中时调用,需要重写对不同菜单项做分支处理 onPrepareOptionsMenu : 在准备打开选项菜单时调用,一般无需重写 onOptionsMenuClosed : 在选项菜单关闭时调用,一般无需重写
上下文菜单类似于Windows上的右键菜单,只不过手机上没有鼠标右键,所以一般在某个控件被长按时弹出。 展示上下文菜单的途径有两种: 1、在某个控件被长按时弹出。通常在onStart函数中加入registerForContextMenu为指定控件注册上下文菜单,在onStop函数中加入unregisterForContextMenu为指定控件注销上下文菜单。 2、在代码中手动打开上下文菜单。先执行registerForContextMenu方法注册菜单,然后执行openContextMenu打开菜单,最后执行unregisterForContextMenu注销菜单。 下面是上下文菜单需要重写的方法: onCreateContextMenu : 控件长按后,准备打开上下文菜单时调用,需要重写指定菜单项目 onContextItemSelected : 在上下文菜单的菜单项选中时调用,需要重写对不同菜单项做分支处理 onContextMenuClosed : 在上下文菜单关闭时调用,一般无需重写
为方便理清两种菜单的相互关系与调用流程,我们对各种菜单点击事件做了测试,下面是不同场景下的日志结果: 打开页面 01-08 15:46:31.309: D/MainActivity(8885): onCreateOptionsMenu 01-08 15:46:31.309: D/MainActivity(8885): onPrepareOptionsMenu 点击弹出选项菜单 01-08 15:47:06.369: D/MainActivity(8885): onMenuOpened 01-08 15:47:06.373: D/MainActivity(8885): onPrepareOptionsMenu 01-08 15:47:06.373: D/MainActivity(8885): onMenuOpened 点击选项菜单的某项 01-08 15:47:31.909: D/MainActivity(8885): onMenuItemSelected 01-08 15:47:31.909: D/MainActivity(8885): onOptionsItemSelected 长按弹出上下文菜单 01-08 15:48:31.337: D/MainActivity(8885): onCreateContextMenu 点击上下文菜单的某项 01-08 15:49:04.589: D/MainActivity(8885): onMenuItemSelected 01-08 15:49:04.589: D/MainActivity(8885): onContextItemSelected 01-08 15:49:04.589: D/MainActivity(8885): onContextMenuClosed 01-08 15:49:04.593: D/MainActivity(8885): onContextMenuClosed 从以上日志可以看出,选项菜单和上下文菜单的区别有: 1、单击菜单项(不管是选项菜单还是上下文菜单)都会先触发onMenuItemSelected,如果是选项菜单则再触发onOptionsItemSelected,如果是上下文菜单则再触发onContextItemSelected; 2、选项菜单在页面打开后就创建好,弹出选项菜单时只是把已创建好的菜单打开而已,但上下文菜单要在每次打开前才进行创建操作; 3、选中某个菜单项后,上下文菜单会调用onContextMenuClosed方法关闭整个菜单,而选项菜单只是在界面上消失,并未调用关闭菜单方法onOptionsMenuClosed;
在实际开发中,Android自带的菜单显得朴素不够灵活,一个是位置固定,如选项菜单固定从页面底部弹出,溢出菜单固定从页面右上角弹出,上下文菜单固定显示在页面中央;另一个是样式固定,无法设置菜单背景,也无法设置其他的菜单显示元素(即使是简单显示左侧图标,也要通过反射机制调用MenuBuilder的setOptionalIconsVisible方法)。为解决以上不足,我们可利用弹窗PopupWindow来实现任意位置的菜单展示,以及可定制的菜单样式。 PopupWindow的机制是实现一个弹出框,其内容可以是任意布局的View,其页面悬浮在当前Activity页面之上。要让PopupWindow支持菜单,可在它的内部定义一个ListView,通过展示列表项和列表点击事件,从而实现悬浮菜单的效果。 下面是弹窗的常用方法: PopupWindow构造函数 : 可设置弹窗的视图内容、大小、是否获得焦点等等。 setContentView : 设置弹窗的视图内容 setWindowLayoutMode : 设置弹窗的宽和高。如想单独设置宽度可使用setWidth方法,如想单独设置高度可使用setHeight方法。 setFocusable : 设置是否获得焦点。如为true则弹窗以外区域不可点击,如为false则弹窗以外区域可以点击。 setBackgroundDrawable : 设置弹窗的背景。 setAnimationStyle : 设置弹窗弹出和缩回时的动画样式。 isShowing : 判断弹窗是否在展示中。 showAtLocation : 让弹窗在上级视图中的绝对坐标中展现。可设置对齐方式,以及横坐标与纵坐标上的绝对偏移。 showAsDropDown : 让弹窗在指定视图位置以下拉形式展现。可设置相对于指定视图的横坐标与纵坐标上的相对偏移。 dismiss : 关闭弹窗。 update : 更新弹窗。 setTouchInterceptor : 设置弹窗的触摸监听器。 setOnDismissListener : 设置弹窗的关闭监听器。 下面是弹窗的几个使用小技巧: 1、点击弹窗以外的区域,弹窗自动消失; 首先保证setFocusable设置为false(经测试setOutsideTouchable设置不管用);然后在Activity页面注册一个手势监听器OnGestureListener,重写onSingleTapUp方法加入弹窗关闭的代码;最后重写Activity页面dispatchTouchEvent方法,调用手势检测GestureDetector的onTouchEvent方法。 2、弹窗在弹出和消失时显示伸缩动画; 调用setAnimationStyle方法设置动画样式,该样式在styles.xml中定义,其中"android:windowEnterAnimation"项定义的是展示弹窗时的动画,"android:windowExitAnimation"项定义的是关闭弹窗时的动画。
下面是菜单与弹窗的例子代码:
import java.util.ArrayList;
import com.example.exmmenu.adapter.MenuLeftAdapter;
import com.example.exmmenu.adapter.MenuPopAdapter;
import com.example.exmmenu.adapter.PopMenuItem;
import com.example.exmmenu.util.MetricsUtil;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.ContextMenu;
import android.view.GestureDetector;
import android.view.GestureDetector.OnGestureListener;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.View.OnClickListener;
import android.view.View.OnTouchListener;
import android.view.ViewGroup.LayoutParams;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.Button;
import android.widget.ListView;
import android.widget.PopupWindow;
import android.widget.Toast;
import android.widget.PopupWindow.OnDismissListener;
public class MainActivity extends Activity implements OnClickListener, OnItemClickListener {
private static final String TAG = "MainActivity";
private Button btn_context_long;
private PopupWindow popupWindow;
private int[] mIconList = {R.drawable.ic_refresh, R.drawable.ic_search,
R.drawable.ic_about, R.drawable.ic_quit};
private String[] mTextList = {"刷新", "搜索", "关于", "退出"};
private ArrayList<PopMenuItem> mTitleList = new ArrayList<PopMenuItem>();
private GestureDetector mGesture;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button btn_option = (Button) findViewById(R.id.btn_option);
Button btn_context = (Button) findViewById(R.id.btn_context);
btn_option.setOnClickListener(this);
btn_context.setOnClickListener(this);
btn_context_long = (Button) findViewById(R.id.btn_context_long);
Button btn_pop = (Button) findViewById(R.id.btn_pop);
Button btn_pop_left = (Button) findViewById(R.id.btn_pop_left);
btn_pop.setOnClickListener(this);
btn_pop_left.setOnClickListener(this);
mGesture = new GestureDetector(this, mGestureListener);
for (int i=0; i<mIconList.length; i++) {
mTitleList.add(new PopMenuItem(mIconList[i], mTextList[i]));
}
}
@Override
protected void onResume() {
registerForContextMenu(btn_context_long);
super.onResume();
}
@Override
protected void onPause() {
unregisterForContextMenu(btn_context_long);
super.onPause();
}
private void closePopWindow() {
if (popupWindow != null && popupWindow.isShowing()) {
popupWindow.dismiss();
popupWindow = null;
}
}
@Override
public void onClick(View v) {
if (v.getId() == R.id.btn_option) {
openOptionsMenu();
} else if (v.getId() == R.id.btn_context) {
registerForContextMenu(v);
openContextMenu(v);
unregisterForContextMenu(v);
} else if (v.getId() == R.id.btn_pop) {
View view = LayoutInflater.from(this).inflate(R.layout.menu_pop, null);
ListView lv_menu_pop = (ListView) view.findViewById(R.id.lv_menu_pop);
MenuPopAdapter adapter = new MenuPopAdapter(this, mTitleList);
lv_menu_pop.setAdapter(adapter);
lv_menu_pop.setOnItemClickListener(this);
popupWindow = new PopupWindow(view, MetricsUtil.getPopWidth(this),
LayoutParams.WRAP_CONTENT);
popupWindow.setFocusable(false);
popupWindow.showAsDropDown(v, 10, 0);
} else if (v.getId() == R.id.btn_pop_left) {
View view = LayoutInflater.from(this).inflate(R.layout.menu_pop, null);
ListView lv_menu_pop = (ListView) view.findViewById(R.id.lv_menu_pop);
MenuLeftAdapter adapter = new MenuLeftAdapter(this, mTitleList);
lv_menu_pop.setAdapter(adapter);
lv_menu_pop.setOnItemClickListener(this);
popupWindow = new PopupWindow(view, MetricsUtil.getPopWidth(this),
LayoutParams.MATCH_PARENT, false);
popupWindow.setOnDismissListener(new OnDismissListener() {
public void onDismiss() {
Toast.makeText(MainActivity.this, "左侧菜单消失了", Toast.LENGTH_SHORT).show();
}
});
//在窗口内部的空白处点击,也要关闭弹窗
view.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
closePopWindow();
return false;
}
});
popupWindow.setAnimationStyle(R.style.AnimationFade);
popupWindow.showAtLocation(v, Gravity.LEFT, 0, 0);
}
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
closePopWindow();
PopMenuItem item = mTitleList.get(position);
Toast.makeText(this, "您点击的菜单名称是"+item.text, Toast.LENGTH_LONG).show();
}
@Override
public boolean onMenuOpened(int featureId, Menu menu) {
Log.d(TAG, "onMenuOpened");
return super.onMenuOpened(featureId, menu);
}
@Override
public boolean onMenuItemSelected(int featureId, MenuItem item) {
Log.d(TAG, "onMenuItemSelected");
return super.onMenuItemSelected(featureId, item);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
Log.d(TAG, "onCreateOptionsMenu");
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
Log.d(TAG, "onOptionsItemSelected");
return super.onOptionsItemSelected(item);
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
Log.d(TAG, "onPrepareOptionsMenu");
return super.onPrepareOptionsMenu(menu);
}
@Override
public void onOptionsMenuClosed(Menu menu) {
Log.d(TAG, "onOptionsMenuClosed");
super.onOptionsMenuClosed(menu);
}
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
Log.d(TAG, "onCreateContextMenu");
getMenuInflater().inflate(R.menu.main, menu);
}
@Override
public boolean onContextItemSelected(MenuItem item) {
Log.d(TAG, "onContextItemSelected");
return super.onContextItemSelected(item);
}
@Override
public void onContextMenuClosed(Menu menu) {
Log.d(TAG, "onContextMenuClosed");
super.onContextMenuClosed(menu);
}
//在弹窗以外的区域点击,都关闭弹窗
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
mGesture.onTouchEvent(ev);
return super.dispatchTouchEvent(ev);
}
private OnGestureListener mGestureListener = new OnGestureListener() {
@Override
public boolean onDown(MotionEvent e) {
return false;
}
@Override
public void onShowPress(MotionEvent e) {
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
closePopWindow();
return false;
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
return false;
}
@Override
public void onLongPress(MotionEvent e) {
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
return false;
}
};
}