大家都知道,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