今天给大家带来一个有趣的实验,基于android N原生代码,实现动态显示和隐藏navigationbar的功能,先说下实现思路,
那么最最重要的就是分析navigationbar的显示过程,从下面开始
##navigationbar的创建过程
在android中navigationbar是在SystemUI的PhoneStatusBar类加载显示的,如下:
PhoneStatusBar#makeStatusBarView
protected PhoneStatusBarView makeStatusBarView() {
......
try {
boolean showNav = mWindowManagerService.hasNavigationBar();
if (DEBUG) Log.v(TAG, "hasNavigationBar=" + showNav);
if (showNav) {
createNavigationBarView(context);
}
} catch (RemoteException ex) {
// no window manager? good luck with that
}
......
return mStatusBarView;
}
这里根据读取到的配置文件决定是否显示navigationbar
mWindowManagerService.hasNavigationBar();
PhoneWindowManager#hasNavigationBar
@Override
public boolean hasNavigationBar() {
return mHasNavigationBar;
}
mHasNavigationBar = res.getBoolean(com.android.internal.R.bool.config_showNavigationBar);
因此,我们可以修改frameworks/base/core/res/res/values文件中config.xml的"config_showNavigationBar"来决定是否显示navigationbar
当然,也可以通过SystemProperties的配置
##增加SystemProperties属性
在/system/sepolicy/property_contexts中添加
persist.shownav. u:object_r:system_prop:s0
使用make -j4全编代码
然后在应用中设置和获取属性:
SystemProperties.set("persist.shownav.enable","false");
SystemProperties.getBoolean("persist.shownav.enable", true)
这样做虽然可以显示和隐藏navigationbar,但是有很大的缺陷,因为我们不能动态监听SystemProperties中的值发生变化,只能是每次生成的image文件,是打开或者关闭,因此如果想要让navigationbar的显示和隐藏由用户动态控制,是不可以的。
##动态显示和隐藏navigationbar
这里,其实我觉得如果要实现动态的改变和隐藏,可以添加一个类似于是否显示和隐藏的系统设置,下面实现动态显示和隐藏navigationbar,我们知道在android中,同样可以通过"Settings.System.putInt"或者"Settings.System.putString"来设置参数的存储,最终是存储到了SettingsProvider里
###在settings中添加设置项
这里我把设置选项添加到了DisplaySettings中
####添加字符串
packages/apps/Settings/res/values/strings.xml
"whether show nav"
####在布局中添加设置选项
packages/apps/Settings/res/xml/display_settings.xml
####在DisplaySettings中实现
public class DisplaySettings extends SettingsPreferenceFragment implements
Preference.OnPreferenceChangeListener, Indexable {
private SwitchPreference mshowNavPreference;
@Override
public void onCreate(Bundle savedInstanceState) {
....
mshowNavPreference = (SwitchPreference) findPreference("show_nav");
if (mshowNavPreference != null) {
// 显示之前的设置
String isShownav = Settings.System.getString(getContentResolver(),"show_navigation_bar");
mshowNavPreference.setChecked("true".equals(isShownav) ? true : false);
}
mshowNavPreference.setOnPreferenceChangeListener(this);
....
}
@Override
public boolean onPreferenceChange(Preference preference, Object objValue) {
....
if (preference == mshowNavPreference) {
boolean auto = (Boolean) objValue;
if (preference == mshowNavPreference) {
boolean auto = (Boolean) objValue;
Settings.System.putString(getContentResolver(),"show_navigation_bar",String.valueOf(auto));
}
}
....
}
}
###在SettingsProvider中添加字段
数据库中数据的默认数据在frameworks/base/packages/SettingsProvider/res/values/defaults.xml中定义,因此我们需要在defaults.xml中添加默认值
true
在frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.Java中的loadSystemSettings()方法中加入新字段的添加代码
loadStringSetting(stmt, "show_navigation_bar",
R.string.def_show_nav_bar);
然后编译SettingsProvider完成之后,push apk到手机即可
在/frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/show_navigation_bar.java中监听"show_navigation_bar"字段值的变化
public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
DragDownHelper.DragDownCallback, ActivityStarter, OnUnlockMethodChangedListener,
HeadsUpManager.OnHeadsUpChangedListener {
private void resetUserSetupObserver() {
....
mContentObserver = new SettingsValueChangeContentObserver();
mContext.getContentResolver().registerContentObserver(Settings.System.getUriFor("show_navigation_bar"),true, mContentObserver);
}
private SettingsValueChangeContentObserver mContentObserver;
class SettingsValueChangeContentObserver extends ContentObserver {
public SettingsValueChangeContentObserver() {
super(new Handler());
}
@Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
Toast.makeText(mContext, Settings.System.getString(mContext.getContentResolver(),"show_navigation_bar"), Toast.LENGTH_SHORT).show();
String isShownav = Settings.System.getString(mContext.getContentResolver(),"show_navigation_bar");
if ("true".equals(isShownav)) {
mWindowManager.addView(mNavigationBarView, getNavigationBarLayoutParams());
} else {
mWindowManager.removeView(mNavigationBarView);
}
}
}
}
到此为止,就实现了根据用户的配置动态显示和隐藏navigationbar的功能,此时此刻,make -j8 全部编译代码,然后emulator启动生成的system.img文件,就可以看到效果了,如下:
由于下模拟器,效果可能会不是很好。
先看下效果吧:
##调整navigationbar的顺序
之前使用过HUAWEI的手机,发现其在设置中有一个功能,就是可以设置底部navigationbar的显示顺序,这个我认为对于用户体验来讲是有很大的进步,因为不同用户的使用喜欢习惯还是不同的,下面带大家实现这样的需求
###加载navigationbar布局
在正式开始之前,我们肯定是需要首先分析navigationbar布局,到现在,已经清楚NavigationBar的布局加载的
PhoneStatusBar#inflateNavigationBarView
protected void inflateNavigationBarView(Context context) {
mNavigationBarView = (NavigationBarView) View.inflate(
context, R.layout.navigation_bar, null);
}
navigation_bar.xml
可以看到这里实际上是显示的NavigationBarInflaterView,NavigationBarInflaterView继承自FrameLayout,那么对于布局的加载一定是在onFinishInflate中完成的
NavigationBarInflaterView#onFinishInflate
@Override
protected void onFinishInflate() {
super.onFinishInflate();
inflateChildren();
clearViews();
inflateLayout(getDefaultLayout());
}
NavigationBarInflaterView#inflateLayout
protected void inflateLayout(String newLayout) {
mCurrentLayout = newLayout;
if (newLayout == null) {
newLayout = getDefaultLayout();
}
String[] sets = newLayout.split(GRAVITY_SEPARATOR, 3);
String[] start = sets[0].split(BUTTON_SEPARATOR);
String[] center = sets[1].split(BUTTON_SEPARATOR);
String[] end = sets[2].split(BUTTON_SEPARATOR);
// Inflate these in start to end order or accessibility traversal will be messed up.
inflateButtons(start, (ViewGroup) mRot0.findViewById(R.id.ends_group), false);
inflateButtons(start, (ViewGroup) mRot90.findViewById(R.id.ends_group), true);
....
}
NavigationBarInflaterView#inflateButtons
private void inflateButtons(String[] buttons, ViewGroup parent, boolean landscape) {
for (int i = 0; i < buttons.length; i++) {
inflateButton(buttons[i], parent, landscape, i);
}
}
NavigationBarInflaterView#inflateButtons
protected View inflateButton(String buttonSpec, ViewGroup parent, boolean landscape,
int indexInParent) {
LayoutInflater inflater = landscape ? mLandscapeInflater : mLayoutInflater;
float size = extractSize(buttonSpec);
String button = extractButton(buttonSpec);
View v = null;
if (HOME.equals(button)) {
v = inflater.inflate(R.layout.home, parent, false);
} else if (BACK.equals(button)) {
v = inflater.inflate(R.layout.back, parent, false);
} else if (RECENT.equals(button)) {
v = inflater.inflate(R.layout.recent_apps, parent, false);
}
parent.addView(v);
addToDispatchers(v);
...
return v;
}
可以看到上面那些代码最终添加的是"R.layout.home" , “R.layout.back” , “R.layout.recent_apps”, 而这些布局的添加是依据当前的button,这些button就是getDefaultLayout()中返回的,上面做了处理,将配置转换成字符串数组,那么我们实现navigationbar的按钮顺序,就可以从这个字符串入手了
这里的思路和之前一样:
###SettingsProvider中增加字段
frameworks/base/packages/SettingsProvider/res/values/defaults.xml 中增加默认值
1
在DatabaseHelper#loadSystemSettings方法,加载该字段
loadIntegerSetting(stmt, "show_navigation_bar_method",R.integer.def_show_nav_bar_method);
###Settings中增加对应的ListPreference
首先需要定义设置项的用到的字符串和值,在packages/apps/Settings/res/values/arrays.xml文件中增加下面字符串数组
- back home recent
- recent back home
- home recent back
- back recent home
- 1
- 2
- 3
- 4
增加设置项,这里,同样的我在Display中显示该设置项。
在packages/apps/Settings/res/xml/display_settings.xml 中增加设置项
在DispalySettings中初始化,并设置点击事件
public class DisplaySettings extends SettingsPreferenceFragment implements
Preference.OnPreferenceChangeListener, Indexable {
private ListPreference mShowNavMethodPreference = null;
@Override
public void onCreate(Bundle savedInstanceState) {
mShowNavMethodPreference = (ListPreference)findPreference("show_nav_method");
mShowNavMethodPreference.setOnPreferenceChangeListener(this);
}
@Override
public boolean onPreferenceChange(Preference preference, Object objValue) {
final String key = preference.getKey();
if ("show_nav_method".equals(key)) {
int value = Integer.parseInt((String) objValue);
String summary = "";
switch (value) {
case 1:
summary = getResources().getStringArray(R.array.show_nav_methods_entries)[0];
break;
case 2:
summary = getResources().getStringArray(R.array.show_nav_methods_entries)[1];
break;
case 3:
summary = getResources().getStringArray(R.array.show_nav_methods_entries)[2];
break;
case 4:
summary = getResources().getStringArray(R.array.show_nav_methods_entries)[3];
break;
default:
summary = getResources().getStringArray(R.array.show_nav_methods_entries)[0];
break;
}
mShowNavMethodPreference.setSummary(summary);
Settings.System.putInt(getContentResolver(), "show_navigation_bar_method", value);
}
}
}
###在SystemUI中添加navigationbar配置
这里的配置就是navigationbar的按钮顺序的配置。
在frameworks/base/packages/SystemUI/res/values/config.xml 文件下,增加下面代码
space,back;home;recent,menu_ime
recent,menu_ime;space,back;home
home;recent,menu_ime;space,back
space,back;recent,menu_ime;home
###更改getDefaultLayout方法
需要更改NavigationBarInflaterView#getDefaultLayout, 目的是根据不同的设置提供不同的布局
protected String getDefaultLayout() {
// 获取当前settings中设置值,决定显示那种布局
int showNavMethod = Settings.System.getInt(mContext.getContentResolver(),"show_navigation_bar_method",1);
switch (showNavMethod) {
case 1:
return mContext.getString(R.string.config_navBarLayout);
case 2:
return mContext.getString(R.string.config_navBarLayout_first);
case 3:
return mContext.getString(R.string.config_navBarLayout_second);
case 4:
return mContext.getString(R.string.config_navBarLayout_third);
default:
return mContext.getString(R.string.config_navBarLayout);
}
}
###注册ContentObserver监听设置的变化
同样的,需要在PhoneStatusBar中增加ContentObserver监听设置的变化
private void resetUserSetupObserver() {
....
mShowMethodObserver = new ShowMethodContentObserver();
mContext.getContentResolver().registerContentObserver(Settings.System.getUriFor("show_navigation_bar_method"),true, mShowMethodObserver);
}
private ShowMethodContentObserver mShowMethodObserver;
class ShowMethodContentObserver extends ContentObserver {
public ShowMethodContentObserver() {
super(new Handler());
}
@Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
Toast.makeText(mContext, "show method changed", Toast.LENGTH_SHORT).show();
String isShownav = Settings.System.getString(mContext.getContentResolver(),"show_navigation_bar");
if ("true".equals(isShownav)) {
mNavigationBarView = (NavigationBarView) View.inflate(
mContext, R.layout.navigation_bar, null);
if (null != mNavigationBarView && mNavigationBarView.getVisibility() == View.VISIBLE) {
mWindowManager.updateViewLayout(mNavigationBarView, getNavigationBarLayoutParams());
} else {
mWindowManager.addView(mNavigationBarView, getNavigationBarLayoutParams());
}
}
}
}
好了,到此为止,已经实现了一个简单的navigationbar的定制功能,效果如下:
ok,本篇博客剩下的内容,年后在处理,新年好呀新年好。
专注技术分享,包括Java,python,AI人工智能,Android分享,不定期更新学习视频,欢迎关注