Android8.0 原生设置定制化内容
前言
最近的工作任务是要把之前单独开发的app中的一部分功能移植到原生Settings中,因此熟悉了一下Settings的修改方法,这篇用来记录下自己实际任务中用到的东西,毕竟年纪大了,记忆力不如从前,也为了后面再有同样的需求(目前确定需求已经在路上,并且很快就会达到)能够快速的完成任务。因为产品还没有上市,这里会改一个自定义的版本来进行记录。
需求描述
按照系统Settings的风格,显示下图所示的UE。
UE.png实现方法
一级菜单的添加
根据需求,需要在设置的一级菜单新增“生活”选项,并且位置有指定要求。
1.在Settings.java (com.android.settings)
文件中添加如下定义
public static class LiftSettingsActivity extends SettingsActivity{}
2.在SettingsActivity.java (com.android.settings)
的doUpdateTilesList()
方法中,添加如下代码。
somethingChanged = setTileEnabled(new ComponentName(packageName,
Settings. LiftSettingsActivity.class.getName()),
true /*false则隐藏,true显示*/, isAdmin)
|| somethingChanged;
可以通过改变setTileEnabled
方法的第二个参数enable
的值来动态的控制此选项的隐藏和显示。
3.创建点击“生活”后跳转到的fragment,可以创建一个文件夹life,fragment的名字为LifeFragment,并在res/xml下创建对应的xml文件life.xml
。
LifeFragment.java
继承PreferenceFragment,也可根据需要继承其它的fragment,在它的onCreate()
方法中调用addPreferencesFromResource(R.xml.life)
方法,将布局文件加载进来。
life.xml
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:settings="http://schemas.android.com/apk/res/com.android.settings"
android:title="@string/life_title">
<!-- 个人信息-->
<Preference
android:key="life_info"
android:title="@string/life_info"
/>
<!-- 生存状态 -->
<Preference
android:key="life_state"
android:title="@string/life_state"
/>
</PreferenceScreen>
4.在SettingsGateway.java(com.android.settings.core.gateway)
的ENTRY_FRAGMENTS
中添加LifeFragment.class.getName()
,并且在SETTINGS_FOR_RESTRICTED
中添加Settings.LifeSettingsActivity.class.getName()
,否则会崩溃,报security exception。
5.在AndroidManifest.xml
中的适当位置添加如下代码。
<activity android:name=".Settings$LifeSettingsActivity"
android:label="@string/life_title"
android:taskAffinity="com.android.settings"
android:icon="@drawable/ic_life"
android:configChanges="orientation|keyboardHidden|screenSize"
android:parentActivityName="Settings">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.VOICE_LAUNCH"/>
</intent-filter>
<!-- 优先级来确定显示位置 -->
<intent-filter android:priority="-8">
<action android:name="com.android.settings.action.SETTINGS"/>
</intent-filter>
<!-- 在首页显示 -->
<meta-data android:name="com.android.settings.category"
android:value="com.android.settings.category.ia.homepage"/>
<!-- 设置title的值 -->
<meta-data android:name="com.android.settings.title"
android:value="@string/life_title"/>
<!-- 跳转到的fragment -->
<meta-data android:name="com.android.settings.FRAGMENT_CLASS"
android:value="com.android.settings.life.LifeFragment"/>
<!-- 被管理账户的intent透传 -->
<meta-data android:name="com.android.settings.PRIMARY_PROFILE_CONTROLLED"
android:value="true"/>
<!-- summary显示-->
<meta-data android:name="com.android.settings.summary"
android:value="@string/life_summary"/>
</activity>
特别说一下com.android.settings.PRIMARY_PROFILE_CONTROLLED
,这个在SettingsInitialize.java
中有用到,如果这个值为true,就会调用PackageManager的addCrossProfileIntentFilter()方法,这个方法的作用是让某些Intent透传到其他Profile而不被本Profile的同名组件所捕获。主要用在被管理账户和主账户之间的数据通信。
优先级不能重复,否则显示位置和预期的不同。
完成以上5个步骤,就完成了一级菜单的加载,接下来就是在二级菜单来处理业务了。
添加一级菜单.gif二级菜单的业务开发
根据需求,二级菜单需要完成业务上的功能。
1.因为生存状态比较简单,就是一个单选功能,本来计划使用ListPreference,但是需要有“确定”,“取消”按钮,所以这里就不使用ListPreference了,因为本身的数据不需要持久化(我这里的需求不需要,有地方存储),但是正常需要让数据持久化的,这里可以自己处理。
在LifeFragment.java
文件中定义一个弹出的单选对话框,在点击“生存状态”时弹出,并且在点击确定时进行数据的保存和summary的更新。
public class LifeFragment extends PreferenceFragment implements OnPreferenceClickListener{
private static final String TAG = "LifeFragment";
private static final String KEY_STATE = "life_state";
private Context mContext;
private AlertDialog mDialog;
private Preference mStatePref;
private int mClickPosition = -1;
/**
* <p>Description:</p>
*/
@Override
public void onCreatePreferences(Bundle arg0, String arg1) {
}
@Override
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
mContext = getActivity();
addPreferencesFromResource(R.xml.life);
mStatePref = findPreference(KEY_STATE);
mStatePref.setOnPreferenceClickListener(this);
}
@Override
public void onResume() {
super.onResume();
mClickPosition = Utils.getLifeDate(mContext, Utils.KEY_LIFE_STATE);
updateSummary();
}
@Override
public boolean onPreferenceClick(Preference preference) {
if (preference == mStatePref) {
createDialog();
}
return false;
}
private void createDialog() {
if (mClickPosition == -1) {
return;
}
mDialog = new AlertDialog.Builder(mContext)
.setTitle(R.string.life_state)
.setSingleChoiceItems(R.array.life_state, mClickPosition, new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
mClickPosition = which;
}
})
.setPositiveButton(R.string.dlg_ok, new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
saveLifeState();
}
})
.setNegativeButton(R.string.dlg_cancel, null)
.create();
mDialog.show();
}
/**
* <p>Title:saveLifeState</p>
* <p>Description:保存生活状态</p>
*/
protected void saveLifeState() {
Utils.saveLifeInfo(mContext, Utils.KEY_LIFE_STATE, mClickPosition);
updateSummary();
}
/**
* <p>Title:updateSummary</p>
* <p>Description:更新summary信息</p>
*/
private void updateSummary() {
mStatePref.setSummary(mContext.getResources().getStringArray(R.array.life_state)[mClickPosition]);
}
@Override
public void onDestroy() {
super.onDestroy();
if (mDialog != null && mDialog.isShowing()) {
mDialog.dismiss();
}
}
}
2.个人信息需要跳转到三级菜单,因此只需要在xml中添加需要跳转的fragment即可。
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:settings="http://schemas.android.com/apk/res/com.android.settings"
android:title="@string/life_title"
>
<Preference
android:key="life_info"
android:title="@string/life_info"
android:fragment="com.android.settings.hytera.life.LifeInfoFragment"
android:summary="@string/life_info_summary"
/>
<Preference
android:key="life_state"
android:title="@string/life_state"
/>
</PreferenceScreen>
三级菜单和二级菜单的“生存状态”基本类似,这里不详细贴出代码了。
三级菜单的处理
实现完成三级菜单后,会发现一个问题,二级菜单生存状态的对话框进行横竖屏切换时不会消失,是因为我们在AndroidManifest.xml
中配置了android:configChanges
,但是三级菜单需要在哪里配置呢?
Fragment必须要依附一个Activity,但是三级菜单使用的是fragment标签直接跳转的,它所依附的是哪个Activity呢?通过查看代码,找到了答案。
如果使用的是布局文件中这种形式的,它所依附的就是SubSettings,通过修改AndroidManifest.xml
中Subsettings,发现的确满足要求,对话框不再消失了,但是这样修改会对所有的依附于Subsettings的fragment都起作用,这显示是不可取的。在Subsettings.java
的代码中,我们看到有一行如下代码,可以帮助我们实现要求。
1.声明LifeSubSettings类,继承自SubSettings。
public class SubSettings extends SettingsActivity {
@Override
public boolean onNavigateUp() {
finish();
return true;
}
@Override
protected boolean isValidFragment(String fragmentName) {
Log.d("SubSettings", "Launching fragment " + fragmentName);
return true;
}
public static class BluetoothSubSettings extends SubSettings { /* empty */ }
public static class LifeSubSettings extends SubSettings { /* empty */ }
}
2.在Utils.java(com.android.settings)
里的onBuildStartFragmentIntent()方法中添加如下代码:
if (BluetoothSettings.class.getName().equals(fragmentName)) {
intent.setClass(context, SubSettings.BluetoothSubSettings.class);
intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_AS_SUBSETTING, true);
}else if ("com.android.settings.life.LifeInfoFragment".equals(fragmentName)) {
//这里添加如果是LifeInfoFragment,则启动LifeSubSettings
intent.setClass(context, SubSettings.LifeSubSettings.class);
intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_AS_SUBSETTING, true);
}
else {
intent.setClass(context, SubSettings.class);
}
3.在AndroidManifest.xml
中添加如下内容,用来控制横竖屏切换时不重走生命周期方法。
<!-- 生活管理 -->
<activity android:name=".SubSettings$LifeSubSettings"
android:taskAffinity="com.android.settings"
android:configChanges="orientation|keyboardHidden|screenSize"
android:parentActivityName="Settings">
</activity>
至此,就已经完成了需求要求的内容,整体来说还是比较简单,如果第一次上手搞这个,还是会花费一些时间来阅读源码,效果图奉上。
效果图.gif