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

大家都知道,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文件,图片资源和字符串我就不提了,具体如下:

diff --git a/frameworks/base/packages/SystemUI/res/layout/navigation_bar.xml b/frameworks/base/packages/SystemUI/res/layout/navigation_bar.xml
index 16027d9..326aafc 100644
--- a/frameworks/base/packages/SystemUI/res/layout/navigation_bar.xml
+++ b/frameworks/base/packages/SystemUI/res/layout/navigation_bar.xml
@@ -42,12 +42,28 @@
             >

             <!-- navigation controls -->
+           <!--BEGIN liweiping
             <View
                 android:layout_width="40dp"
                 android:layout_height="match_parent"
                 android:layout_weight="0"
                 android:visibility="invisible"
                 />
+           -->
+            <FrameLayout
+                android:layout_width="@dimen/navigation_extra_key_width"
+                android:layout_height="match_parent"
+                android:layout_weight="0" >
+                <ImageButton
+                    android:id="@+id/hide_bar_btn"
+                    android:layout_width="@dimen/navigation_extra_key_width"
+                    android:layout_height="match_parent"
+                    android:contentDescription="@string/accessibility_hide"
+                    android:src="@drawable/ic_sysbar_hide"
+                     />
+
+            </FrameLayout>
+           <!--END liweiping -->
             <com.android.systemui.statusbar.policy.KeyButtonView android:id="@+id/back"
                 android:layout_width="@dimen/navigation_key_width"
                 android:layout_height="match_parent"
@@ -246,12 +262,28 @@
                 android:layout_weight="0"
                 android:contentDescription="@string/accessibility_back"
                 />
+           <!--BEGIN liweiping
             <View
                 android:layout_height="40dp"
                 android:layout_width="match_parent"
                 android:layout_weight="0"
                 android:visibility="invisible"
                 />
+           -->
+            <FrameLayout
+                android:layout_weight="0"
+                android:layout_width="match_parent"
+                android:layout_height="40dp" >
+
+                <ImageButton
+                    android:id="@+id/hide_bar_btn"
+                    android:layout_width="match_parent"
+                    android:layout_height="40dp"
+                    android:contentDescription="@string/accessibility_hide"
+                    android:src="@drawable/ic_sysbar_hide_land"
+                     />
+            </FrameLayout>
+           <!--END liweiping -->
         </LinearLayout>

         <!-- lights out layout to match exactly -->

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

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
index 88e71e2..7545984 100644
--- 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
@@ -45,6 +45,7 @@ import com.android.systemui.R;
 import com.android.systemui.statusbar.BaseStatusBar;
 import com.android.systemui.statusbar.DelegateViewHelper;
 import com.android.systemui.statusbar.policy.DeadZone;
+import com.android.systemui.statusbar.policy.KeyButtonRipple;
 import com.android.systemui.statusbar.policy.KeyButtonView;

 import java.io.FileDescriptor;
