Android 4.0以上设备虚拟按键中显示Menu键

栏目: IOS · Android · 发布时间: 7年前

内容简介:版权声明: https://blog.csdn.net/wangjinyu501/article/details/84326987

版权声明: https://blog.csdn.net/wangjinyu501/article/details/84326987

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

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

Android 4.0以上设备虚拟按键中显示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,所以直接分析它的具体逻辑。

@Override
    public 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即可。

@Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);//注意调用顺序
        showNavigationMenuKey(getWindow());
    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_MENU) {
            // 业务逻辑
            return true;
        }
        return super.onKeyDown(keyCode, event);
    }

以上所述就是小编给大家介绍的《Android 4.0以上设备虚拟按键中显示Menu键》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Flexible Rails

Flexible Rails

Peter Armstrong / Manning Publications / 2008-01-23 / USD 44.99

Rails is a fantastic tool for web application development, but its Ajax-driven interfaces stop short of the richness you gain with a tool like Adobe Flex. Simply put, Flex is the most productive way t......一起来看看 《Flexible Rails》 这本书的介绍吧!

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具