当前位置:   article > 正文

android 虚拟导航按钮(NavigationBar)可手动隐藏开发_switch_navigation_bar

switch_navigation_bar

NavigationBar可以手动隐藏,随着华为荣耀手机有了这个特点后,目前有很多android手机都有该特性。如下截图所示:



        上图的底部虚拟导航按钮的左、右边有两个按钮点击这个按钮,虚拟按钮就会消失,当从屏幕底部向上滑动时候,虚拟按钮就就会出现,这个消失和出现的过程中整个屏幕的布局都会从新计算。

         接下来,分下面几个点来说一下具体实现的效果。

         (一):首先第一个问题是如何让虚拟按钮(NavigationBar)消失。通过分析android源代码可以发现虚拟按钮(NavigationBar)的加入和实现是由系统app:systemui来完成的。

          在systemui这个app里面有一个类PhoneStatusBar,在这个类被创建的时候会判断当前系统是否定义了虚拟按钮(NavigationBar),如果定义了就添加,否则不添加。源码如下。

           判断是否有定义虚拟按钮(NavigationBar):

boolean showNav = mWindowManagerService.hasNavigationBar();
           创建虚拟按钮(NavigationBar)的代码:

  1. int layoutId = R.layout.navigation_bar;
  2. if(RecentsActivity.FLOAT_WINDOW_SUPPORT){
  3. layoutId = R.layout.navigation_bar_float_window;
  4. }
  5. mNavigationBarView =
  6. (NavigationBarView) View.inflate(context, /*R.layout.navigation_bar*/layoutId, null);
          很显然mNavigationBarView这个view就是显示虚拟按钮(NavigationBar)的。

          最后在systemui的这个类PhoneStatusBar里面通过如下方法把mNavigationBarView显示出来(添加到系统中):

