概述

在Android4.0以后,谷歌添加了虚拟导航键来替换实体键,虚拟导航栏有三个按钮分别是Back键,Home键,Recent键。一般的,Android默认不显示Menu键,本篇将讲述如何开启Menu键以及它背后的实现原理。

在一些产品中可以发现在虚拟导航栏上有菜单键功能,而一般的应用是没有这个功能的,效果如图右下角所示:

那是如何实现的呢。先看一下代码:

   /*** 显示虚拟导航栏菜单按钮.* 虚拟导航栏菜单按钮在4.0以后默认不显示,可以利用反射强行设置,调用位置须在setContentView之后* 具体可以参考5.0以及6.0中的PhoneWindow类源码** @param window {@link Window}*/public static void showNavigationMenuKey(Window window) {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {showNavigationLollipopMR1(window);}if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {showNavigationIceCreamSandwich(window);}}/*** 显示虚拟导航栏菜单按钮.* Android 4.0 - Android 5.0* API 14 - 21** @param window {@link Window}*/private static void showNavigationIceCreamSandwich(Window window) {try {int flags = WindowManager.LayoutParams.class.getField("FLAG_NEEDS_MENU_KEY").getInt(null);window.addFlags(flags);} catch (IllegalAccessException e) {e.printStackTrace();} catch (NoSuchFieldException e) {e.printStackTrace();}}/*** 显示虚拟导航栏菜单按钮.* Android 5.1.1 - Android 8.0* API 22 - 25** @param window {@link Window}*/private static void showNavigationLollipopMR1(Window window) {try {Method setNeedsMenuKey = Window.class.getDeclaredMethod("setNeedsMenuKey", int.class);setNeedsMenuKey.setAccessible(true);int value = WindowManager.LayoutParams.class.getField("NEEDS_MENU_SET_TRUE").getInt(null);setNeedsMenuKey.invoke(window, value);} catch (NoSuchMethodException e) {e.printStackTrace();} catch (NoSuchFieldException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();} catch (InvocationTargetException e) {e.printStackTrace();}}

源码分析

通过上面代码可以知道是使用反射来完成设置导航栏菜单键的,下面来分析一下为什么要使用这个方案。
Android中所有视图都对应着Window来负责管理,在Activity的setContentView方法中会与Window关联在一起。

  /*** Set the activity content from a layout resource.  The resource will be* inflated, adding all top-level views to the activity.** @param layoutResID Resource ID to be inflated.** @see #setContentView(android.view.View)* @see #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)*/public void setContentView(@LayoutRes int layoutResID) {getWindow().setContentView(layoutResID);initWindowDecorActionBar();}

