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

作者: Tom_Ji | 来源:发表于2019-03-25 14:13 被阅读13次

    前言

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

    相关文章

      网友评论

        本文标题:Android8.0 原生设置定制化内容

        本文链接:https://www.haomeiwen.com/subject/psflvqtx.html