android 虚拟导航按钮(NavigationBar)可手动隐藏开发

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;
        }
    }


完毕!












  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值