当前位置:   article > 正文

Android系统实现navigationbar订制_android config_shownavigationbar

android config_shownavigationbar

今天给大家带来一个有趣的实验,基于android N原生代码,实现动态显示和隐藏navigationbar的功能,先说下实现思路,

  • 在SettingsProvider中增加一个"show_navigation_bar"字段,用来存储当前是否显示和隐藏navigationbar的值
  • 在Settings中增加一个SwitchPreference,并且设置setOnPreferenceChangeListener,使用Settings.System.putString存储设置的值
  • 在SystemUI中注册一个ContentObserver,监听"show_navigation_bar"数据的变化,当发生变化时候,根据当前的值,决定是否显示或者隐藏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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

这里根据读取到的配置文件决定是否显示navigationbar

mWindowManagerService.hasNavigationBar();
  • 1

PhoneWindowManager#hasNavigationBar

@Override
    public boolean hasNavigationBar() {
        return mHasNavigationBar;
    }
mHasNavigationBar = res.getBoolean(com.android.internal.R.bool.config_showNavigationBar);
  • 1
  • 2
  • 3
  • 4
  • 5

因此,我们可以修改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全编代码
  • 1
  • 2
  • 3

这里写图片描述

然后在应用中设置和获取属性:

SystemProperties.set("persist.shownav.enable","false");
SystemProperties.getBoolean("persist.shownav.enable", true)
  • 1
  • 2

这样做虽然可以显示和隐藏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>
  • 1
  • 2

####在布局中添加设置选项

packages/apps/Settings/res/xml/display_settings.xml
<SwitchPreference
                android:key="show_nav"
                android:title="@string/whether_shownav"
                android:summary="@string/whether_shownav" />
  • 1
  • 2
  • 3
  • 4
  • 5

####在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));
        }
        }
        ....
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

###在SettingsProvider中添加字段
数据库中数据的默认数据在frameworks/base/packages/SettingsProvider/res/values/defaults.xml中定义,因此我们需要在defaults.xml中添加默认值

<String name="def_show_nav_bar">true</String>
  • 1

在frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.Java中的loadSystemSettings()方法中加入新字段的添加代码

loadStringSetting(stmt, "show_navigation_bar",
                    R.string.def_show_nav_bar);
  • 1
  • 2

然后编译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);
            }
        }  
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

到此为止,就实现了根据用户的配置动态显示和隐藏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);
}

  • 1
  • 2
  • 3
  • 4
  • 5

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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

可以看到这里实际上是显示的NavigationBarInflaterView,NavigationBarInflaterView继承自FrameLayout,那么对于布局的加载一定是在onFinishInflate中完成的
NavigationBarInflaterView#onFinishInflate

@Override
protected void onFinishInflate() {
        super.onFinishInflate();
        inflateChildren();
        clearViews();
        inflateLayout(getDefaultLayout());
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

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);
        ....
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

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);
        }
}
  • 1
  • 2
  • 3
  • 4
  • 5

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;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

可以看到上面那些代码最终添加的是"R.layout.home" , “R.layout.back” , “R.layout.recent_apps”, 而这些布局的添加是依据当前的button,这些button就是getDefaultLayout()中返回的,上面做了处理,将配置转换成字符串数组,那么我们实现navigationbar的按钮顺序,就可以从这个字符串入手了

这里的思路和之前一样:

  • 在SettingsProvider中增加一个字段, 用来存储当前设置显示何种布局
  • 在Settings中增加对应的ListPreference 供用户选择
  • 在PhoneStatusBar中增加ContentObserver监听设置的变化,当数据发生变化时候,重新加载"R.layout.navigation_bar" 布局,此时会执行NavigationBarInflaterView#getDefaultLayout,在getDefaultLayout中读取用户的设置,返回不同的布局配置

###SettingsProvider中增加字段
frameworks/base/packages/SettingsProvider/res/values/defaults.xml 中增加默认值

<integer name="def_show_nav_bar_method">1</integer>
  • 1

在DatabaseHelper#loadSystemSettings方法,加载该字段

loadIntegerSetting(stmt, "show_navigation_bar_method",R.integer.def_show_nav_bar_method);
  • 1

###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>

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

增加设置项,这里,同样的我在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" />
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

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

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40

###在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 -->
  • 1
  • 2
  • 3
  • 4

###更改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);
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

###注册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());
		}
                
            }  
        }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

好了,到此为止,已经实现了一个简单的navigationbar的定制功能,效果如下:

这里写图片描述

这里写图片描述

这里写图片描述

ok,本篇博客剩下的内容,年后在处理,新年好呀新年好。

专注技术分享,包括Java,python,AI人工智能,Android分享,不定期更新学习视频,欢迎关注
在这里插入图片描述

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/weixin_40725706/article/detail/219288
推荐阅读
相关标签
  

闽ICP备14008679号