mWindowManager.addView(mNavigationBarView, getNavigationBarLayoutParams());
          隐藏的方法就可以通过 mWindowManager来removeView方法来实现 。实验结果是:这是一个不错的选择。

          具体如何实现呢?首先通过上面的mNavigationBarView的布局文件(R.layout.navigation_bar)可以知道,我们可以重写这个布局文件,在重写的布局文件中添加两个隐藏按钮,这个很简单。其次还有个问题,就是在systemui里面隐藏了虚拟按钮(NavigationBar,如何通知WindowManager,这个可以通过定义一个新的KEYCODE,单点击这个隐藏按钮时候发出这个特殊的KEYCODE的按键事件。 

        (二)如何实现从底部滑动时候能够显示已经隐藏的虚拟按钮(NavigationBar),当然在横屏的时候,是从左、右边缘滑动来显示已经隐藏的虚拟按(NavigationBar) 通过对源码的分析发现,在PhoneWindowManager这个类里面可以找到蛛丝马迹。

在包 com.android.internal.policy.impl里面有PhoneWindowManager类,这是一个主要管理手机显示系统的所有window的类,我们知道在android中每个显示的界面其实都是一个window,比如一个activity的显示界面、一个Dialog弹出框、还有上面提到的 虚拟按钮(NavigationBar)、包括下拉状态栏、已经锁屏界面等等,通过hierarchyviewer.bat这个工具就可以看到当前手机正在显示的所有window.

         当然PhoneWindowManager类还处理一些其他的事情,比如按键事件的处理(按home回到待机界面)、锁屏的触发等等,不过我觉得其实都是管理的是window的切换。

         在这个PhoneWindowManager类里面,你会发现一个有用的对象:mSystemGestures。从名字可以发现这是一个系统手势的类。没错,你能够从手机屏幕顶部下滑拉出系统状态栏就是靠他了。

         我们就需要修改这个类,使得手机可以发现我们从下往上滑动的手势(从左、右边边缘滑动),这样就可以在  PhoneWindowManager里面收到这个我们需要的手势了。

         这样一来,在PhoneWindowManager里面收到显示虚拟按钮(NavigationBar)的手势,就想办法去显示虚拟按钮(NavigationBar)就可以了。在PhoneWindowManager中要显示虚拟按钮(NavigationBar)的办法和显示下拉状态栏类似,你需要在systemui里面定义相应的方法,然后在PhoneWindowManager里面通过如下代码获取StatusBarService:

  IStatusBarService statusbar = getStatusBarService();

         最后通过StatusBarService来调用在PhoneStatusBar里面定义的显示虚拟按钮(NavigationBar)的方法。

        如下是源码:

       PhoneWindowManager显示虚拟按钮(NavigationBar)的函数:

  1. private void showNavigationBar(final int type){
  2. if(isKeyguardLocked()){
  3. return;
  4. }
  5. startToShowNavbar = true;
  6. //Slog.i("yu_PhoneWindowManager", "showNavigationBar: NavigationBarMoveType="+NavigationBarMoveType);
  7. NavigationBarMoveType = type;
  8. if(mNavigationBar == null) {
  9. Slog.i("yu_PhoneWindowManager", "RAMOS showNavigationBar: showNavigationBar");
  10. //Log.v("NavigationGuard", "RAMOS showNavigationBar startToShowNavbar="+startToShowNavbar);
  11. mHandler.post(new Runnable() {
  12. @Override
  13. public void run() {
  14. try {
  15. IStatusBarService statusbar = getStatusBarService();
  16. if (statusbar != null) {
  17. //Slog.i("yu_PhoneWindowManager", "showNavigationBar");
  18. statusbar.showNavigationBar(type);
  19. }
  20. } catch (RemoteException e) {
  21. // re-acquire status bar service next time it is needed.
  22. mStatusBarService = null;
  23. }
  24. }
  25. });
  26. }
  27. }
          注意:在PhoneWindowManager类里面可以通过判断这个对象mNavigationBar是否为空来确认 虚拟按钮(NavigationBar)是否被隐藏。

         其他的showNavigationBar方法的声明和定义你只需仿照hideRecentApps来做就可以了。
         最后在PhoneStatusBar里面实现具体的虚拟按钮(NavigationBar)显示即可。

         比如,下面是我的实现方法:

  1. @Override // CommandQueue
  2. public void showNavigationBar(int type) {
  3. //Log.i("way", TAG + " showNavigationBar...");
  4. Log.i("yu_PhoneStatusBar", "showNavigationBar type="+type);
  5. if (mNavigationBarView != null) {
  6. try {
  7. mWindowManagerService.StartToShowNavbar(type);
  8. } catch (RemoteException ex) {
  9. }
  10. return;
  11. }
  12. if(mTempNavigationBarView == null){
  13. makeNewNavigationBar();
  14. }
  15. if(mTempNavigationBarView != null){
  16. mNavigationBarView = mTempNavigationBarView;
  17. //mNavigationBarView.setVisibility(View.VISIBLE);
  18. mWindowManager.addView(mNavigationBarView, mNavigationBarLayoutParams);
  19. prepareNavigationBarView(true);
  20. mNavigationBarView.setDisabledFlags(mDisabled);
  21. //mNavigationBarView.reorient();
  22. //mNavigationBarView.notifyScreenOn(true);
  23. mTempNavigationBarView = null;
  24. }else{
  25. Log.i("yu_PhoneStatusBar", "showNavigationBar: ERROR");
  26. }
  27. }
            重要是这这一个语句: mWindowManager.addView(mNavigationBarView, mNavigationBarLayoutParams);
            从上面的代码你会发现一个特别的变量: mTempNavigationBarView,其实这就是一个NavigationBarView。这是因为:为了显示NavigationBarView的时候能够快一点,所以每次在通过mWindowManager来removeView掉NavigationBarView后,我会自动去创建一个新的NavigationBarView等待下次显示用,这样一来下次要显示的时候直接使用即可,就不用创建了。

           注意:每次通过mWindowManager来removeView掉NavigationBarView后,这个刚刚被remove的NavigationBarView是不能再次利用的,下次还使用这个NavigationBarView会报错。

          (三)如何实现,在设置里面去配置虚拟按钮(NavigationBar)的排序。如下图:



       要解决这个问题,需要修改下面三个地方:

       1:在frameworks/base/core/java/android/provider/Settings.java里面添加如此代码:

             public static final String RAMOS_NAVBAR_STYLE = "RAMOS_NAVBAR_STYLE";

             我们就可以通过RAMOS_NAVBAR_STYLE 来保存我们虚拟按钮(NavigationBar)配置的排序了。

        2:在设置中app中,添加一个设置的界面重写SettingsPreferenceFragment来实现,配置自己的Preferences的xml文件,其中的RadioPreferences需要自己重写CheckBoxPreference来完成:

        如下是我写的CheckBoxPreference的RamosRadioNavbarStylePreference关键代码:

  1. public RamosRadioNavbarStylePreference(Context context, AttributeSet attrs, int defStyle) {
  2. super(context, attrs, defStyle);
  3. setWidgetLayoutResource(R.layout.preference_widget_radiobutton);
  4. }

  1. void setOnClickListener(OnClickListener listener) {
  2. mListener = listener;
  3. }
  4. @Override
  5. public void onClick() {
  6. if (mListener != null) {
  7. mListener.onRadioButtonClicked(this);
  8. }
  9. }
  10. @Override
  11. protected void onBindView(View view) {
  12. super.onBindView(view);
  13. mViewGroup = view;
  14. TextView title = (TextView) view.findViewById(android.R.id.title);
  15. if (title != null) {
  16. title.setSingleLine(false);
  17. title.setMaxLines(3);
  18. }
  19. UpdateNavbarStyle(view);
  20. }

  1. public void setNavbarStyle(int style){
  2. if(style > -1 && mNavbarStyle != style){
  3. mNavbarStyle = style;
  4. notifyChanged();//UpdateNavbarStyle(mViewGroup);
  5. }
  6. }
  7. private void UpdateNavbarStyle(View view){
  8. if(mNavbarStyle < 0){
  9. return;
  10. }
  11. ImageView tempview;
  12. switch(mNavbarStyle){
  13. case NAV_BAR_STYLE_0:
  14. tempview = (ImageView) view.findViewById(R.id.ramos_hide_navbar_left);
  15. tempview.setVisibility(View.VISIBLE);
  16. tempview = (ImageView) view.findViewById(R.id.ramos_hide_navbar_right);
  17. tempview.setVisibility(View.VISIBLE);
  18. tempview = (ImageView) view.findViewById(R.id.back);
  19. tempview.setImageResource(R.drawable.ic_sysbar_back);//ic_sysbar_back_right
  20. tempview = (ImageView) view.findViewById(R.id.recent_apps);
  21. tempview.setImageResource(R.drawable.ic_sysbar_recent);
  22. break;
  23. case NAV_BAR_STYLE_1:
  24. tempview = (ImageView) view.findViewById(R.id.ramos_hide_navbar_left);
  25. tempview.setVisibility(View.VISIBLE);
  26. tempview = (ImageView) view.findViewById(R.id.ramos_hide_navbar_right);
  27. tempview.setVisibility(View.VISIBLE);
  28. tempview = (ImageView) view.findViewById(R.id.back);
  29. tempview.setImageResource(R.drawable.ic_sysbar_recent); // ic_sysbar_back ic_sysbar_recent
  30. tempview = (ImageView) view.findViewById(R.id.recent_apps);
  31. tempview.setImageResource(R.drawable.ic_sysbar_back_right); //ic_sysbar_back_right ic_sysbar_recent
  32. break;
  33. case NAV_BAR_STYLE_2:
  34. tempview = (ImageView) view.findViewById(R.id.ramos_hide_navbar_left);
  35. tempview.setVisibility(View.INVISIBLE);
  36. tempview = (ImageView) view.findViewById(R.id.ramos_hide_navbar_right);
  37. tempview.setVisibility(View.VISIBLE);
  38. tempview = (ImageView) view.findViewById(R.id.back);
  39. tempview.setImageResource(R.drawable.ic_sysbar_back);//ic_sysbar_back_right
  40. tempview = (ImageView) view.findViewById(R.id.recent_apps);
  41. tempview.setImageResource(R.drawable.ic_sysbar_recent);
  42. break;
  43. case NAV_BAR_STYLE_3:
  44. tempview = (ImageView) view.findViewById(R.id.ramos_hide_navbar_left);
  45. tempview.setVisibility(View.VISIBLE);
  46. tempview = (ImageView) view.findViewById(R.id.ramos_hide_navbar_right);
  47. tempview.setVisibility(View.INVISIBLE);
  48. tempview = (ImageView) view.findViewById(R.id.back);
  49. tempview.setImageResource(R.drawable.ic_sysbar_back);//ic_sysbar_back_right
  50. tempview = (ImageView) view.findViewById(R.id.recent_apps);
  51. tempview.setImageResource(R.drawable.ic_sysbar_recent);
  52. break;
  53. case NAV_BAR_STYLE_4:
  54. tempview = (ImageView) view.findViewById(R.id.ramos_hide_navbar_left);
  55. tempview.setVisibility(View.INVISIBLE);
  56. tempview = (ImageView) view.findViewById(R.id.ramos_hide_navbar_right);
  57. tempview.setVisibility(View.VISIBLE);
  58. tempview = (ImageView) view.findViewById(R.id.back);
  59. tempview.setImageResource(R.drawable.ic_sysbar_recent); // ic_sysbar_back ic_sysbar_recent
  60. tempview = (ImageView) view.findViewById(R.id.recent_apps);
  61. tempview.setImageResource(R.drawable.ic_sysbar_back_right); //ic_sysbar_back_right ic_sysbar_recent
  62. break;
  63. case NAV_BAR_STYLE_5:
  64. tempview = (ImageView) view.findViewById(R.id.ramos_hide_navbar_left);
  65. tempview.setVisibility(View.VISIBLE);
  66. tempview = (ImageView) view.findViewById(R.id.ramos_hide_navbar_right);
  67. tempview.setVisibility(View.INVISIBLE);
  68. tempview = (ImageView) view.findViewById(R.id.back);
  69. tempview.setImageResource(R.drawable.ic_sysbar_recent); // ic_sysbar_back ic_sysbar_recent
  70. tempview = (ImageView) view.findViewById(R.id.recent_apps);
  71. tempview.setImageResource(R.drawable.ic_sysbar_back_right); //ic_sysbar_back_right ic_sysbar_recent
  72. break;
  73. case NAV_BAR_STYLE_6:
  74. tempview = (ImageView) view.findViewById(R.id.ramos_hide_navbar_left);
  75. tempview.setVisibility(View.INVISIBLE);
  76. tempview = (ImageView) view.findViewById(R.id.ramos_hide_navbar_right);
  77. tempview.setVisibility(View.INVISIBLE);
  78. tempview = (ImageView) view.findViewById(R.id.back);
  79. tempview.setImageResource(R.drawable.ic_sysbar_back);//ic_sysbar_back_right
  80. tempview = (ImageView) view.findViewById(R.id.recent_apps);
  81. tempview.setImageResource(R.drawable.ic_sysbar_recent);
  82. break;
  83. case NAV_BAR_STYLE_7:
  84. tempview = (ImageView) view.findViewById(R.id.ramos_hide_navbar_left);
  85. tempview.setVisibility(View.INVISIBLE);
  86. tempview = (ImageView) view.findViewById(R.id.ramos_hide_navbar_right);
  87. tempview.setVisibility(View.INVISIBLE);
  88. tempview = (ImageView) view.findViewById(R.id.back);
  89. tempview.setImageResource(R.drawable.ic_sysbar_recent); // ic_sysbar_back ic_sysbar_recent
  90. tempview = (ImageView) view.findViewById(R.id.recent_apps);
  91. tempview.setImageResource(R.drawable.ic_sysbar_back_right); //ic_sysbar_back_right ic_sysbar_recent
  92. break;
  93. default:
  94. break;
  95. }
  96. }

          在设置里面通过如下代码来保存期配置的值:

Settings.System.putInt(mActivity.getContentResolver(), Settings.System.RAMOS_NAVBAR_STYLE, style);

        (3)最后在NavigationBarView里面来实现其配置的虚拟按钮(NavigationBar):

         在NavigationBarView中通过ContentObserver来监控RAMOS_NAVBAR_STYLE值得变化,一旦变化,就会从新排序NavigationBarView中各个按钮的显示。

这里,我是通过替换他们View的ID、ImageResourc和KeyCode以及他们的长按短按事件ClickLisener。

          


         到这里就算完结了,不过有两个问题需要解决:

         其一:在输入法的弹出框出现的时候,来回隐藏和显示虚拟按钮(NavigationBar),会看到输入框下面有黑色背景或者显示不全等问题,如何解决呢?

                     其实这是因为输入法弹出框比较独立,没有能够试试刷新和布局造成的。解决办法是:在包com.android.internal.policy.impl的PhoneWindow类有一个方法:

private void updateNavigationGuard(WindowInsets insets)

                      在这个方法里面只需要做到每次虚拟按钮(NavigationBar)有变化时候调用该方法即可:requestFitSystemWindows();

         其二:每次要显示虚拟按钮(NavigationBar)时候,从底部往上滑动时候会触发屏幕中其他view的click或者move触摸事件,造成误点击了某个图标,滑动了一些菜单等问题,如何破解?

                     首先在PhoneWindow中拦截不需要的触摸操作,在PhoneWindow的DecorView中的onInterceptTouchEvent里面把不需要的触摸事件return true即可。

                     注意:DecorView是所有activity的显示view的父view.

                     这里难点就是如何判断一个触摸事件是不需要的,也就是说如何判断一个触摸事件是要显示虚拟按钮(NavigationBar)的 ,如果一个使用的操作(手势)是来显示虚拟按钮(NavigationBar)的,那么这个操作就不要用来做其他的,就可以把这次触摸操作当成不需要的操作了。因为通常情况下,你不可能又要显示虚拟按钮(NavigationBar),又要点击一个其他界面的按钮。

                     如何判断一个触摸(手势)是来显示虚拟按钮(NavigationBar)的呢? 这里需要通过上面说到的PhoneWindowManager和SystemGesturesPointerEventListener了。

                     大致的办法是:

                                           在SystemGesturesPointerEventListener识别显示虚拟按钮(NavigationBar)的手势,从底部滑动、从左、右边滑动,这里有一个要求,就是要尽快的识别出来,希望能在滑动的前3个MotionEvent事件识别出来,原来的从顶部往下滑动的手势识别需要6个MotionEvent事件以上,这是不够的。

                                            然后在PhoneWindowManager定义一个正在开始显示虚拟按钮(NavigationBar)的boolean startToShowNavbar变量,并定义一个public方法来判断startToShowNavbar的状态。

  1. private static boolean startToShowNavbar = false;
  2. //private static final int KEY_CODE_RAMOS_HIDE_NAVBAR = 1994;
  3. @Override
  4. public boolean hasShowingNavbar() {
  5. //Log.v("NavigationGuard", "RAMOS hasShowingNavbar startToShowNavbar="+startToShowNavbar);
  6. return startToShowNavbar;
  7. }
                                        如何复位 startToShowNavbar这个变量呢,就是说如何判断显示虚拟按钮(NavigationBar)已经完成呢?在PhoneWindowManager的方法layoutWindowLw被调用,并且在 layoutWindowLw中出现了TYPE_NAVIGATION_BAR,就复位。如下修改的截图:


                 

                          最后在PhoneWindow的DecorView中的onInterceptTouchEvent判断即可了,如下源码:

  1. private boolean getShowIngNavBar() {
  2. try {
  3. return WindowManagerHolder.sWindowManager.hasShowingNavbar();
  4. } catch (RemoteException ex) {
  5. Log.e(TAG, "RAMOS getShowIngNavBar:", ex);
  6. return false;
  7. }
  8. }


完毕!












声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小丑西瓜9/article/detail/219286
推荐阅读
相关标签
  

闽ICP备14008679号