PreferenceFragmentCompat 设置界面 View

Quibbler 2020-7-28 3134

PreferenceFragmentCompat 设置界面


        很早之就接触过PreferenceScreen设置项的常用用法(见《PreferenceScreen 设置项》),现在抽空再来了解一下它的承载主体PreferenceFragmentCompat及设置项的用法。



1、混乱的deprecate

        先来屡一下Preference相关的包和类,到底哪些被deprecate?推荐用哪个?因为适配这个工作量不小,选错可能会给以后留下一个“惊喜”!


1.1、android.preference(退出历史)

        最早Preference跟随系统库发布,位于Android源码android.preference包中,不需要添加额外依赖就可以直接使用。其中就有常用的Preference、PreferenceScreen、PreferenceActivity、PreferenceFragment等。但是这一锅全部被deprecate掉,不再推荐使用。


1.2、androidx.preference(推荐)

        Google推出Jetpack,将一众混乱的各种support包、deprecate的包都统一起来。今天要说的PreferenceFragmentCompat就在androidx.preference包中。由于不是捆绑系统的类,所以使用PreferenceFragmentCompat需要在build.gradle中添加额外的依赖

    implementation 'androidx.preference:preference:1.1.1'

        参考:Android 开发者 > Jetpack > androidx.preference


1.3、PreferenceFragment

        PreferenceFragment这个单独拿出来说一下,是因为在android.preference和androidx.preference包中都有,但是最终还是被deprecate掉,在代码的不断重构过程中最终还是被Google“抛弃”。与此类似的还有PreferenceDialogFragment、ListPreferenceDialogFragment、EditTextPreferenceDialogFragment等,都改为了_Compat兼容的类。

        它的用法和后面要讲的PreferenceFragmentCompat差不多,继续往下看吧。



2、PreferenceFragmentCompat

        继续《PreferenceScreen 设置项》写好xml设置页面,还需要借助PreferenceFragmentCompat,并且动态添加到Activity设置页面中。


2.1、继承PreferenceFragmentCompat

        PreferenceFragmentCompat是抽象类,该类中只有一个抽象方法onCreatePreferences(Bundle savedInstanceState, String rootKey)

    /**
     * 在{@link #onCreate(Bundle)}期间调用以提供此片段的首选项。希望子类直接或通过辅助方法(例如{@link 
     * #addPreferencesFromResource(int)})调用{@link #setPreferenceScreen(PreferenceScreen)}。 。
     *
     * @param savedInstanceState 如果要从先前保存的状态重新创建片段,则为该状态。
     * @param rootKey    如果不为null,则此首选项片段应使用此键植于{@link PreferenceScreen}。
     */
    public abstract void onCreatePreferences(Bundle savedInstanceState, String rootKey);


        继承PreferenceFragmentCompat并实现该方法,相对于Activity的setContentView(R.layout.*)方法,调用setPreferencesFromResource(int preferencesResId,String key)加载res/xml目录下的设置页面。

    public class SettingsFragment extends PreferenceFragmentCompat {
        @Override
        public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
            setPreferencesFromResource(R.xml.root_preferences, rootKey);
        }
    }

        注意:可以使用PreferenceActivity似乎更加方便,不过整个页面只能是设置页,不利于页面的组合,使用Fragment会体会到它的强大便捷之处。android.preference.PreferenceActivity已经废弃,应当使用AndroidX包中的PreferenceActivity


2.2、动态添加PreferenceFragmentCompat

        PreferenceFragmentCompat也是Fragment,需要添加到Activity中。在Activity布局中放置一个FrameLayout,后面用来替换成设置Fragment。关于Fragment参考《Fragment用法》

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    
    <FrameLayout
        android:id="@+id/settings"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
        
</LinearLayout>

        也可以不需要FrameLayout,直接替换

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/settings"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
            
</LinearLayout>

        替换成设置Fragment

    getSupportFragmentManager()    //获取FragmentManager
            .beginTransaction()    //开启一个事务
            .replace(R.id.settings, new SettingsFragment())
            .commit();             //提交Fragment

        


3、Preference回调

        Preference中定义了三种类型的回调接口,用来处理设置项的点击、选项值变化等场景。

