三种菜单控件的兼容性问题处理集锦

2019-01-18 15:12:59 浏览数 (1)

选项菜单OptionsMenu的兼容问题

如果开发者用的是2.*及以上版本的Android Studio,那么极有可能发现openOptionsMenu方法无法调出菜单列表,不是SDK版本不够新,恰恰相反,正是因为SDK版本太新了。我们在Android Studio里面创建一个新的Activity代码,默认都是继承AppCompatActivity,而且build.gradle中也指定了appcompat-v7的编译版本,举例如下:

代码语言:javascript复制
	compile 'com.android.support:appcompat-v7:24.2.0'

现在就是跟appcompat-v7的版本有关,经过多方实验,如果编译用的appcompat-v7版本大等于22.1.0,那么openOptionsMenu方法将失效;如果appcompat-v7版本小于22.1.0,比如使用21.0.3版本来编译,那么openOptionsMenu方法是能够弹出菜单的。另外,如果页面代码继承Activity,而非AppCompatActivity,则openOptionsMenu方法可正常使用。所以解决这个问题有两种办法: 1、页面代码继承AppCompatActivity,同时build.gradle中指定较低版本的appcompat-v7来编译(但将无法使用新版本的功能),具体配置修改如下:

代码语言:javascript复制
	compile 'com.android.support:appcompat-v7:21.0.3'

2、页面代码改为继承Activity,可是如此一来,App中的各页面风格可能无法保持一致。 如果嫌麻烦的话,干脆就不要用选项菜单的openOptionsMenu方法了。自己写个PopupMenu或者ListPopupWindow实现弹出菜单的功能,PopupMenu和ListPopupWindow使用说明参见《Android开发笔记(一百二十一)列表弹窗PopupMenu和ListPopupWindow》;也可以使用更灵活的弹窗控件PopupWindow,该控件的使用说明参见《Android开发笔记(六十五)多样的菜单》。

上下文菜单ContextMenu的兼容问题

一般情况下使用上下文菜单没什么问题,但是给ListView的列表项注册上下文菜单就得注意了。比如下面的代码,本来想在长按列表项时弹出上下文菜单:

代码语言:javascript复制
	@Override
	public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
		registerForContextMenu(view);
		openContextMenu(view);
		unregisterForContextMenu(view);
		return true;
	}

可是运行时程序却异常退出,查看日志发现,打开上下文菜单时不停地调用AbsListView.showContextMenuForChild,最后出现栈溢出异常“java.lang.StackOverflowError”,这是因为上下文菜单的长按事件与列表项的长按监听器OnItemLongClickListener相互影响,使得程序陷入了死循环。最后的处理办法,还是要把两种长按事件阻隔开,即等待列表项长按事件处理完毕之后,再去触发上下文菜单事件;同时在打开上下文菜单之前,务必清空列表项的长按事件,确保这两种事件不会互相影响。修改后的代码如下所示:

代码语言:javascript复制
	private View mCurrentView;
	@Override
	public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
		mCurrentView = view;
		mHandler.postDelayed(mPopupMenu, 100);
		return true;
	}
	
	private Handler mHandler = new Handler();
	private Runnable mPopupMenu = new Runnable() {
		@Override
		public void run() {
			lv_cart.setOnItemLongClickListener(null);
			registerForContextMenu(mCurrentView);
			openContextMenu(mCurrentView);
			unregisterForContextMenu(mCurrentView);
			lv_cart.setOnItemLongClickListener(ShoppingCartActivity.this);
		}
	};

溢出菜单OverflowMenu的兼容问题

溢出菜单用于在顶部导航栏右侧展示,这个顶部导航栏可以是ActionBar,也可以是Android5.0之后的Toolbar。由于ActionBar与Toolbar使用方式上的差异,因此造成溢出菜单要分别对这种导航栏进行兼容适配。如果读者对ActionBar和Toolbar还不太了解的话,建议先看看这两篇博文《Android开发笔记(二十)顶部导航栏》、《Android开发笔记(一百一十九)工具栏Toolbar》。 举个例子,默认情况下,溢出菜单列表的菜单项不会在文字左边显示图标,即使设置了icon属性也不管用。要想让菜单项显示左侧图标,得调用MenuBuilder的setOptionalIconsVisible方法,通过菜单的featureId判断此菜单是否来源于ActionBar和Toolbar,如果是这二者来源(ActionBar的featureId是8,Toolbar的featureId是108),则显示菜单文字左边的图标。具体的判断代码如下所示:

代码语言:javascript复制
	public static void setOverflowIconVisible(int featureId, Menu menu) {
		// ActionBar的featureId是8,Toolbar的featureId是108
		if (featureId % 100 == Window.FEATURE_ACTION_BAR && menu != null) {
			if (menu.getClass().getSimpleName().equals("MenuBuilder")) {
				try {
					Method m = menu.getClass().getDeclaredMethod(
							"setOptionalIconsVisible", Boolean.TYPE);
					m.setAccessible(true);
					m.invoke(menu, true);
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}
	}

再举个例子,如果想让溢出菜单的某个菜单图标显示在导航栏上,可以在菜单布局中将showAsAction属性设置为ifRoom或者always,布局代码如下所示:

代码语言:javascript复制
<menu xmlns:android="http://schemas.android.com/apk/res/android" >

    <item
        android:id="@ id/menu_refresh"
        android:orderInCategory="1"
        android:icon="@drawable/ic_refresh"
        android:showAsAction="ifRoom"
        android:title="刷新"/>

    <item
        android:id="@ id/menu_about"
        android:orderInCategory="8"
        android:icon="@drawable/ic_about"
        android:showAsAction="never"
        android:title="关于"/>
    
    <item
        android:id="@ id/menu_quit"
        android:orderInCategory="9"
        android:icon="@drawable/ic_quit"
        android:showAsAction="never"
        android:title="退出"/>
</menu>

上面这个菜单布局,在ActionBar时代没有问题;然而到了Toolbar时代,反而出了问题。即使导航栏上还有空间,也设置了ifRoom或者always的菜单项,可是其图标并不会显示在导航栏上。为什么会这样呢?这是因为Toolbar控件不是位于内核的addroid.jar,也不是位于v4的兼容包android-support-v4.jar,而是位于appcompat-v7的兼容包中,开发者要在工程中把appcompat-v7做为一个库导入到本工程中。这就意味着,Toolbar其实是做为一个自定义控件引进来的,倘若在布局文件中使用Toolbar,得声明它的全路径“android.support.v7.widget.Toolbar”;那么在菜单布局中,同样也要补充对自定义控件的相关处理,首先要给根节点menu增加命名空间声明xmlns:app="http://schemas.android.com/apk/res-auto",然后还要把android:showAsAction="ifRoom"改为app:showAsAction="ifRoom"。 下面是修改后适用于Toolbar的菜单布局文件:

代码语言:javascript复制
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto" >

    <item
        android:id="@ id/menu_refresh"
        android:orderInCategory="1"
        android:icon="@drawable/ic_refresh"
        app:showAsAction="ifRoom"
        android:title="刷新"/>

    <item
        android:id="@ id/menu_about"
        android:orderInCategory="8"
        android:icon="@drawable/ic_about"
        app:showAsAction="never"
        android:title="关于"/>
    
    <item
        android:id="@ id/menu_quit"
        android:orderInCategory="9"
        android:icon="@drawable/ic_quit"
        app:showAsAction="never"
        android:title="退出"/>
</menu>

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

0 人点赞