編輯:關於Android編程
NavigationBar可以手動隱藏,隨著華為榮耀手機有了這個特點後,目前有很多android手機都有該特性。如下截圖所示:

上圖的底部虛擬導航按鈕的左、右邊有兩個按鈕
點擊這個按鈕,虛擬按鈕就會消失,當從屏幕底部向上滑動時候,虛擬按鈕就就會出現,這個消失和出現的過程中整個屏幕的布局都會從新計算。
接下來,分下面幾個點來說一下具體實現的效果。
在systemui這個app裡面有一個類PhoneStatusBar,在這個類被創建的時候會判斷當前系統是否定義了虛擬按鈕(NavigationBar),如果定義了就添加,否則不添加。源碼如下。
判斷是否有定義虛擬按鈕(NavigationBar):
boolean showNav = mWindowManagerService.hasNavigationBar();創建虛擬按鈕(NavigationBar)的代碼:
int layoutId = R.layout.navigation_bar;
if(RecentsActivity.FLOAT_WINDOW_SUPPORT){
layoutId = R.layout.navigation_bar_float_window;
}
mNavigationBarView =
(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)的函數:
private void showNavigationBar(final int type){
if(isKeyguardLocked()){
return;
}
startToShowNavbar = true;
//Slog.i("yu_PhoneWindowManager", "showNavigationBar: NavigationBarMoveType="+NavigationBarMoveType);
NavigationBarMoveType = type;
if(mNavigationBar == null) {
Slog.i("yu_PhoneWindowManager", "RAMOS showNavigationBar: showNavigationBar");
//Log.v("NavigationGuard", "RAMOS showNavigationBar startToShowNavbar="+startToShowNavbar);
mHandler.post(new Runnable() {
@Override
public void run() {
try {
IStatusBarService statusbar = getStatusBarService();
if (statusbar != null) {
//Slog.i("yu_PhoneWindowManager", "showNavigationBar");
statusbar.showNavigationBar(type);
}
} catch (RemoteException e) {
// re-acquire status bar service next time it is needed.
mStatusBarService = null;
}
}
});
}
}
注意:在PhoneWindowManager類裡面可以通過判斷這個對象mNavigationBar是否為空來確認虛擬按鈕(NavigationBar)是否被隱藏。
其他的showNavigationBar方法的聲明和定義你只需仿照hideRecentApps來做就可以了。
最後在PhoneStatusBar裡面實現具體的虛擬按鈕(NavigationBar)顯示即可。
比如,下面是我的實現方法:
@Override // CommandQueue
public void showNavigationBar(int type) {
//Log.i("way", TAG + " showNavigationBar...");
Log.i("yu_PhoneStatusBar", "showNavigationBar type="+type);
if (mNavigationBarView != null) {
try {
mWindowManagerService.StartToShowNavbar(type);
} catch (RemoteException ex) {
}
return;
}
if(mTempNavigationBarView == null){
makeNewNavigationBar();
}
if(mTempNavigationBarView != null){
mNavigationBarView = mTempNavigationBarView;
//mNavigationBarView.setVisibility(View.VISIBLE);
mWindowManager.addView(mNavigationBarView, mNavigationBarLayoutParams);
prepareNavigationBarView(true);
mNavigationBarView.setDisabledFlags(mDisabled);
//mNavigationBarView.reorient();
//mNavigationBarView.notifyScreenOn(true);
mTempNavigationBarView = null;
}else{
Log.i("yu_PhoneStatusBar", "showNavigationBar: ERROR");
}
}
重要是這這一個語句:mWindowManager.addView(mNavigationBarView, mNavigationBarLayoutParams);
注意:每次通過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關鍵代碼:
public RamosRadioNavbarStylePreference(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
setWidgetLayoutResource(R.layout.preference_widget_radiobutton);
}
void setOnClickListener(OnClickListener listener) {
mListener = listener;
}
@Override
public void onClick() {
if (mListener != null) {
mListener.onRadioButtonClicked(this);
}
}
@Override
protected void onBindView(View view) {
super.onBindView(view);
mViewGroup = view;
TextView title = (TextView) view.findViewById(android.R.id.title);
if (title != null) {
title.setSingleLine(false);
title.setMaxLines(3);
}
UpdateNavbarStyle(view);
}
public void setNavbarStyle(int style){
if(style > -1 && mNavbarStyle != style){
mNavbarStyle = style;
notifyChanged();//UpdateNavbarStyle(mViewGroup);
}
}
private void UpdateNavbarStyle(View view){
if(mNavbarStyle < 0){
return;
}
ImageView tempview;
switch(mNavbarStyle){
case NAV_BAR_STYLE_0:
tempview = (ImageView) view.findViewById(R.id.ramos_hide_navbar_left);
tempview.setVisibility(View.VISIBLE);
tempview = (ImageView) view.findViewById(R.id.ramos_hide_navbar_right);
tempview.setVisibility(View.VISIBLE);
tempview = (ImageView) view.findViewById(R.id.back);
tempview.setImageResource(R.drawable.ic_sysbar_back);//ic_sysbar_back_right
tempview = (ImageView) view.findViewById(R.id.recent_apps);
tempview.setImageResource(R.drawable.ic_sysbar_recent);
break;
case NAV_BAR_STYLE_1:
tempview = (ImageView) view.findViewById(R.id.ramos_hide_navbar_left);
tempview.setVisibility(View.VISIBLE);
tempview = (ImageView) view.findViewById(R.id.ramos_hide_navbar_right);
tempview.setVisibility(View.VISIBLE);
tempview = (ImageView) view.findViewById(R.id.back);
tempview.setImageResource(R.drawable.ic_sysbar_recent); // ic_sysbar_back ic_sysbar_recent
tempview = (ImageView) view.findViewById(R.id.recent_apps);
tempview.setImageResource(R.drawable.ic_sysbar_back_right); //ic_sysbar_back_right ic_sysbar_recent
break;
case NAV_BAR_STYLE_2:
tempview = (ImageView) view.findViewById(R.id.ramos_hide_navbar_left);
tempview.setVisibility(View.INVISIBLE);
tempview = (ImageView) view.findViewById(R.id.ramos_hide_navbar_right);
tempview.setVisibility(View.VISIBLE);
tempview = (ImageView) view.findViewById(R.id.back);
tempview.setImageResource(R.drawable.ic_sysbar_back);//ic_sysbar_back_right
tempview = (ImageView) view.findViewById(R.id.recent_apps);
tempview.setImageResource(R.drawable.ic_sysbar_recent);
break;
case NAV_BAR_STYLE_3:
tempview = (ImageView) view.findViewById(R.id.ramos_hide_navbar_left);
tempview.setVisibility(View.VISIBLE);
tempview = (ImageView) view.findViewById(R.id.ramos_hide_navbar_right);
tempview.setVisibility(View.INVISIBLE);
tempview = (ImageView) view.findViewById(R.id.back);
tempview.setImageResource(R.drawable.ic_sysbar_back);//ic_sysbar_back_right
tempview = (ImageView) view.findViewById(R.id.recent_apps);
tempview.setImageResource(R.drawable.ic_sysbar_recent);
break;
case NAV_BAR_STYLE_4:
tempview = (ImageView) view.findViewById(R.id.ramos_hide_navbar_left);
tempview.setVisibility(View.INVISIBLE);
tempview = (ImageView) view.findViewById(R.id.ramos_hide_navbar_right);
tempview.setVisibility(View.VISIBLE);
tempview = (ImageView) view.findViewById(R.id.back);
tempview.setImageResource(R.drawable.ic_sysbar_recent); // ic_sysbar_back ic_sysbar_recent
tempview = (ImageView) view.findViewById(R.id.recent_apps);
tempview.setImageResource(R.drawable.ic_sysbar_back_right); //ic_sysbar_back_right ic_sysbar_recent
break;
case NAV_BAR_STYLE_5:
tempview = (ImageView) view.findViewById(R.id.ramos_hide_navbar_left);
tempview.setVisibility(View.VISIBLE);
tempview = (ImageView) view.findViewById(R.id.ramos_hide_navbar_right);
tempview.setVisibility(View.INVISIBLE);
tempview = (ImageView) view.findViewById(R.id.back);
tempview.setImageResource(R.drawable.ic_sysbar_recent); // ic_sysbar_back ic_sysbar_recent
tempview = (ImageView) view.findViewById(R.id.recent_apps);
tempview.setImageResource(R.drawable.ic_sysbar_back_right); //ic_sysbar_back_right ic_sysbar_recent
break;
case NAV_BAR_STYLE_6:
tempview = (ImageView) view.findViewById(R.id.ramos_hide_navbar_left);
tempview.setVisibility(View.INVISIBLE);
tempview = (ImageView) view.findViewById(R.id.ramos_hide_navbar_right);
tempview.setVisibility(View.INVISIBLE);
tempview = (ImageView) view.findViewById(R.id.back);
tempview.setImageResource(R.drawable.ic_sysbar_back);//ic_sysbar_back_right
tempview = (ImageView) view.findViewById(R.id.recent_apps);
tempview.setImageResource(R.drawable.ic_sysbar_recent);
break;
case NAV_BAR_STYLE_7:
tempview = (ImageView) view.findViewById(R.id.ramos_hide_navbar_left);
tempview.setVisibility(View.INVISIBLE);
tempview = (ImageView) view.findViewById(R.id.ramos_hide_navbar_right);
tempview.setVisibility(View.INVISIBLE);
tempview = (ImageView) view.findViewById(R.id.back);
tempview.setImageResource(R.drawable.ic_sysbar_recent); // ic_sysbar_back ic_sysbar_recent
tempview = (ImageView) view.findViewById(R.id.recent_apps);
tempview.setImageResource(R.drawable.ic_sysbar_back_right); //ic_sysbar_back_right ic_sysbar_recent
break;
default:
break;
}
}
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的狀態。
private static boolean startToShowNavbar = false;
//private static final int KEY_CODE_RAMOS_HIDE_NAVBAR = 1994;
@Override
public boolean hasShowingNavbar() {
//Log.v("NavigationGuard", "RAMOS hasShowingNavbar startToShowNavbar="+startToShowNavbar);
return startToShowNavbar;
}
如何復位startToShowNavbar這個變量呢,就是說如何判斷顯示虛擬按鈕(NavigationBar)已經完成呢?在PhoneWindowManager的方法layoutWindowLw被調用,並且在layoutWindowLw中出現了TYPE_NAVIGATION_BAR,就復位。如下修改的截圖:

最後在PhoneWindow的DecorView中的onInterceptTouchEvent判斷即可了,如下源碼:
private boolean getShowIngNavBar() {
try {
return WindowManagerHolder.sWindowManager.hasShowingNavbar();
} catch (RemoteException ex) {
Log.e(TAG, "RAMOS getShowIngNavBar:", ex);
return false;
}
}

完畢!
Android Fragment動態創建詳解及示例代碼
Android Fragment 動態創建Fragment是activity的界面中的一部分或一種行為。可以把多個Fragment組合到一個activity中來創建一個多
android動畫——屬性動畫(Property Animation)
相對與視圖動畫 ,屬性動畫(android3.0提出的) 使用條件:完全彌補了View anim System的缺陷,你可以為一個對象的任何屬性添加動畫,(View或者非
收藏的Android非常好用的組件或者框架。
收藏的Android非常好用的組件或者框架。 android框架 先說兩個網站: http://www.androidviews.net/ 很好的國外開源代碼站,就是訪
android使用ItemDecoration給RecyclerView 添加水印
前言項目中有使用到水印效果,如下圖所示。在實現過程中,最終選用ItemDecoration來實現,其中有兩大步驟:自定義Drawable來完成水印圖片、使用ItemDec