Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> android 虛擬導航按鈕(NavigationBar)可手動隱藏開發

android 虛擬導航按鈕(NavigationBar)可手動隱藏開發

編輯:關於Android編程

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

\

 

上圖的底部虛擬導航按鈕的左、右邊有兩個按鈕\點擊這個按鈕,虛擬按鈕就會消失,當從屏幕底部向上滑動時候,虛擬按鈕就就會出現,這個消失和出現的過程中整個屏幕的布局都會從新計算。

接下來,分下面幾個點來說一下具體實現的效果。

(一):首先第一個問題是如何讓虛擬按鈕(NavigationBar)消失。通過分析android源代碼可以發現虛擬按鈕(NavigationBar)的加入和實現是由系統app:systemui來完成的。

在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);
從上面的代碼你會發現一個特別的變量:mTempNavigationBarView,其實這就是一個NavigationBarView。這是因為:為了顯示NavigationBarView的時候能夠快一點,所以每次在通過mWindowManager來removeView掉NavigationBarView後,我會自動去創建一個新的NavigationBarView等待下次顯示用,這樣一來下次要顯示的時候直接使用即可,就不用創建了。

 

注意:每次通過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;
        }
    }
\

 

完畢!

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved