Android之framework修改底部导航栏NavigationBar动态显示和隐藏

转载自:http://blog.csdn.net/way_ping_li/article/details/45727335  

大家都知道,Android从3.0版本开始就加入了NavigationBar,主要是为那些没有实体按键的设备提供虚拟按键,但是,它始终固定在底部,占用48dp的像素高度,尽管从android 4.4开始可以全透明,使用这一部分像素,但三个按钮始终悬浮在屏幕上,这对于有强迫症的朋友来说是无法忍受的。因此,本文的目的就是修改framework部分代码,可以动态隐藏和显示NavigationBar,同时又尽量不影响系统的正常。

主要思路:

在NavigationBar的布局左部加入一个Button(在SystemUI模块实现),点击隐藏NavigationBar,即将NavigationBar从WindowManager中移除掉。需要的时候,通过一个从屏幕底部向上的滑动手势(在policy模块实现)调出NavigationBar。如下两图对比所示:一张为移除前,另一张为移除后。


具体实现:

①.增加按钮实现动态隐藏,主要修改在frameworks/base/packages/SystemUI模块,首先我们增加一个按钮,主要修改

frameworks/base/packages/SystemUI/res/layout/navigation_bar.xml文件,图片资源和字符串我就不提了,具体如下:

[html]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. diff --git a/frameworks/base/packages/SystemUI/res/layout/navigation_bar.xml b/frameworks/base/packages/SystemUI/res/layout/navigation_bar.xml  
  2. index 16027d9..326aafc 100644  
  3. --- a/frameworks/base/packages/SystemUI/res/layout/navigation_bar.xml  
  4. +++ b/frameworks/base/packages/SystemUI/res/layout/navigation_bar.xml  
  5. @@ -42,12 +42,28 @@  
  6.              >  
  7.   
  8.              <!-- navigation controls -->  
  9. +           <!--BEGIN liweiping  
  10.              <View  
  11.                  android:layout_width="40dp"  
  12.                  android:layout_height="match_parent"  
  13.                  android:layout_weight="0"  
  14.                  android:visibility="invisible"  
  15.                  />  
  16. +           -->  
  17. +            <FrameLayout  
  18. +                android:layout_width="@dimen/navigation_extra_key_width"  
  19. +                android:layout_height="match_parent"  
  20. +                android:layout_weight="0" >  
  21. +                <ImageButton  
  22. +                    android:id="@+id/hide_bar_btn"  
  23. +                    android:layout_width="@dimen/navigation_extra_key_width"  
  24. +                    android:layout_height="match_parent"  
  25. +                    android:contentDescription="@string/accessibility_hide"  
  26. +                    android:src="@drawable/ic_sysbar_hide"  
  27. +                     />  
  28. +  
  29. +            </FrameLayout>  
  30. +           <!--END liweiping -->  
  31.              <com.android.systemui.statusbar.policy.KeyButtonView android:id="@+id/back"  
  32.                  android:layout_width="@dimen/navigation_key_width"  
  33.                  android:layout_height="match_parent"  
  34. @@ -246,12 +262,28 @@  
  35.                  android:layout_weight="0"  
  36.                  android:contentDescription="@string/accessibility_back"  
  37.                  />  
  38. +           <!--BEGIN liweiping  
  39.              <View  
  40.                  android:layout_height="40dp"  
  41.                  android:layout_width="match_parent"  
  42.                  android:layout_weight="0"  
  43.                  android:visibility="invisible"  
  44.                  />  
  45. +           -->  
  46. +            <FrameLayout  
  47. +                android:layout_weight="0"  
  48. +                android:layout_width="match_parent"  
  49. +                android:layout_height="40dp" >  
  50. +  
  51. +                <ImageButton  
  52. +                    android:id="@+id/hide_bar_btn"  
  53. +                    android:layout_width="match_parent"  
  54. +                    android:layout_height="40dp"  
  55. +                    android:contentDescription="@string/accessibility_hide"  
  56. +                    android:src="@drawable/ic_sysbar_hide_land"  
  57. +                     />  
  58. +            </FrameLayout>  
  59. +           <!--END liweiping -->  
  60.          </LinearLayout>  
  61.   
  62.          <!-- lights out layout to match exactly -->  