对比区别联系执行顺序
onPreferenceClick通过preference.setOnPreferenceClickListener 和preference.setOnPreferenceChangeListener来注册listener使用的最先执行,返回true, 则不调用onPreferenceTreeClick方法2
onPreferenceChange通过preference.setOnPreferenceClickListener 和preference.setOnPreferenceChangeListener来注册listener使用的独立其他2个点击事件,总是会运行1
onPreferenceTreeClick是PreferenceActivity 中的一个方法,直接重写该该方法即可。返回true 则不执行默认动作或返回上层调用链。3


3.1、变化:Preference.OnPreferenceChangeListener

        当Preference的值将要发生变化的时候调用。通过控制返回值true/false是否接受本次值得变化。此时可以提示用户是否进行变更

            public boolean onPreferenceChange(Preference preference, Object newValue) {
                //做业务逻辑处理
                boolean result = (boolean) newValue;
                
                if (result) {
                    // 返回true  本次值变化生效
                    return true;
                } else {
                    AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
                    builder.setTitle("选择")
                            .setMessage("是否确定关闭?")
                            .setNegativeButton("取消", new DialogInterface.OnClickListener() {
                                @Override
                                public void onClick(DialogInterface dialog, int which) {
                                    //用户选择取消,不关闭。直接关闭Dialog即可,因为后面已经返回了false,本次修改不造成变化
                                    dialog.dismiss();
                                }
                            })
                            .setPositiveButton("确定", new DialogInterface.OnClickListener() {
                                @Override
                                public void onClick(DialogInterface dialog, int which) {
                                    //用户选择确定,那么本次值虽然后面已经返回false,不再接受,但是这里可以继续根据用户的选择更改。
                                }
                            })
                            .create()
                            .show();
                    // 返回false 本次变化失效。在Dialog中通过选择控制值。
                    return false;
                }
            }

        Preference设置变化监听器setOnPreferenceChangeListener

    preference.setOnPreferenceChangeListener(...);


3.2、点击:Preference.OnPreferenceClickListener

        点击Preference界面设置项的时候调用,实际验证:当值发生变化后,才调用该函数。如果这个时候再去做点击判断是否改变值就晚了。

        场景:用户点击一个设置项,弹出对话框告诉用户是否确认修改,如果取消那么值不变。就可以给指定的Preference设置点击事件;或者先拦截,用户进行选择值再变化。

        顺序:先调用onPreferenceChange函数,然后才设置checkbox的状态,最后才会调用onPreferenceClick。

    private Preference.OnPreferenceClickListener preferenceClickListener = new Preference.OnPreferenceClickListener() {
        @Override
        public boolean onPreferenceClick(Preference preference) {
            return false;
        }
    };

        Preference设置点击监听器setOnPreferenceChangeListener

    preference.setOnPreferenceClickListener(preferenceClickListener);


3.3、跳转Activity

        一般点击设置页面,有些是实现跳转。可以在标签中添加<intent/>实现跳转:

    <Preference
        app:icon="@drawable/setting_icon_1"
        app:key="jump"
        app:summary="跳转到另一个Activity"
        app:title="跳转">
        
        <intent android:action="com.quibbler.jump" />
        
    </Preference>

        或者更精确的添加android:targetPackageandroid:targetClass指定跳转的Activity,防治系统中有其它应用有相同的Action冲突:

    <intent android:action="com.quibbler.jump"
        android:targetPackage="com.easyicon.server"
        android:targetClass="com.easyicon.server.activity.JumpHereActivity"/


        另一种方法,在继承的PreferenceFragmentCompat类中,通过key找到设置项,添加点击事件。

    Preference preference = getPreferenceManager().findPreference("jump");
    preference.setOnPreferenceClickListener(preferenceClickListener);

        Preference.OnPreferenceClickListener设置项点击监听器,点击进行跳转。关于Activity跳转,参考启动Activity的几种方式

        private Preference.OnPreferenceClickListener preferenceClickListener = new Preference.OnPreferenceClickListener() {
            @Override
            public boolean onPreferenceClick(Preference preference) {
                String key = preference.getKey();
                if ("jump".equals(key)) {
//                    Intent intent = new Intent();
//                    intent.setAction("com.quibbler.jump");
//                    getActivity().startActivity(intent);
                }
                return false;
            }
        };

        坑点:如果这里onPreferenceClick(Preference preference)方法返回了true,那么在xml设置布局中<Preference/>节点下设置的<intent/>不起作用