在setContentView过程中,Activity将具体实现交给Window来处理。Window是一个抽象类,它的唯一实现类是PhoneWindow,所以直接分析它的具体逻辑。

  @Overridepublic void setContentView(View view, ViewGroup.LayoutParams params) {// Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window// decor, when theme attributes and the like are crystalized. Do not check the feature// before this happens.if (mContentParent == null) {installDecor();//从调用此方法继续分析} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {mContentParent.removeAllViews();}if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {view.setLayoutParams(params);final Scene newScene = new Scene(mContentParent, view);transitionTo(newScene);} else {mContentParent.addView(view, params);}mContentParent.requestApplyInsets();final Callback cb = getCallback();if (cb != null && !isDestroyed()) {cb.onContentChanged();}mContentParentExplicitlySet = true;}

可以看到在Window的setContentView法中会初始化DecorView,如果没有DecorView则创建它。创建好DecorView后,继续按照设定的主题样式形成最终的DecorView。如下:

  private void installDecor() {mForceDecorInstall = false;if (mDecor == null) {mDecor = generateDecor(-1);mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);mDecor.setIsRootNamespace(true);if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);}} else {mDecor.setWindow(this);}if (mContentParent == null) {mContentParent = generateLayout(mDecor);//设置主题样式......//省略后续代码}

generateLayout方法非常关键,这里就是系统设置导航栏菜单键的地方。

protected ViewGroup generateLayout(DecorView decor) {//......省略代码段final Context context = getContext();final int targetSdk = context.getApplicationInfo().targetSdkVersion;final boolean targetPreHoneycomb = targetSdk<android.os.Build.VERSION_CODES.HONEYCOMB;final boolean targetPreIcs = targetSdk<android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH;final boolean targetPreL = targetSdk < android.os.Build.VERSION_CODES.LOLLIPOP;final boolean targetHcNeedsOptions = context.getResources().getBoolean(R.bool.target_honeycomb_needs_options_menu);final boolean noActionBar = !hasFeature(FEATURE_ACTION_BAR) || hasFeature(FEATURE_NO_TITLE);//Menu的显示或者隐藏if (targetPreHoneycomb || (targetPreIcs && targetHcNeedsOptions && noActionBar)) {setNeedsMenuKey(WindowManager.LayoutParams.NEEDS_MENU_SET_TRUE);} else {setNeedsMenuKey(WindowManager.LayoutParams.NEEDS_MENU_SET_FALSE);}//...省略代码段
}

上述分析的系统源码基于API 26,在API 21中,此部分略有不同,如下:

protected ViewGroup generateLayout(DecorView decor) {//......省略代码段final Context context = getContext();final int targetSdk = context.getApplicationInfo().targetSdkVersion;final boolean targetPreHoneycomb = targetSdk < android.os.Build.VERSION_CODES.HONEYCOMB;final boolean targetPreIcs = targetSdk<android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH;final boolean targetPreL = targetSdk < android.os.Build.VERSION_CODES.LOLLIPOP;final boolean targetHcNeedsOptions = context.getResources().getBoolean(R.bool.target_honeycomb_needs_options_menu);final boolean noActionBar = !hasFeature(FEATURE_ACTION_BAR) || hasFeature(FEATURE_NO_TITLE);if (targetPreHoneycomb || (targetPreIcs && targetHcNeedsOptions && noActionBar)) {addFlags(WindowManager.LayoutParams.FLAG_NEEDS_MENU_KEY);} else {clearFlags(WindowManager.LayoutParams.FLAG_NEEDS_MENU_KEY);}//...省略代码段
}

在Android API 26的Window类中多了一个方法,setNeedMenuKey 函数,该函数的作用就是设置是否显示虚拟菜单键。在Android 5.1.1之前是否显示菜单键是WindowManager.LayoutParams 中的一个flags,而在Android 5.1.1及以后,谷歌把这个标记为改到了WindowManager.LayoutParams类中的needsMenuKey 字段去了,可以通过setNeedMenuKey 方法来修改。

因此利用反射即可完成显示Menu键,注意调用方法时需要在setContentView之后,也就是在系统完成DecorView之后调用,强行显示。Menu的点击事件只需要监听onKeyDown,判断keyCode即可。

 @Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);//注意调用顺序showNavigationMenuKey(getWindow());}@Overridepublic boolean onKeyDown(int keyCode, KeyEvent event) {if (keyCode == KeyEvent.KEYCODE_MENU) {// 业务逻辑return true;}return super.onKeyDown(keyCode, event);}

参考:

  • https://blog.csdn.net/scau_zhangpeng/article/details/78955886
  • https://blog.csdn.net/u014651216/article/details/53188698

