Android开发笔记(六十五)多样的菜单

菜单Menu

Android的菜单分为两类:选项菜单和上下文菜单,默认使用选项菜单。菜单的布局文件存放在res/menu目录下,使用ADT新建一个Android工程,首页代码MainActivity中会自动生成onMenuOpened和onMenuItemSelected函数代码。


展示选项菜单的途径有三种:
1、按下菜单键;
2、在代码中手动打开选项菜单,即调用函数openOptionsMenu;
3、按下导航栏右侧溢出菜单按钮,溢出菜单参见《 Android开发笔记(二十)顶部导航栏ActionBar》;


下面是选项菜单需要重写的方法:
onMenuOpened : 在菜单弹出时调用,一般无需重写
onMenuItemSelected : 在菜单项选择时调用,查看该方法的源码,会发现该方法内部做分支处理,判断如果是选项菜单则调用onOptionsItemSelected,如果是上下文菜单则调用onContextItemSelected。一般无需重写
onCreateOptionsMenu : 在页面打开时调用,需要重写指定菜单项目
onOptionsItemSelected : 在选项菜单的菜单项选中时调用,需要重写对不同菜单项做分支处理
onPrepareOptionsMenu : 在准备打开选项菜单时调用,一般无需重写
onOptionsMenuClosed : 在选项菜单关闭时调用,一般无需重写


上下文菜单ContextMenu

上下文菜单类似于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;


弹窗PopupWindow

在实际开发中,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;
		}
		
	};
	
}





点此查看Android开发笔记的完整目录

你可能感兴趣的:(android,contextMenu,菜单,menu,PopupWindow)