4、PreferenceManager指定保存文件名

        默认保存在/data/data/包名/shared_prefs/包名_preference.xml中,可以通过PreferenceManager指定保存设置Preference的文件名

    PreferenceManager preferenceManager = getPreferenceManager();

        指定保存SharedPreference文件名:

    preferenceManager.setSharedPreferencesName(name);
    preferenceManager.setSharedPreferencesMode(Context.MODE_PRIVATE);



5、动态删除/添加Perference

        有时候设置项需要根据不同的条件变化,可以加载不同的xml/设置布局,也可以在代码中动态的添加删除Preference设置项。所以必须要有PreferenceFragmentCompat对象

    PreferenceFragmentCompat mSettingsFragment = new SettingsFragment();


5.1、动态删除

        首先需要找到要删除的Preference,借助PreferenceFragmentCompat对象获取到PreferenceManager设置管理器

    PreferenceManager preferenceManager = mSettingsFragment.getPreferenceManager();

        借助PreferenceManager设置管理器,通过key找到Preference设置项:

    Preference preference = preferenceManager.findPreference("key_set");


        ①借助PreferenceScreen删除

        要从哪删除?当然是PreferenceFragmentCompat对象中设置容器中,我们设置XML布局中的顶级容器标签就是PreferenceScreen。先获取PreferenceFragmentCompat的容器

    PreferenceScreen preferenceScreen = mSettingsFragment.getPreferenceScreen();

        再从中删除即可:

    PreferenceScreen preferenceScreen = mSettingsFragment.getPreferenceScreen();
    preferenceScreen.removePreference(preference);


        ②借助PreferenceCategory删除

        还可以单独从某个设置组中删除,先通过PreferenceManager设置管理器找到设置组。从该设置组中删除Preference设置项。

    PreferenceCategory preferenceCategory = preferenceManager.findPreference("key_category");
    if (null != preferenceCategory) {
        //从组别中删除该设置项,最好判空
        preferenceCategory.removePreference(preferenceScreen);
    }


5.2、动态添加

        添加,构造好一个设置项,初始化各种参数title、key等。

    Preference preference = new Preference(this);
    preference.setTitle("添加新的设置");
    preference.setKey("pref_new_key");


        直接添加到PreferenceScreen中

    PreferenceScreen preferenceScreen = mSettingsFragment.getPreferenceScreen();
    preferenceScreen.addPreference(preference);


        添加到指定设置组中:

    PreferenceCategory preferenceCategory = preferenceManager.findPreference("key_category");
    //添加到该组的最后一个位置上
    preferenceCategory.addPreference(preference);


        注意:无论如何添加设置项都是添加到最后一个位置,或者组的最后一个位置。这就不太好真正实现设置项的隐藏和显示。源码设置Preference的插入似乎有突破口,根据Index插入。



推荐博客:

        Google > Docs > Reference > PreferenceFragmentCompat

        PreferenceFragmentCompat

        Android Preference详解之初识Preference及Preference系

        PreferenceFragment去完成设置页面

        Android开发之PreferenceActivity的使用

        

不忘初心的阿甘
最新回复 (1)
  • Quibbler 2020-8-1
    2


            自定义Preference界面,重写onBindViewHolder(PreferenceViewHolder holder)方法加载自定义Layout布局

        /**
         * Binds the created View to the data for this Preference.
         * 
         * This is a good place to grab references to custom Views in the layout and
         * set properties on them.
         * 
         * Make sure to call through to the superclass's implementation.
         *
         * @param holder The ViewHolder that provides references to the views to fill in. These views
         *               will be recycled, so you should not hold a reference to them after this method
         *               returns.
         */
        public void onBindViewHolder(PreferenceViewHolder holder) {
            ...
        }


    • 安卓笔记本
      3
        登录 注册 QQ
返回
仅供学习交流,切勿用于商业用途。如有错误欢迎指出:fluent0418@gmail.com