Android开发Android技术知识Android开发

Android8.0 原生设置定制化内容

2019-03-25  本文已影响13人  Tom_Ji

前言

最近的工作任务是要把之前单独开发的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
上一篇下一篇

猜你喜欢

热点阅读