Android 4.0以上设备虚拟按键中显示Menu键相关推荐

  1. 怎么让Android4.0以上机器的虚拟按键中显示menu键

    4.0的menu被放到了actionbar上,如果不做任何设置的话,虚拟按键(虚拟的back和home键那里)上不会显示menu.网上搜了两个解决方法: 1. 修改AndroidManifest.xm ...

  2. android判断多个按钮,Android开发之判断有无虚拟按键(导航栏)的实例

    判断有无虚拟按键(导航栏) 现在很大一部分手机没有虚拟按键,一部分有.我们在做适配的时候可能会用到这方面的知识. 例如:屏幕填充整个屏幕的时候,没办法只能连导航栏一起填充了,但是这个不是我们想要的,我 ...

  3. 安卓平板隐藏虚拟按键_实现安卓设备虚拟按键隐藏和显示的方法和系统的制作方法...

    实现安卓设备虚拟按键隐藏和显示的方法和系统的制作方法 [技术领域] [0001]本发明涉及移动终端显示技术领域,尤其涉及一种方便实现安卓设备虚拟按键隐藏和显示的方法和系统. [背景技术] [0002] ...

  4. Android实现隐藏手机底部虚拟按键

    现在的手机比较流行底部带虚拟按键,比如华为.nexus,一般情况下对开发一个APP没啥影响,但是不一般情况下就会有影响的,比如全屏录像功能, 用简单的方法直接获取camera支持的手机屏幕分辨率然后用 ...

  5. android 6.0 连接电脑,如何通过蓝牙连接您的Android 6.0棉花糖设备到您的电脑 | MOS86...

    蓝牙已经存在多年了.它非常适用于窄带应用,如将手机连接到耳机或汽车音响设备.虽然没有太多带宽,您可以在Android和PC之间共享文件和文件夹.如果你想连接你的Android 6.0棉花糖设备通过蓝牙 ...

  6. Android 4.0以上设备的虚拟按键中menu键的显示问题

    在 Android 4.0以后,google添加了虚拟导航键来替换实体键,同时按键由原来的四大天王改为back.home.recent三个.研究源码可以发现是否显示菜单键实在 Window初始化的布局 ...

  7. Android 隐藏底部三个虚拟按键

    工具类中使用 // 隐藏底部的虚拟按键 方法一 滑动屏幕 可重新显示出来public static void hideBottomUIMenu(Activity activity) {//隐藏虚拟按键 ...

  8. 安卓平板隐藏虚拟按键_如何隐藏 Android 下方的三个虚拟按键

    满意答案 RaulEP 2016.05.21 采纳率:54%    等级:5 已帮助:205人 Android 隐藏虚拟按键,可以使用谷歌官方提供的api里的SYSTEM_UI_FLAG_HIDE_N ...

  9. android+7.0+升级,终于来了!Android 7.0升级设备名单公布

    上周谷歌终于宣布Android 7.0正式命名为 Android Nougat(牛轧糖),一如既往,预计很快就有一批设备通过OTA升级至该系统.据悉,Android 7.0跟之前的Android 6. ...

  10. 微博机型Android怎么去掉,如何设置微博来源中显示出的手机型号 怎么去掉微博来源中的android字样...

    如何设置微博来源中显示出的手机型号 怎么去掉微博来源中的android字样 微博是一款分享.传播.获取实时信息的社交网络平台,是现在年轻人最爱的一款社交App,可是,不少朋友还不知道怎么设置微博来源中 ...

最新文章

  1. 【基础篇】DatePickerDialog日期控件的基本使用(一)
  2. Robotium调用getActivity()导致程序挂起的方法
  3. (私人收藏)2019科协WER解决方案
  4. 详解基于 Cortex-M3 的任务调度(下)
  5. 799页!吴恩达深度学习笔记.PDF
  6. CentOS用户和用户组的操作
  7. 虚虚实实,亦假亦真的 ValueTuple,绝对能眩晕你
  8. Python | Xpath实战训练
  9. Struts2数据封装
  10. rf连oracle版本一致,Navicat premium连不上Oracle的问题解决
  11. grub引导U盘(集成常用工具/深山红叶PE工具箱V30/完美者U盘维护系统V8.1)
  12. Javadoc注释的用法
  13. 今天进行的将zzb从apache迁移到nginx
  14. 制作抽签器用html,利用几何画板制作随机抽签器
  15. 【光学】基于matlab色散曲线拟合【含Matlab源码 2053期】
  16. 权威DNS、递归DNS以及DNS相关排名
  17. 背景图片虚化的效果的css样式的实现
  18. reflections歌词翻译_英文歌曲reflection的歌词翻译
  19. Go语言自学系列 | 高效golang开发
  20. stm32中的“hello world”

热门文章

  1. emi滤波matlab,EMI滤波器的作用和种类
  2. 电脑键盘上各个键的作用
  3. PPPOE拨号之一:Cisco 路由器adsl拨号配置
  4. 那些年我不知道为啥的电脑“灵异事件”
  5. WordPress付费资源素材下载主题 总裁CeoMax主题
  6. 使用office tool plus清除office激活状态
  7. 网上体育商城的设计与实现
  8. Android ViewModel与LiveData组件组合使用详解
  9. c语言判断闰年次数,C语言判断闰年,即判断年份是否为闰年
  10. 大学有必要考华为认证吗?