@@ -265,6 +266,13 @@ public class NavigationBarView extends LinearLayout {
     public View getImeSwitchButton() {
         return mCurrentView.findViewById(R.id.ime_switcher);
     }
+    //BEGIN liweiping
+    public View getHideBarButton() {
+       View view = mCurrentView.findViewById(R.id.hide_bar_btn);
+       view.setBackground(new KeyButtonRipple(getContext(), view));
+        return view;
+    }
+    //END liweiping

     private void getIcons(Resources res) {
         mBackIcon = res.getDrawable(R.drawable.ic_sysbar_back);
@@ -412,7 +420,6 @@ public class NavigationBarView extends LinearLayout {
         mCurrentView = mRotatedViews[Surface.ROTATION_0];

         getImeSwitchButton().setOnClickListener(mImeSwitcherClickListener);
-
         updateRTLOrder();
     }

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

+    private final OnClickListener mHideBarClickListener = new OnClickListener() {
+        @Override
+        public void onClick(View view) {
+           Log.i("way", "mHideBarClickListener  onClick...");
+           removeNavigationBar();
+        }
+    };

+    private void removeNavigationBar() {
+        if (DEBUG) Log.d(TAG, "removeNavigationBar: about to remove " + mNavigationBarView);
+        if (mNavigationBarView == null) return;
+
+        mWindowManager.removeView(mNavigationBarView);
+        mNavigationBarView = null;
+    }

到此,隐藏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函数,具体如下:

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
index bb53e12..907202d 100644
--- a/frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
+++ b/frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
@@ -1241,13 +1241,27 @@ public class PhoneWindowManager implements WindowManagerPolicy {
                     public void onSwipeFromBottom() {
                         if (mNavigationBar != null && mNavigationBarOnBottom) {
                             requestTransientBars(mNavigationBar);
+                            Log.i("way", "onSwipeFromBottom... mNavigationBar != null && mNavigationBarOnBottom");
                         }
+                        //BEGIN liweiping
+                        else{
+                           Log.i("way", "onSwipeFromBottom...");
+                           showNavigationBar();
+                        }
+                        //END liweiping
                     }
                     @Override
                     public void onSwipeFromRight() {
                         if (mNavigationBar != null && !mNavigationBarOnBottom) {
                             requestTransientBars(mNavigationBar);
+                            Log.i("way", "onSwipeFromRight... mNavigationBar != null && !mNavigationBarOnBottom");
+                        }
+                        //BEGIN liweiping
+                        else{
+                           Log.i("way", "onSwipeFromRight...");
+                           showNavigationBar();
                         }
+                        //END liweiping
                     }
                     @Override
                     public void onDebug() {
@@ -1293,7 +1307,24 @@ public class PhoneWindowManager implements WindowManagerPolicy {
             goingToSleep(WindowManagerPolicy.OFF_BECAUSE_OF_USER);
         }
     }
-
+    //BEGIN liweiping
+    private void showNavigationBar(){
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    IStatusBarService statusbar = getStatusBarService();
+                    if (statusbar != null) {
+                        statusbar.showNavigationBar();
+                    }
+                } catch (RemoteException e) {
+                    // re-acquire status bar service next time it is needed.
+                    mStatusBarService = null;
+                }
+            }
+        });
+    }
+    //END liweiping
     private void updateKeyAssignments() {
         final boolean hasMenu = (mDeviceHardwareKeys & KEY_MASK_MENU) != 0;
         final boolean hasHome = (mDeviceHardwareKeys & KEY_MASK_HOME) != 0;

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

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
index f85e2d9..3f75840 100644
--- a/frameworks/base/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/frameworks/base/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -366,6 +366,27 @@ public class StatusBarManagerService extends IStatusBarService.Stub {
                     "WindowManager.LayoutParams");
         }
     }
