赞
踩
今天给大家带来一个有趣的实验,基于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
<string name="whether_shownav">"whether show nav"</string>
####在布局中添加设置选项
packages/apps/Settings/res/xml/display_settings.xml
<SwitchPreference
android:key="show_nav"
android:title="@string/whether_shownav"
android:summary="@string/whether_shownav" />
####在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中添加默认值
<String name="def_show_nav_bar">true</String>
在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
<com.android.systemui.statusbar.phone.NavigationBarView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:systemui="http://schemas.android.com/apk/res-auto"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:background="@drawable/system_bar_background">
<com.android.systemui.statusbar.phone.NavigationBarInflaterView
android:id="@+id/navigation_inflater"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</com.android.systemui.statusbar.phone.NavigationBarView>
可以看到这里实际上是显示的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 中增加默认值
<integer name="def_show_nav_bar_method">1</integer>
在DatabaseHelper#loadSystemSettings方法,加载该字段
loadIntegerSetting(stmt, "show_navigation_bar_method",R.integer.def_show_nav_bar_method);
###Settings中增加对应的ListPreference
首先需要定义设置项的用到的字符串和值,在packages/apps/Settings/res/values/arrays.xml文件中增加下面字符串数组
<string-array name="show_nav_methods_entries">
<item>back home recent</item>
<item>recent back home</item>
<item>home recent back</item>
<item>back recent home</item>
</string-array>
<string-array name="show_nav_methods_values" translatable="false">
<item>1</item>
<item>2</item>
<item>3</item>
<item>4</item>
</string-array>
增加设置项,这里,同样的我在Display中显示该设置项。
在packages/apps/Settings/res/xml/display_settings.xml 中增加设置项
<ListPreference
android:key="show_nav_method"
android:title="@string/show_nav_title"
android:summary="@string/show_nav_summary"
android:entries="@array/show_nav_methods_entries"
android:entryValues="@array/show_nav_methods_values" />
在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 文件下,增加下面代码
<string name="config_navBarLayout" translatable="false">space,back;home;recent,menu_ime</string> <!-- back home recent -->
<string name="config_navBarLayout_first" translatable="false">recent,menu_ime;space,back;home</string> <!-- recent back home -->
<string name="config_navBarLayout_second" translatable="false">home;recent,menu_ime;space,back</string> <!-- home recent back -->
<string name="config_navBarLayout_third" translatable="false">space,back;recent,menu_ime;home</string> <!-- back recent 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分享,不定期更新学习视频,欢迎关注
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。