此篇文章只做记录一下这个功能自己实现的喜悦。如果能帮助其他人,那也荣幸之至。我会写的比较细,拿到源码谁都能改。要先谢谢网络上两位大神的博文给予的帮助。
请参考 https://blog.csdn.net/cuckoochun/article/details/84109895
https://blog.csdn.net/u012932409/article/details/89156391
效果:
从图中我们可以看出的效果是在底部导航栏添加一个按钮,他的功能是点击隐藏底部NavigationBar,然后我们在上滑底部,再显示NavigationBar。基本就是这样的一个流程。具体怎么实现,看下面》》》》》
以我之前写文章的习惯,我会先把需要用到的文件列出来(如果你在看这篇文章,那就可以先把这些文件全部打开了)。本文基于MTK平台Android 8.1实现的功能,修改文件均在vendor目录下面(千万不要像我刚开始直接在framework目录,写了一天发现怎么都不对)!
vendor\mediatek\proprietary\packages\apps\SystemUI\src\com\android\systemui\statusbar\phone\NavigationBarInflaterView.java
vendor\mediatek\proprietary\packages\apps\SystemUI\src\com\android\systemui\statusbar\phone\NavigationBarView.java
vendor\mediatek\proprietary\packages\apps\SystemUI\src\com\android\systemui\statusbar\phone\NavigationBarFragment.java
vendor\mediatek\proprietary\packages\apps\SystemUI\src\com\android\systemui\statusbar\phone\StatusBar.java
frameworks\base\services\core\java\com\android\server\policy\PhoneWindowManager.java(注意这不在vendor)
vendor\mediatek\proprietary\packages\apps\SystemUI\res\values-sw372dp\config.xml
以上就是基本源码里面自带的目录文件,需要自己增加的,后面会详细写到。
我们看到的NavigationBar他是Android 系统Systemui这个app里面的一部分,NavigationBar默认底部是三个按钮,back,home,recent。
我们第一步先在NavigationBar里面添加一个hide的按钮:
首先添加布局:
vendor\mediatek\proprietary\packages\apps\SystemUI\res\layout\hide_show.xml
我们所看到的back、home、recent都是上面这样类似的布局,独立存在,为了方便,我把系统中home的布局贴出来对比
KeyButtonView这个自定义的view里面处理了所有这些按钮需要处理的响应操作。也许会有人问没看到设置图片,莫急后面会讲到。
完成这一步我们就要看看这个NavigationBar的加载流程了。
首先在StatusBar.java这个文件中(Android7.0不是这个),有这样一段代码
try {
boolean showNav = mWindowManagerService.hasNavigationBar();
if (DEBUG) Log.v(TAG, "hasNavigationBar=" + showNav);
if (showNav) {
createNavigationBar();
}
} catch (RemoteException ex) {
// no window manager? good luck with that
}
他先判断需不需要去创建NavigationBar,我们不管其他情况,只讲顺序创建即可,对于createNavigationBar();方法我们需要一探究竟。
protected void createNavigationBar() {//初始化,我们可以看到,他其实是在创建一个fragment
mNavigationBarView = NavigationBarFragment.create(mContext, (tag, fragment) -> {
mNavigationBar = (NavigationBarFragment) fragment;
if (mLightBarController != null) {
mNavigationBar.setLightBarController(mLightBarController);
}
mNavigationBar.setCurrentSysuiVisibility(mSystemUiVisibility);
});
}
创建一个fragment,就是我们上面目录中的NavigationBarFragment。所有的NavigationBar都在这个fragment里面实现。其中加载底部按键的的代码在NavigationBarInflaterView文件(所有文件都是上文中提到的目录文件)中,那么我们再看看NavigationBarInflaterView中做了些什么操作。
SystemUI\src\com\android\systemui\statusbar\phone\NavigationBarInflaterView.java
他通过方法getDefaultLayout()去获取需要展示几个布局,我们需要添加一个按钮,就在config_navBarLayout里面添加这个按钮的名字,因为这个文件在系统中有很多,如果我们不确定当前设备支持哪一种,就打印出这个String,看看具体是哪个文件中的,我的平板支持的是res\values-sw372dp\config.xml这个文件里面的配置。
protected String getDefaultLayout() {
return mContext.getString(R.string.config_navBarLayout);
}
我贴出config里面的设置来:
left[.15W],hide[.5WC];back[.5WC];home[.5WC];recent[.5WC],right[.15W]
加了一个hide,其中left好right用的是W,中间的布局都是WC,5WC,表示布局占得weight。其中的值,大家可以适当修改,目前贴出的是我自己已经修改过的文件。
protected void inflateLayout(String newLayout) {
mCurrentLayout = newLayout;
if (newLayout == null) {
newLayout = getDefaultLayout();
}
Log.w(TAG,"newLayout=="+newLayout);
String[] sets = newLayout.split(GRAVITY_SEPARATOR, 4);
String[] hide = sets[0].split(BUTTON_SEPARATOR);//加一个隐藏按钮
String[] start = sets[1].split(BUTTON_SEPARATOR);
String[] center = sets[2].split(BUTTON_SEPARATOR);
String[] end = sets[3].split(BUTTON_SEPARATOR);
inflateButtons(hide, mRot0.findViewById(R.id.ends_group), isRot0Landscape, false);// add by jsb 加载隐藏按钮
inflateButtons(hide, mRot90.findViewById(R.id.ends_group), !isRot0Landscape, false);// add by jsb
// Inflate these in start to end order or accessibility traversal will be messed up.
inflateButtons(start, mRot0.findViewById(R.id.ends_group), isRot0Landscape, false);
inflateButtons(start, mRot90.findViewById(R.id.ends_group), !isRot0Landscape, false);
inflateButtons(center, mRot0.findViewById(R.id.ends_group), isRot0Landscape, false);
inflateButtons(center, mRot90.findViewById(R.id.ends_group), !isRot0Landscape, false);
/* addGravitySpacer(mRot0.findViewById(R.id.ends_group));
addGravitySpacer(mRot90.findViewById(R.id.ends_group)); */
inflateButtons(end, mRot0.findViewById(R.id.ends_group), isRot0Landscape, false);
inflateButtons(end, mRot90.findViewById(R.id.ends_group), !isRot0Landscape, false);
}
inflateButtons是加载填充按钮的,其中我们需要注意的修改有下面几点:
通过inflateButtons(); 添加了一个hide按钮,
原本的inflateButtons(center, mRot0.findViewById(R.id.ends_group), isRot0Landscape, false);是inflateButtons(center, mRot0.findViewById(R.id.center_group), isRot0Landscape, false);大家注意一下这里,就是我们看到的中间的那个home按键,在初始代码里,他是加载在布局center_group中的,这个布局大家可以去源码里面的布局查看,他是固定在中间的,所以如果不改掉这个东西,我们就会看到home键一直固定在那里。这里一定要注意了。!!!!!!!!!!!!!!!
加载完button后,就在createView()方法里面添加下面代码
else if (HIDE.equals(button)){// add by jsb for hide navigationBar
v = inflater.inflate(R.layout.hide_show, parent, false);
}//如果读到的initbuttons是hide,那么就加载我们自己刚才创建的布局文件
在全局加一个
public static final String HIDE = "hide";//一定要和config文件中一致
这个文件的修改就已经基本完成了,那么接下来就是看怎么加载这些图片了。我们事先要准备好两种png,
ic_sysbar_down和ic_sysbar_down_dark,一黑一白,只要和系统中一样就行,大小的话各个系统应该不一样,我的像素的22*22,先准备好了。
然后就要看文件systemui\statusbar\phone\NavigationBarView.java
首先看构造方法里面的修改
public NavigationBarView(Context context, AttributeSet attrs) {
super(context, attrs);
//省略部分代码------------------------------------
updateIcons(context, Configuration.EMPTY, mConfiguration);
mBarTransitions = new NavigationBarTransitions(this);
mButtonDispatchers.put(R.id.hide, new ButtonDispatcher(R.id.hide));// add by jsb for hide navigationbar
// getHideButton().setLongClickable(false);// add by jsb for hide navigationbar
mButtonDispatchers.put(R.id.back, new ButtonDispatcher(R.id.back));
mButtonDispatchers.put(R.id.home, new ButtonDispatcher(R.id.home));
mButtonDispatchers.put(R.id.recent_apps, new ButtonDispatcher(R.id.recent_apps));
mButtonDispatchers.put(R.id.menu, new ButtonDispatcher(R.id.menu));
mButtonDispatchers.put(R.id.ime_switcher, new ButtonDispatcher(R.id.ime_switcher));
mButtonDispatchers.put(R.id.accessibility_button,
new ButtonDispatcher(R.id.accessibility_button));
}
通过mButtonDispatchers.put(R.id.hide, new ButtonDispatcher(R.id.hide));去put我们自定义的hide布局按钮。
之后添加一个公共方法
public ButtonDispatcher getHideButton() {// add by jsb
return mButtonDispatchers.get(R.id.hide);
}
在setDisabledFlags方法中设置可见getHideButton()
public void setDisabledFlags(int disabledFlags, boolean force) {
if (!force && mDisabledFlags == disabledFlags) return;
//省略部分代码------------------------
getHideButton().setVisibility(disableHome ? View.INVISIBLE : View.VISIBLE);
getBackButton().setVisibility(disableBack ? View.INVISIBLE : View.VISIBLE);
getHomeButton().setVisibility(disableHome ? View.INVISIBLE : View.VISIBLE);
getRecentsButton().setVisibility(disableRecent ? View.INVISIBLE : View.VISIBLE);
}
接下来就是设置图片了:
private void updateIcons(Context ctx, Configuration oldConfig, Configuration newConfig) {
//已经省略了部分代码
Drawable iconLight, iconDark;
//// M: Navigation bar plugin changes
iconLight = mNavBarPlugin.getBackImage(
ctx.getDrawable(R.drawable.ic_sysbar_back));
iconDark = mNavBarPlugin.getBackImage(
ctx.getDrawable(R.drawable.ic_sysbar_back_dark));
mBackIcon = getDrawable(iconLight,iconDark);
//mBackIcon = getDrawable(ctx, R.drawable.ic_sysbar_back, R.drawable.ic_sysbar_back_dark);
mBackLandIcon = mBackIcon;
iconLight = mNavBarPlugin.getBackImeImage(
ctx.getDrawable(R.drawable.ic_sysbar_down));
iconDark = mNavBarPlugin.getBackImeImage(
ctx.getDrawable(R.drawable.ic_sysbar_down_dark));
// mHideDown = getDrawable(ctx,R.drawable.ic_sysbar_down,R.drawable.ic_sysbar_down_dark);//jsb
mHideDown = getDrawable(iconLight,iconDark);
//mBackAltIcon = getDrawable(ctx,
// R.drawable.ic_sysbar_back_ime, R.drawable.ic_sysbar_back_ime_dark);
iconLight = mNavBarPlugin.getBackImeImage(
ctx.getDrawable(R.drawable.ic_sysbar_back_ime));
iconDark = mNavBarPlugin.getBackImeImage(
ctx.getDrawable(R.drawable.ic_sysbar_back_ime_dark));
}
}
在updateIcons()方法里面设置我们之前准备的两张图片,
iconLight = mNavBarPlugin.getBackImeImage(
ctx.getDrawable(R.drawable.ic_sysbar_down));
iconDark = mNavBarPlugin.getBackImeImage(
ctx.getDrawable(R.drawable.ic_sysbar_down_dark));
mHideDown = getDrawable(iconLight,iconDark);
最后在setNavigationIconHints方法里面设置getHideButton().setImageDrawable(mHideDown);
public void setNavigationIconHints(int hints, boolean force) {
//已经省略的部分代码---------------------------
getBackButton().setImageDrawable(backIcon);
updateRecentsIcon();
if (mUseCarModeUi) {
getHomeButton().setImageDrawable(mHomeCarModeIcon);
} else {
getHomeButton().setImageDrawable(mHomeDefaultIcon);
}
getHideButton().setImageDrawable(mHideDown);//add jsb 添加图片应用
// The Accessibility button always overrides the appearance of the IME switcher
final boolean showImeButton =
!mShowAccessibilityButton && ((hints & StatusBarManager.NAVIGATION_HINT_IME_SHOWN)
!= 0);
}
通过上面的步骤,我们已经可以看到底部导航栏的新添加的隐藏按钮了。接下来就是实现点击隐藏,和上滑显示的功能。
然后我们用Android应用的思路,既然他是一个按钮,那么肯定可以在fragment里面设置监听事件,所以我们回到NavigationBarFragment。
通过下面代码来这是底部各个按钮的监听事件,我们先看源码中如何修改:
private void prepareNavigationBarView() {
mNavigationBarView.reorient();
ButtonDispatcher recentsButton = mNavigationBarView.getRecentsButton();
recentsButton.setOnClickListener(this::onRecentsClick);
recentsButton.setOnTouchListener(this::onRecentsTouch);
recentsButton.setLongClickable(true);
recentsButton.setOnLongClickListener(this::onLongPressBackRecents);
//添加隐藏按钮的点击监听事件
ButtonDispatcher hideButton = mNavigationBarView.getHideButton();
hideButton.setOnClickListener(this::onHideClick);
ButtonDispatcher backButton = mNavigationBarView.getBackButton();
backButton.setLongClickable(true);
backButton.setOnLongClickListener(this::onLongPressBackRecents);
ButtonDispatcher homeButton = mNavigationBarView.getHomeButton();
homeButton.setOnTouchListener(this::onHomeTouch);
homeButton.setOnLongClickListener(this::onHomeLongClick);
ButtonDispatcher accessibilityButton = mNavigationBarView.getAccessibilityButton();
accessibilityButton.setOnClickListener(this::onAccessibilityClick);
accessibilityButton.setOnLongClickListener(this::onAccessibilityLongClick);
updateAccessibilityServicesState(mAccessibilityManager);
}
在fragment中prepareNavigationBarView()方法是来处理所有按钮的点击监听事件的,我们仿造他,也自己加一个。但是我们只需要单击事件,长摁的处理不做。
创建一个onHideClick点击方法
一个OnSureHideListener接口
addOnSureHideListener初始化接口的方法,共外部调用
private OnSureHideListener sureHideListener;//自定义接口监听
private void onHideClick(View v) {
Log.d("jsb_nav","点击了jsb按钮");
if(sureHideListener != null){
sureHideListener.onClick();
}
}
//写一个接口回调,监听hide按钮的点击
public interface OnSureHideListener{
void onClick();
}
public void addOnSureHideListener(OnSureHideListener listener){
sureHideListener = listener;
}
在hide被点击后,触发接口回调监听,那么回调监听的处理,需要回到systemui\statusbar\phone\StatusBar.java中
protected void createNavigationBar() {
mNavigationBarView = NavigationBarFragment.create(mContext, (tag, fragment) -> {
mNavigationBar = (NavigationBarFragment) fragment;
if (mLightBarController != null) {
mNavigationBar.setLightBarController(mLightBarController);
}
mNavigationBar.setCurrentSysuiVisibility(mSystemUiVisibility);
//添加回调监听
mNavigationBar.addOnSureHideListener(() -> {
Log.d("jsb_nav","自定义接口回调成功");
if (mNavigationBarView == null) return;
mWindowManager.removeViewImmediate(mNavigationBarView);
mNavigationBarView = null;
});
});
}
当监听到点击事件后,我们就隐藏mNavigationBarView;
上滑监听显示的话,就需要用到广播了,首先在frameworks\base\services\core\java\com\android\server\policy\PhoneWindowManager.java文件里面添加滑动监听触发广播
mSystemGestures = new SystemGesturesPointerEventListener(context,
new SystemGesturesPointerEventListener.Callbacks() {
@Override
public void onSwipeFromTop() {
if (mStatusBar != null) {
requestTransientBars(mStatusBar);
}
}
@Override
public void onSwipeFromBottom() {
if (mNavigationBar != null && mNavigationBarPosition == NAV_BAR_BOTTOM) {
requestTransientBars(mNavigationBar);
}
/* jsb add */
Intent intent = new Intent();
intent.setAction("SHOW_NAVIGATION_BAR");
context.sendBroadcast(intent);
Log.i("jsb_nav","上滑");
}
该监听方法已经在源码里面存着,我们只需要在onSwipeFromBottom(),里面触发时发送一个SHOW_NAVIGATION_BAR的广播
在StatusBar.java添加注册广播(在文件中搜索一下filter,就可以看到源码里面已经有注册的广播,我们添加一个action就行)
filter.addAction("SHOW_NAVIGATION_BAR");// add by jsb
最后响应广播,显示NavigationBar。(这个代码在哪里加??请在文件中搜索mBroadcastReceiver)。
else if(action.equals("SHOW_NAVIGATION_BAR")){
Log.d("jsb_nav","接收到了广播SHOW_NAVIGATION_BAR");
if (mNavigationBarView != null) return;
createNavigationBar();
}
好了,以上就是全部的修改过程了,我这边目前算是比较完整的实现了整个功能。
写完已经是中午了。。。。划了一上午水,老板知道要拉我再去喝个茶了。拜拜~~~~
今夕是何夕~晚风过花庭~,哈哈