+    //BEGIN liweiping
+    @Override
+    public void showNavigationBar() {
+        enforceStatusBar();
+
+        android.util.Log.d("way", TAG + " showNavigationBar...");
+
+        synchronized(mLock) {
+            mHandler.post(new Runnable() {
+                    public void run() {
+                        if (mBar != null) {
+                            try {
+                                mBar.showNavigationBar();
+                            } catch (RemoteException ex) {
+                            }
+                        }
+                    }
+                });
+        }
+    }
+    //END liweiping

     private void updateUiVisibilityLocked(final int vis, final int mask) {
         if (mSystemUiVisibility != vis) {

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

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
index 9db875f..4f24b6e 100644
--- a/frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -56,6 +56,7 @@ public class CommandQueue extends IStatusBar.Stub {
     private static final int MSG_BUZZ_BEEP_BLINKED          = 15 << MSG_SHIFT;
     private static final int MSG_NOTIFICATION_LIGHT_OFF     = 16 << MSG_SHIFT;
     private static final int MSG_NOTIFICATION_LIGHT_PULSE   = 17 << MSG_SHIFT;
+    private static final int MSG_SHOW_NAVIGATIONBAR   = 18 << MSG_SHIFT;//ADD liweiping

     public static final int FLAG_EXCLUDE_NONE = 0;
     public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0;
@@ -83,6 +84,7 @@ public class CommandQueue extends IStatusBar.Stub {
         public void animateCollapsePanels(int flags);
         public void animateExpandSettingsPanel();
         public void setSystemUiVisibility(int vis, int mask);
+        public void showNavigationBar();//ADD liweiping
         public void topAppWindowChanged(boolean visible);
         public void setImeWindowStatus(IBinder token, int vis, int backDisposition,
                 boolean showImeSwitcher);
@@ -154,6 +156,14 @@ public class CommandQueue extends IStatusBar.Stub {
             mHandler.obtainMessage(MSG_SET_SYSTEMUI_VISIBILITY, vis, mask, null).sendToTarget();
         }
     }
+    //BEGIN liweiping
+    public void showNavigationBar() {
+        synchronized (mList) {
+            mHandler.removeMessages(MSG_SHOW_NAVIGATIONBAR);
+            mHandler.sendEmptyMessage(MSG_SHOW_NAVIGATIONBAR);
+        }
+    }
+    //END liweiping

     public void topAppWindowChanged(boolean menuVisible) {
         synchronized (mList) {
@@ -283,6 +293,11 @@ public class CommandQueue extends IStatusBar.Stub {
                 case MSG_SET_SYSTEMUI_VISIBILITY:
                     mCallbacks.setSystemUiVisibility(msg.arg1, msg.arg2);
                     break;
+                //BEGIN liweiping
+                case MSG_SHOW_NAVIGATIONBAR:
+                   mCallbacks.showNavigationBar();
+                   break;
+                //END liweiping
                 case MSG_TOP_APP_WINDOW_CHANGED:
                     mCallbacks.topAppWindowChanged(msg.arg1 != 0);
                     break;

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

+    @Override // CommandQueue
+    public void showNavigationBar() {
+       Log.i("way", TAG + " showNavigationBar...");
+       forceAddNavigationBar();
+    }

+    private void forceAddNavigationBar() {
+        // If we have no Navbar view and we should have one, create it
+        if (mNavigationBarView != null) {
+            return;
+        }
+
+        mNavigationBarView =
+                (NavigationBarView) View.inflate(mContext, R.layout.navigation_bar, null);

+        mNavigationBarView.setDisabledFlags(mDisabled);
+        mNavigationBarView.setBar(this);
+        addNavigationBar(true); // dynamically adding nav bar, reset System UI visibility!
+    }

+    private void prepareNavigationBarView(boolean forceReset) {
+        mNavigationBarView.reorient();
+        mNavigationBarView.getRecentsButton().setOnClickListener(mRecentsClickListener);
+        mNavigationBarView.getRecentsButton().setOnTouchListener(mRecentsPreloadOnTouchListener);
+        mNavigationBarView.getRecentsButton().setLongClickable(true);
+        mNavigationBarView.getRecentsButton().setOnLongClickListener(mLongPressBackRecentsListener);
+        mNavigationBarView.getBackButton().setLongClickable(true);
+        mNavigationBarView.getBackButton().setOnLongClickListener(mLongPressBackRecentsListener);
+        mNavigationBarView.getHomeButton().setOnTouchListener(mHomeActionListener);
+        mNavigationBarView.getHideBarButton().setOnClickListener(mHideBarClickListener);//ADD liweiping
+
+        if (forceReset) {
+            // Nav Bar was added dynamically - we need to reset the mSystemUiVisibility and call
+            // setSystemUiVisibility so that mNavigationBarMode is set to the correct value
+           Log.i("way", "prepareNavigationBarView mNavigationBarMode = "+ mNavigationBarMode + " mSystemUiVisibility = " + mSystemUiVisibility + " mNavigationIconHints = " + mNavigationIconHints);
+           mNavigationBarMode = 0;
+
+            int newVal = mSystemUiVisibility;
+            mSystemUiVisibility = View.SYSTEM_UI_FLAG_VISIBLE;
+            setSystemUiVisibility(newVal, /*SYSTEM_UI_VISIBILITY_MASK*/0xffffffff);
+            int hints = mNavigationIconHints;
+            mNavigationIconHints = 0;
+            setNavigationIconHints(hints);
+            topAppWindowChanged(mShowMenu);
+        }
+
+        updateSearchPanel();
+    }
+
+    // For small-screen devices (read: phones) that lack hardware navigation buttons
+    private void addNavigationBar(boolean forceReset) {
+        if (DEBUG) Log.v(TAG, "addNavigationBar: about to add " + mNavigationBarView);
+        if (mNavigationBarView == null) return;
+
+        prepareNavigationBarView(forceReset);
+
+        mWindowManager.addView(mNavigationBarView, getNavigationBarLayoutParams());
+    }
+    //END liweiping

需要注意的是:

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

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

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

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

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

时间: 2024-10-25 11:54:46

Android之framework修改底部导航栏NavigationBar动态显示和隐藏的相关文章

Android 修改底部导航栏navigationbar的颜色

Android 修改底部导航栏navigationbar的颜色 getWindow().setNavigationBarColor(Color.BLUE); //写法一 getWindow().setNavigationBarColor(getResources().getColor(R.color.black_12));//写法二

关于在Android中如何设置底部导航栏

Android应用底部导航栏(选项卡)实例 现在很多android的应用都采用底部导航栏的功能,这样可以使得用户在使用过程中随意切换不同的页面,现在我采用TabHost组件来自定义一个底部的导航栏的功能. 我们先看下该demo实例的框架图:   其中各个类的作用以及资源文件就不详细解释了,还有资源图片(在该Demo中借用了其它应用程序的资源图片)也不提供了,大家可以自行更换自己需要的资源图片.直接上各个布局文件或各个类的代码: [1]  res/layout目录下的maintabs.xml 源码

Android开源项目——带图标文字的底部导航栏IconTabPageIndicator

接下来的博客计划是,在<Android官方技术文档翻译>之间会发一些Android开源项目的介绍,直接剩下的几篇Android技术文档发完,然后就是Android开源项目和Gradle翻译了.当然,其他的文章笔记也会偶尔发一下. 本文原创,转载请注明在CSDN上的出处: http://blog.csdn.net/maosidiaoxian/article/details/42638245 简介 本篇文章介绍的是一个底部导航栏,叫IconTabPageIndicator,一个带图标文字的导航栏.

Android基础入门教程——5.2.2 Fragment实例精讲——底部导航栏的实现(方法2)

Android基础入门教程--5.2.2 Fragment实例精讲--底部导航栏的实现(方法2) 标签(空格分隔): Android基础入门教程 本节引言: 上一节中我们使用LinearLayout + TextView实现了底部导航栏的效果,每次点击我们都要重置 所有TextView的状态,然后选中点击的TextView,有点麻烦是吧,接下来我们用另一种方法: RadioGroup + RadioButton来实现我们上一节的效果! 1.一些碎碎念 本节用到的是实现单选效果的RadioButt

【Android基础篇】TabHost实现底部导航栏

在App应用中,导航栏往往是用于解决功能分块的最佳控件,而底部导航栏更是导航栏中最常用的,因为它位于屏幕底部,用户操作起来会很方便. 下面介绍一下使用Android控件TabHost实现底部导航栏的方法. TabHost可以在控件库里直接拖到页面上,非常方便,但拖出来的是顶部导航栏,如下图所示: 到这里就可以开始实现底部导航栏了,我们首先转到它的XML布局代码里,然后修改成下面这样: <FrameLayout xmlns:android="http://schemas.android.co

Android实习札记(5)---Fragment之底部导航栏的实现

Android实习札记(5)---Fragment之底部导航栏的实现 --转载请注明出处:coder-pig 在Part 4我们回顾了一下Fragment的基本概念,在本节中我们就来学习Fragment应用的简单例子吧! 就是使用Fragment来实现简单的底部导航栏,先贴下效果图: 看上去很简单,实现起来也是很简单的哈!那么接着下来就看下实现的流程图吧: 实现流程图: 看完流程图是不是有大概的思路了,那么接着就开始代码的编写吧: 代码实现: ①先写布局,布局的话很简单,一个FrameLayou

Android仿小米商城底部导航栏之二(BottomNavigationBar、ViewPager和Fragment的联动使用)

简介 在前文<Android仿小米商城底部导航栏(基于BottomNavigationBar)>我们使用BottomNavigationBar控件模仿实现了小米商城底部导航栏效果.接下来更进一步的,我们将通过BottomNavigationBar控件和ViewPager空间的联动使用来实现主界面的滑动导航. 导航是移动应用最重要的方面之一,对用户体验是良好还是糟糕起着至关重要的作用.好的导航可以让一款应用更加易用并且让用户快速上手.相反,糟糕的应用导航很容易让人讨厌,并遭到用户的抛弃.为了打造

Android Fragment解析及UI底部导航栏实例

import android.os.Bundle; import android.support.v4.app.FragmentActivity; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentTransaction; import android.view.View; import android.view.View.OnClickListener; import and

Android应用底部导航栏(选项卡)实例

现在很多android的应用都采用底部导航栏的功能,这样可以使得用户在使用过程中随意切换不同的页面,现在我采用TabHost组件来自定义一个底部的导航栏的功能. 我们先看下该demo实例的框架图: 其中各个类的作用以及资源文件就不详细解释了,还有资源图片(在该Demo中借用了其它应用程序的资源图片)也不提供了,大家可以自行更换自己需要的资源图片.直接上各个布局文件或各个类的代码: [1]  res/layout目录下的 maintabs.xml 源码: <?xml version="1.0