接下来修改frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java,为按钮提供一个接口,具体如下:

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. diff --git a/frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java  
  2. index 88e71e2..7545984 100644  
  3. --- a/frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java  
  4. +++ b/frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java  
  5. @@ -45,6 +45,7 @@ import com.android.systemui.R;  
  6.  import com.android.systemui.statusbar.BaseStatusBar;  
  7.  import com.android.systemui.statusbar.DelegateViewHelper;  
  8.  import com.android.systemui.statusbar.policy.DeadZone;  
  9. +import com.android.systemui.statusbar.policy.KeyButtonRipple;  
  10.  import com.android.systemui.statusbar.policy.KeyButtonView;  
  11.   
  12.  import java.io.FileDescriptor;  
  13. @@ -265,6 +266,13 @@ public class NavigationBarView extends LinearLayout {  
  14.      public View getImeSwitchButton() {  
  15.          return mCurrentView.findViewById(R.id.ime_switcher);  
  16.      }  
  17. +    //BEGIN liweiping  
  18. +    public View getHideBarButton() {  
  19. +       View view = mCurrentView.findViewById(R.id.hide_bar_btn);  
  20. +       view.setBackground(new KeyButtonRipple(getContext(), view));  
  21. +        return view;  
  22. +    }  
  23. +    //END liweiping  
  24.   
  25.      private void getIcons(Resources res) {  
  26.          mBackIcon = res.getDrawable(R.drawable.ic_sysbar_back);  
  27. @@ -412,7 +420,6 @@ public class NavigationBarView extends LinearLayout {  
  28.          mCurrentView = mRotatedViews[Surface.ROTATION_0];  
  29.   
  30.          getImeSwitchButton().setOnClickListener(mImeSwitcherClickListener);  
  31. -  
  32.          updateRTLOrder();  
  33.      }  



最后便是在frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java实现点击事件了:

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. +    private final OnClickListener mHideBarClickListener = new OnClickListener() {  
  2. +        @Override  
  3. +        public void onClick(View view) {  
  4. +           Log.i("way""mHideBarClickListener  onClick...");  
  5. +           removeNavigationBar();  
  6. +        }  
  7. +    };  
  8.   
  9. +    private void removeNavigationBar() {  
  10. +        if (DEBUG) Log.d(TAG, "removeNavigationBar: about to remove " + mNavigationBarView);  
  11. +        if (mNavigationBarView == nullreturn;  
  12. +  
  13. +        mWindowManager.removeView(mNavigationBarView);  
  14. +        mNavigationBarView = null;  
  15. +    }  



到此,隐藏NavigationBar告一段落了。


②.接下来便是显示NavigationBar,这个修改相对复杂一点。因为此时NavigationBar处于不可见状态,我们无法通过增加按钮的方式让其显示,但是我们知道,状态栏下拉通过手势向下滑动即可。因此很容易便想到通过手势从屏幕底部向上滑动来显示NavigationBar。我的想法是在policy模块中增加一个接口,通过frameworks/base/services/core/java/com/android/server/statusbar/StatusBarManagerService.java服务传递到状态栏中,从而触发显示NavigationBar事件。

也许大家会有疑问,为什么是在policy模块修改?其实我这只是一种解决方案,因为我知道

frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java 有现成的手势滑动接口。其实你也可以SystemUI中增加一个这样的事件,我们需要的就是这么一个触发事件。

PhoneWindowManager.java的修改主要是实现onSwipeFromBottom(竖屏时)和onSwipeFromRight(横屏时)两个接口,然后调用showNavigationBar,在showNavigationBar函数中,我们调用StatusBarManagerService服务中的showNavigationBar函数,具体如下:

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. diff --git a/frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java  
  2. index bb53e12..907202d 100644  
  3. --- a/frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java  
  4. +++ b/frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java  
  5. @@ -1241,13 +1241,27 @@ public class PhoneWindowManager implements WindowManagerPolicy {  
  6.                      public void onSwipeFromBottom() {  
  7.                          if (mNavigationBar != null && mNavigationBarOnBottom) {  
  8.                              requestTransientBars(mNavigationBar);  
  9. +                            Log.i("way""onSwipeFromBottom... mNavigationBar != null && mNavigationBarOnBottom");  
  10.                          }  
  11. +                        //BEGIN liweiping  
  12. +                        else{  
  13. +                           Log.i("way""onSwipeFromBottom...");  
  14. +                           showNavigationBar();  
  15. +                        }  
  16. +                        //END liweiping  
  17.                      }  
  18.                      @Override  
  19.                      public void onSwipeFromRight() {  
  20.                          if (mNavigationBar != null && !mNavigationBarOnBottom) {  
  21.                              requestTransientBars(mNavigationBar);  
  22. +                            Log.i("way""onSwipeFromRight... mNavigationBar != null && !mNavigationBarOnBottom");  
  23. +                        }  
  24. +                        //BEGIN liweiping  
  25. +                        else{  
  26. +                           Log.i("way""onSwipeFromRight...");  
  27. +                           showNavigationBar();  
  28.                          }  
  29. +                        //END liweiping  
  30.                      }  
  31.                      @Override  
  32.                      public void onDebug() {  
  33. @@ -1293,7 +1307,24 @@ public class PhoneWindowManager implements WindowManagerPolicy {  
  34.              goingToSleep(WindowManagerPolicy.OFF_BECAUSE_OF_USER);  
  35.          }  
  36.      }  
  37. -  
  38. +    //BEGIN liweiping  
  39. +    private void showNavigationBar(){  
  40. +        mHandler.post(new Runnable() {  
  41. +            @Override  
  42. +            public void run() {  
  43. +                try {  
  44. +                    IStatusBarService statusbar = getStatusBarService();  
  45. +                    if (statusbar != null) {  
  46. +                        statusbar.showNavigationBar();  
  47. +                    }  
  48. +                } catch (RemoteException e) {  
  49. +                    // re-acquire status bar service next time it is needed.  
  50. +                    mStatusBarService = null;  
  51. +                }  
  52. +            }  
  53. +        });  
  54. +    }  
  55. +    //END liweiping  
  56.      private void updateKeyAssignments() {  
  57.          final boolean hasMenu = (mDeviceHardwareKeys & KEY_MASK_MENU) != 0;  
  58.          final boolean hasHome = (mDeviceHardwareKeys & KEY_MASK_HOME) != 0;  



这时事件传递到了StatusBarManagerService中,我们来看看StatusBarManagerService.java如何实现showNavigationBar:

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. diff --git a/frameworks/base/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/frameworks/base/services/core/java/com/android/server/statusbar/StatusBarManagerService.java  
  2. index f85e2d9..3f75840 100644  
  3. --- a/frameworks/base/services/core/java/com/android/server/statusbar/StatusBarManagerService.java  
  4. +++ b/frameworks/base/services/core/java/com/android/server/statusbar/StatusBarManagerService.java  
  5. @@ -366,6 +366,27 @@ public class StatusBarManagerService extends IStatusBarService.Stub {  
  6.                      "WindowManager.LayoutParams");  
  7.          }  
  8.      }  
  9. +    //BEGIN liweiping  
  10. +    @Override  
  11. +    public void showNavigationBar() {  
  12. +        enforceStatusBar();  
  13. +  
  14. +        android.util.Log.d("way", TAG + " showNavigationBar...");  
  15. +  
  16. +        synchronized(mLock) {  
  17. +            mHandler.post(new Runnable() {  
  18. +                    public void run() {  
  19. +                        if (mBar != null) {  
  20. +                            try {  
  21. +                                mBar.showNavigationBar();  
  22. +                            } catch (RemoteException ex) {  
  23. +                            }  
  24. +                        }  
  25. +                    }  
  26. +                });  
  27. +        }  
  28. +    }  
  29. +    //END liweiping  
  30.   
  31.      private void updateUiVisibilityLocked(final int vis, final int mask) {  
  32.          if (mSystemUiVisibility != vis) {  



从上述代码可以看出,StatusBarManagerService只是起到一个传递作用,将消息传递到StatusBar中,最终的实现是在SystemUI模块的frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java,如下所示:

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. diff --git a/frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java  
  2. index 9db875f..4f24b6e 100644  
  3. --- a/frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java  
  4. +++ b/frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java  
  5. @@ -56,6 +56,7 @@ public class CommandQueue extends IStatusBar.Stub {  
  6.      private static final int MSG_BUZZ_BEEP_BLINKED          = 15 << MSG_SHIFT;  
  7.      private static final int MSG_NOTIFICATION_LIGHT_OFF     = 16 << MSG_SHIFT;  
  8.      private static final int MSG_NOTIFICATION_LIGHT_PULSE   = 17 << MSG_SHIFT;  
  9. +    private static final int MSG_SHOW_NAVIGATIONBAR   = 18 << MSG_SHIFT;//ADD liweiping  
  10.   
  11.      public static final int FLAG_EXCLUDE_NONE = 0;  
  12.      public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0;  
  13. @@ -83,6 +84,7 @@ public class CommandQueue extends IStatusBar.Stub {  
  14.          public void animateCollapsePanels(int flags);  
  15.          public void animateExpandSettingsPanel();  
  16.          public void setSystemUiVisibility(int vis, int mask);  
  17. +        public void showNavigationBar();//ADD liweiping  
  18.          public void topAppWindowChanged(boolean visible);  
  19.          public void setImeWindowStatus(IBinder token, int vis, int backDisposition,  
  20.                  boolean showImeSwitcher);  
  21. @@ -154,6 +156,14 @@ public class CommandQueue extends IStatusBar.Stub {  
  22.              mHandler.obtainMessage(MSG_SET_SYSTEMUI_VISIBILITY, vis, mask, null).sendToTarget();  
  23.          }  
  24.      }  
  25. +    //BEGIN liweiping  
  26. +    public void showNavigationBar() {  
  27. +        synchronized (mList) {  
  28. +            mHandler.removeMessages(MSG_SHOW_NAVIGATIONBAR);  
  29. +            mHandler.sendEmptyMessage(MSG_SHOW_NAVIGATIONBAR);  
  30. +        }  
  31. +    }  
  32. +    //END liweiping  
  33.   
  34.      public void topAppWindowChanged(boolean menuVisible) {  
  35.          synchronized (mList) {  
  36. @@ -283,6 +293,11 @@ public class CommandQueue extends IStatusBar.Stub {  
  37.                  case MSG_SET_SYSTEMUI_VISIBILITY:  
  38.                      mCallbacks.setSystemUiVisibility(msg.arg1, msg.arg2);  
  39.                      break;  
  40. +                //BEGIN liweiping  
  41. +                case MSG_SHOW_NAVIGATIONBAR:  
  42. +                   mCallbacks.showNavigationBar();  
  43. +                   break;  
  44. +                //END liweiping  
  45.                  case MSG_TOP_APP_WINDOW_CHANGED:  
  46.                      mCallbacks.topAppWindowChanged(msg.arg1 != 0);  
  47.                      break;  



CommandQueue.java收到了这个消息之后,又回调给了base/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java,绕了大半天,消息终于回来了,我们就是需要在PhoneStatusBar.java实现显示NavigationBar的函数了:

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. +    @Override // CommandQueue  
  2. +    public void showNavigationBar() {  
  3. +       Log.i("way", TAG + " showNavigationBar...");  
  4. +       forceAddNavigationBar();  
  5. +    }  
  6.   
  7. +    private void forceAddNavigationBar() {  
  8. +        // If we have no Navbar view and we should have one, create it  
  9. +        if (mNavigationBarView != null) {  
  10. +            return;  
  11. +        }  
  12. +  
  13. +        mNavigationBarView =  
  14. +                (NavigationBarView) View.inflate(mContext, R.layout.navigation_bar, null);  
  15.   
  16. +        mNavigationBarView.setDisabledFlags(mDisabled);  
  17. +        mNavigationBarView.setBar(this);  
  18. +        addNavigationBar(true); // dynamically adding nav bar, reset System UI visibility!  
  19. +    }  
  20.   
  21. +    private void prepareNavigationBarView(boolean forceReset) {  
  22. +        mNavigationBarView.reorient();  
  23. +        mNavigationBarView.getRecentsButton().setOnClickListener(mRecentsClickListener);  
  24. +        mNavigationBarView.getRecentsButton().setOnTouchListener(mRecentsPreloadOnTouchListener);  
  25. +        mNavigationBarView.getRecentsButton().setLongClickable(true);  
  26. +        mNavigationBarView.getRecentsButton().setOnLongClickListener(mLongPressBackRecentsListener);  
  27. +        mNavigationBarView.getBackButton().setLongClickable(true);  
  28. +        mNavigationBarView.getBackButton().setOnLongClickListener(mLongPressBackRecentsListener);  
  29. +        mNavigationBarView.getHomeButton().setOnTouchListener(mHomeActionListener);  
  30. +        mNavigationBarView.getHideBarButton().setOnClickListener(mHideBarClickListener);//ADD liweiping  
  31. +  
  32. +        if (forceReset) {  
  33. +            // Nav Bar was added dynamically - we need to reset the mSystemUiVisibility and call  
  34. +            // setSystemUiVisibility so that mNavigationBarMode is set to the correct value  
  35. +           Log.i("way""prepareNavigationBarView mNavigationBarMode = "+ mNavigationBarMode + " mSystemUiVisibility = " + mSystemUiVisibility + " mNavigationIconHints = " + mNavigationIconHints);  
  36. +           mNavigationBarMode = 0;  
  37. +             
  38. +            int newVal = mSystemUiVisibility;  
  39. +            mSystemUiVisibility = View.SYSTEM_UI_FLAG_VISIBLE;  
  40. +            setSystemUiVisibility(newVal, /*SYSTEM_UI_VISIBILITY_MASK*/0xffffffff);  
  41. +            int hints = mNavigationIconHints;  
  42. +            mNavigationIconHints = 0;  
  43. +            setNavigationIconHints(hints);  
  44. +            topAppWindowChanged(mShowMenu);  
  45. +        }  
  46. +  
  47. +        updateSearchPanel();  
  48. +    }  
  49. +  
  50. +    // For small-screen devices (read: phones) that lack hardware navigation buttons  
  51. +    private void addNavigationBar(boolean forceReset) {  
  52. +        if (DEBUG) Log.v(TAG, "addNavigationBar: about to add " + mNavigationBarView);  
  53. +        if (mNavigationBarView == nullreturn;  
  54. +  
  55. +        prepareNavigationBarView(forceReset);  
  56. +  
  57. +        mWindowManager.addView(mNavigationBarView, getNavigationBarLayoutParams());  
  58. +    }  
  59. +    //END liweiping  


需要注意的是:

①显示NavigationBar时,需要重新实例化一次NavigationBarView,我之前有试过移除NavigationBarView后未置空,下次添加时直接使用,会出现状态栏重启的情况,具体原因未知,log显示动画播放错误之类。

②重新添加NavigationBarView时需要恢复NavigationBarView之前的状态,比如说隐藏前时是透明的、显示输入法按钮、菜单键等等。

③本文是在Android5.0的代码上修改的,其他版本未验证。

④本文仅是提供一种思路,并非最优方案。

⑤转载请注明出处:http://blog.csdn.net/way_ping_li

  • 3
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
要取消 Android 底部导航栏的半透明/毛玻璃效果,可以在 styles.xml 文件中的 AppTheme 样式中添加以下属性: ```xml <item name="android:navigationBarColor">@android:color/transparent</item> <item name="android:windowTranslucentNavigation">false</item> ``` 其中,`android:navigationBarColor` 属性设置为 `@android:color/transparent` 表示将导航栏的颜色设置为透明,`android:windowTranslucentNavigation` 属性设置为 `false` 表示取消导航栏的半透明效果。 完整的 AppTheme 样式如下: ```xml <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar"> <!-- 取消导航栏的半透明效果 --> <item name="android:navigationBarColor">@android:color/transparent</item> <item name="android:windowTranslucentNavigation">false</item> <!-- 其他属性 --> <!-- ... --> </style> ``` 如果想要实现 Android 底部导航栏毛玻璃效果,可以使用 Android 系统提供的 `BlurDrawable` 类来实现。具体实现步骤可以参考以下代码: ```java // 创建一个 BlurDrawable 对象 BlurDrawable blurDrawable = new BlurDrawable(); // 设置模糊半径和颜色 blurDrawable.setBlurRadius(10); blurDrawable.setColor(Color.parseColor("#66000000")); // 将 BlurDrawable 对象设置为导航栏的背景 getWindow().setNavigationBarColor(Color.TRANSPARENT); getWindow().setNavigationBarDividerColor(Color.TRANSPARENT); getWindow().setNavigationBarColor(blurDrawable.getColor()); getWindow().setNavigationBarDividerColor(blurDrawable.getColor()); ``` 其中,`BlurDrawable` 对象的 `setBlurRadius` 方法用于设置模糊半径,`setColor` 方法用于设置毛玻璃颜色。最后将 `BlurDrawable` 对象设置为导航栏的背景即可。 需要注意的是,`BlurDrawable` 类是 Android 11 引入的新类,如果你的应用最低支持 Android 版本不是 11,需要在 build.gradle 文件中添加以下依赖: ```groovy implementation 'androidx.appcompat:appcompat:1.4.0' implementation 'androidx.core:core-ktx:1.6.0' implementation 'androidx.window:window:1.0.0' ``` 并且在代码中导入以下类: ```java import androidx.appcompat.graphics.drawable.BlurDrawable; import androidx.window.WindowManager; ```
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值