Android之framework修改底部导航栏NavigationBar动态显示和隐藏 谁借莪1个温暖的怀抱¢ 2022-07-15 12:57 1049阅读 0赞 转载自: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。如下两图对比所示:一张为移除前,另一张为移除后。 ![Center][] **具体实现:** ①.增加按钮实现动态隐藏,主要修改在frameworks/base/packages/SystemUI模块,首先我们增加一个按钮,主要修改 frameworks/base/packages/SystemUI/res/layout/navigation\_bar.xml文件,图片资源和字符串我就不提了,具体如下: **\[html\]** [view plain][] [copy][view plain] [![在CODE上查看代码片][CODE]][CODE_CODE] [![派生到我的代码片][ico_fork.svg]][ico_fork.svg 1] 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][view plain] [![在CODE上查看代码片][CODE]][CODE_CODE] [![派生到我的代码片][ico_fork.svg]][ico_fork.svg 1] 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][view plain] [![在CODE上查看代码片][CODE]][CODE_CODE] [![派生到我的代码片][ico_fork.svg]][ico_fork.svg 1] 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 == **null**) **return**; 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][view plain] [![在CODE上查看代码片][CODE]][CODE_CODE] [![派生到我的代码片][ico_fork.svg]][ico_fork.svg 1] 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][view plain] [![在CODE上查看代码片][CODE]][CODE_CODE] [![派生到我的代码片][ico_fork.svg]][ico_fork.svg 1] 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][view plain] [![在CODE上查看代码片][CODE]][CODE_CODE] [![派生到我的代码片][ico_fork.svg]][ico_fork.svg 1] 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][view plain] [![在CODE上查看代码片][CODE]][CODE_CODE] [![派生到我的代码片][ico_fork.svg]][ico_fork.svg 1] 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 == **null**) **return**; 54. \+ 55. \+ prepareNavigationBarView(forceReset); 56. \+ 57. \+ mWindowManager.addView(mNavigationBarView, getNavigationBarLayoutParams()); 58. \+ \} 59. \+ //END liweiping **需要注意的是:** ①显示NavigationBar时,需要重新实例化一次NavigationBarView,我之前有试过移除NavigationBarView后未置空,下次添加时直接使用,会出现状态栏重启的情况,具体原因未知,log显示动画播放错误之类。 ②重新添加NavigationBarView时需要恢复NavigationBarView之前的状态,比如说隐藏前时是透明的、显示输入法按钮、菜单键等等。 ③本文是在Android5.0的代码上修改的,其他版本未验证。 ④本文仅是提供一种思路,并非最优方案。 [Android]: http://lib.csdn.net/base/15 [Center]: /images/20220715/463ac2f026a9427c8e0b6dd908e55d6e.png [view plain]: http://blog.csdn.net/way_ping_li/article/details/45727335# [CODE]: https://code.csdn.net/assets/CODE_ico.png [CODE_CODE]: https://code.csdn.net/snippets/667047 [ico_fork.svg]: https://code.csdn.net/assets/ico_fork.svg [ico_fork.svg 1]: https://code.csdn.net/snippets/667047/fork
还没有评论,来说两句吧...