1500字范文,内容丰富有趣,写作好帮手!
1500字范文 > android 虚拟导航按钮(NavigationBar)可手动隐藏开发

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

时间:2024-05-16 20:00:26

相关推荐

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() { @Overridepublic 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 // CommandQueuepublic 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;}@Overridepublic void onClick() {if (mListener != null) {mListener.onRadioButtonClicked(this);}}@Overrideprotected 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_righttempview = (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_backic_sysbar_recenttempview = (ImageView) view.findViewById(R.id.recent_apps);tempview.setImageResource(R.drawable.ic_sysbar_back_right); //ic_sysbar_back_right ic_sysbar_recentbreak;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_righttempview = (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_righttempview = (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_backic_sysbar_recenttempview = (ImageView) view.findViewById(R.id.recent_apps);tempview.setImageResource(R.drawable.ic_sysbar_back_right); //ic_sysbar_back_right ic_sysbar_recentbreak; 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_backic_sysbar_recenttempview = (ImageView) view.findViewById(R.id.recent_apps);tempview.setImageResource(R.drawable.ic_sysbar_back_right); //ic_sysbar_back_right ic_sysbar_recentbreak; 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_righttempview = (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_backic_sysbar_recenttempview = (ImageView) view.findViewById(R.id.recent_apps);tempview.setImageResource(R.drawable.ic_sysbar_back_right); //ic_sysbar_back_right ic_sysbar_recentbreak;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;@Overridepublic 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;}}

完毕!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。