Preference 系统自带的偏好设置页面解析

导读

最近做项目无意中看到Perference类,发现Android系统设置就是用这个实现,感觉自己以前写的设置页面都白写了,而且还浪费时间

关于设置页面的介绍这里就不详细介绍了,官方文档已经写得很详细,官方推荐我们使用在XML文件中声明Preference类的各种子类构建设置页面,而不是使用View对象构建用户界面

下面是我的学习总结,希望能辅助到小白学习吧..!(赶时间直接想用的跳到Preference简单使用)

Google 官方文档链接

官方文档Preference列出来的类结构树

这里写图片描述

==补充说明==

  1. Preference类提供了SharedPreference用于保存或读取数据.key属性作为SharedPreference的键.最终保存在data/data/包名/shared_prefs文件下.
  2. 由于应用的设置UI使用的是Preference对象构建而成,隐藏需要专门的Activity或Fragment子类显示列表设置.
    • 如果应用版本低于Android 3.0(API 10),必需将Activity构建为PreferenceActivity类的扩展
    • 对于Android 3.0 以上的版本,在传统的Activitiy中使用PreferenceFragment来显示

在data/data/包名/shared_prefs 导出文件如下


<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
    <!--ListPreference在SharedPreference保存的状态-->
    <string name="key_List">1</string>

    <!--MultiSelectListPreference在SharedPreference保存的状态-->
    <set name="key_MultiSelectList">
        <string>1</string>
        <string>2</string>
    </set>

    <!--EditTextPreference在SharedPreference保存的状态-->
    <string name="key_EditText">fedhfghhfdghdffghdfgh</string>

    <!--CheckBoxPreference在SharedPreference保存的状态-->
    <boolean name="key_CheckBoxPreference" value="true" />

    <!--RingtonePreference在SharedPreference保存的状态-->
    <string name="key_RingtonePreference">content://media/internal/audio/media/6</string>

</map>

PreferenceScreen

  1. PreferenceScreen作为PreferenceActivity基本布局的根容器,类似普通布局的各种Layout
  2. 可以通过xml方式或者通过createPreferenceScreen(Context)方式来构造PreferenceScreen
  3. PreferenceScreen可以被PreferenceCategory嵌套,也可以PreferenceScreen嵌套PreferenceScreen,将通过新开屏幕显示

PreferenceScreen
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:key="first_preferencescreen">
    <CheckBoxPreference
            android:key="wifi enabled"
            android:title="WiFi" />
    <PreferenceScreen
            android:key="second_preferencescreen"
            android:title="WiFi settings">
        <CheckBoxPreference
                android:key="prefer wifi"
                android:title="Prefer WiFi" />
        ... other preferences here ...
    </PreferenceScreen>
</PreferenceScreen> 

PreferenceCategory

通常作为二级容器嵌套在PreferenceScreen里提供分组的作用,类似数据库SQL中的group by.

PreferenceCategory效果图

EditTextPreference(输入框Dialog)

<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
    <!--EditText getEditText()  获取显示在Dialog上的EditText-->
    <!--String getText()    获取对应SharedPreferences保存的值-->
    <!--String setText()    设置对应SharedPreferences保存的值-->

    <EditTextPreference
        android:dialogTitle="ETPreference"
        android:key="key_EditText"
        android:summary="EditTextPreference summary"
        android:title="EditTextPreference"/>

        </PreferenceScreen>

这里写图片描述

ListPreference(单选框列表Dialog)


    <!--android:entries 列表的显示项数据源-->
    <!--android:entryValues 列表实际保存至内部的值-->
    <!--android:defaultValue    这个属性不是特有的,但是有一点需要注意:这里设置的是android:entryValues里的值-->
    <!--android:dialogMessage   如果设置了这个属性,那么列表怎么会被覆盖掉-->
    <!--int findIndexOfValue(String value)  返回获取ListPreference中的实体内容的下标值-->
    <!--CharSequence[] getEntries() 返回当前Preference设置的entries集合(xml配置的字符串数据源)-->
    <!--CharSequence getEntry() 返回当前选中的entries值-->
    <!--CharSequence[] getEntryValues() 返回当前的entries对应的value集合(xml配置的字符串数据源)-->
    <!--CharSequence getSummary()   获取当前summary-->
    <!--String getValue()   获取key值-->

    <ListPreference
        android:dialogTitle="List"
        android:entries="@array/game"
        android:entryValues="@array/game_index"
        android:key="key_List"
        android:summary="ListPreference summary"
        android:title="ListPreference"
        />

这里写图片描述

MultiSelectListPreference(多选框列表Dialog)


    <!--MultiSelectListPreference新增方法   说明-->
    <!--int findIndexOfValue(String value)  返回获取ListPreference中的实体内容的下标值-->
    <!--CharSequence[] getEntries() 返回当前Preference 设置的entries集合(xml配置的字符串数据源)-->
    <!--CharSequence[] getEntryValues() 返回当前的entries对应的value集合(xml配置的字符串数据源)-->
    <!--Set getValues() 返回当前选中的values,SharedPreference的-->
    <!--void setEntries(int entriesResId)-->
    <!--void setEntries(CharSequence[] entries)-->
    <!--void setEntryValues(CharSequence[] entryValues)-->
    <!--void setEntryValues(int entryValuesResId)-->
    <!--void setValues(Set values)-->

    <MultiSelectListPreference
        android:dialogTitle="MultiSelectList"
        android:entries="@array/game"
        android:entryValues="@array/game_index"
        android:key="key_MultiSelectList"
        android:summary="MultiSelectListPreference summary"
        android:title="MultiSelectListPreference"/>

这里写图片描述

res/values/array.xml定义

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!--ListPreference 要显示的数据-->
    <string-array name="game">
        <item>Dota</item>
        <item>Dota2</item>
        <item>NBA2K10</item>
        <item>FIFA2K10</item>
    </string-array>
    <string-array name="game_index">
        <item>0</item>
        <item>1</item>
        <item>2</item>
        <item>3</item>
    </string-array>

</resources>

CheckBoxPreference

    <!--android:widgetLayout 选中框布局-->
    <!--android:summaryOff  uncheck时的summary-->
    <!--android:summaryOn   check时的summary-->
    <CheckBoxPreference
        android:icon="@mipmap/ic_launcher"
        android:key="key_checkbox_preference"
        android:order="10"
        android:summary="CheckboxPreference summary"
        android:summaryOff="Summary off"
        android:summaryOn="On Summary"
        android:title="CheckBoxPreference"/>

这里写图片描述

SwitchPreference


    <!--android:summaryOff  uncheck时的summary-->
    <!--android:summaryOn   check时的summary-->
    <!--android:switchTextOff 处于off状态,switch使用的文本-->
    <!--android:switchTextOn-->
    <SwitchPreference
        android:key="key_SwitchPreference"
        android:order="1"
        android:summary="SwitchPreference summary"
        android:summaryOff="Off "
        android:summaryOn="On"
        android:switchTextOff="关"
        android:switchTextOn="开"
        android:title="SwitchPreference"
        android:widgetLayout="@layout/activity_show"
        />

这里写图片描述

RingtonePreference

RingtonePreference起作用就是供我们选择系统铃声的

特有属性 说明
android:ringtoneType 铃声类型:ringtone(1)、notification(2)、all(7)、alarm(4)
android:showDefault 布尔值是否显示默认铃声
android:showSilent 布尔值是否显示静音

<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
    android:key="using_categories_in_root_screen"
    android:summary="Using Preference Categories"
    android:title="Categories">

    <RingtonePreference
        android:key="key_prerence"
        android:title="RingPreferece Title"
        android:summary="RingPreference Summary"
        />

</PreferenceScreen>

这里写图片描述

自定义Preference(主要是自定义控件方式)

public class NumberPickerPreference extends DialogPreference {
    public NumberPickerPreference(Context context, AttributeSet attrs) {
        super(context, attrs);

        setDialogLayoutResource(R.layout.numberpicker_dialog);
        setPositiveButtonText(android.R.string.ok);
        setNegativeButtonText(android.R.string.cancel);

        setDialogIcon(null);
    }
    ...
}

Preference监听事件的回调接口和方法

一、监听某个Preference被点击回调


         findPreference("Preference").setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
            @Override
            public boolean onPreferenceClick(Preference preference) {
                Toast.makeText(getActivity(), "Preference 被点击了", Toast.LENGTH_SHORT).show();
                return true;
            }
        });

二、监听任意首选项状态发生改变时立即收到通知

//强引用方式创建监听器,防止监听器被垃圾回收掉
//这个回调只有状态发生改变才会被调用
 SharedPreferences.OnSharedPreferenceChangeListener listener =
            new SharedPreferences.OnSharedPreferenceChangeListener() {
                public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
                    // listener implementation
                    if (key.equals("key_EditTextPreference")) {
                        Preference connectionPref = findPreference(key);
                        // Set summary to be the user-description for the selected value
                        Preference key_EditTextPreference = findPreference("key_EditTextPreference");
                        key_EditTextPreference.setSummary(sharedPreferences.getString(key, "") + "");
                    }
                }
            };

//生命周期管理,在onResume()和onPause()回调期间分别注册和注销

@Override
protected void onResume() {
    super.onResume();
    getPreferenceScreen().getSharedPreferences()
            .registerOnSharedPreferenceChangeListener(listener);
}

@Override
protected void onPause() {
    super.onPause();
    getPreferenceScreen().getSharedPreferences()
            .unregisterOnSharedPreferenceChangeListener(listener);
}

Preference简单使用

一、在res/xml/目录下创建为根节点的xml文件,并按照上述的XXXPreference设置属性

 <PreferenceCategory
        android:title="PreferenceCategory"
        >
        <!--android:widgetLayout  修改Preference里的子布局-->
        <!--android:layout 修改Preference的布局-->
        <Preference
            android:icon="@mipmap/ic_launcher_round"
            android:summary="Open otherActivity (Preference)"
            android:title="Open otherActivity"
            >


        </Preference>
    </PreferenceCategory>
    <!--嵌套PreferenceScreen 会再打开一个子屏幕-->
    <!--可以使用extra给intent标签加参数来传递参数实现意图 会再打开一个子屏-->
    <PreferenceScreen
        android:summary="Open the Web (Preference)"
        android:title="嵌套PreferenceScreen Open the Web "
        >
        <Preference
            android:title="Open the Web (Preference)"
            >
            <intent
                android:action="android.intent.action.VIEW"
                android:data="http://www.hao123.com"/>

        </Preference>
    </PreferenceScreen>

    <!--选择系统铃声的-->
    <!--android:ringtoneType    铃声类型:ringtone、notification、all、alarm-->
    <!--android:showDefault 布尔值是否显示默认铃声-->
    <!--android:showSilent  布尔值是否显示静音-->
    <RingtonePreference
        android:key="key_RingtonePreference"
        android:ringtoneType="all"
        android:showDefault="true"
        android:showSilent="true"
        android:summary="RingPreference (铃声设置)"
        android:title="RingtonePreference"
        />
    <!--android:order 首选项的顺序(较低的值在前面)-->
    <EditTextPreference
        android:key="key_EditTextPreference"
        android:order="1"
        android:summary="EditTextPreference 输入框"
        android:title="EditTextPreference"/>
    <Preference
        android:key="key_Preference"
        android:summary="please Click me"
        android:title="Preference Click Test"
        />

二、初始化PreferenceActivity或PreferenceFragment,按需设置相关监听器

public class NormalPreferenceFragment extends PreferenceFragment  {

    private RingtonePreference mKey_ringtonePreference;
    private Preference         mKey_preference;

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //引入Preference XML文件
        addPreferencesFromResource(R.xml.normal_preference);
        mKey_ringtonePreference = (RingtonePreference) findPreference("key_RingtonePreference");

        int ringtoneType = mKey_ringtonePreference.getRingtoneType();
        boolean showDefault = mKey_ringtonePreference.getShowDefault();
        boolean showSilent = mKey_ringtonePreference.getShowSilent();

        mKey_preference = findPreference("key_Preference");
        mKey_preference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
            @Override
            public boolean onPreferenceClick(Preference preference) {
                Toast.makeText(getActivity(), "Preference 被点击了", Toast.LENGTH_SHORT).show();
                return true;
            }
        });
    }

    SharedPreferences.OnSharedPreferenceChangeListener listener =
            new SharedPreferences.OnSharedPreferenceChangeListener() {
                public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
                    // listener implementation
                    if (key.equals("key_EditTextPreference")) {
                        Preference connectionPref = findPreference(key);
                        // Set summary to be the user-description for the selected value
                        Preference key_EditTextPreference = findPreference("key_EditTextPreference");
                        key_EditTextPreference.setSummary(sharedPreferences.getString(key, "") + "");
                    }
                }
            };



    @Override
    public void onResume() {
        super.onResume();
        //注册监听
        getPreferenceScreen().getSharedPreferences()
                .registerOnSharedPreferenceChangeListener(listener);
    }

    @Override
    public void onPause() {
        super.onPause();
        //取消监听
        getPreferenceScreen().getSharedPreferences()
                .unregisterOnSharedPreferenceChangeListener(listener);
    }

}

首选项标头使用

我们知道可以用嵌套的PreferenceScreen元素构建子屏幕,但在Android 3.0(API 10)以上的版本,更建议我们我们使用”标头”功能,好处是在大屏幕运行时,会自动提供双窗格布局

一、创建标头XML文件

<?xml version="1.0" encoding="utf-8"?>
<preference-headers xmlns:android="http://schemas.android.com/apk/res/android">
    <header
        android:fragment="zs.xmx.last.NormalPreferenceFragment"
        android:summary="NormalPreferenceFragment"
        android:title="NormalPreferenceFragment">
        <!-- key/value pairs can be included as arguments for the fragment. -->
        <extra
            android:name="headers_key"
            android:value="preference-headers 携带的值"/>
    </header>
    <header
        android:fragment="zs.xmx.last.DialogPreferenceFragment"
        android:summary="DialogPreferenceFragment"
        android:title="DialogPreferenceFragment"/>

    <header
        android:fragment="zs.xmx.last.TwoStatePreferenceFragment"
        android:summary="TwoStatePreferenceFragment"
        android:title="TwoStatePreferenceFragment"/>
    <header
        android:fragment="zs.xmx.last.CustomPreferenceFragment"
        android:summary="CustomPreferenceFragment"
        android:title="CustomPreferenceFragment"/>

</preference-headers>

二、显示标头


/**
 * 使用标头,要继承PreferenceActivity,重写onBuildHeaders()和isValidFragment()
 */
public class SettingPreferenceActivity extends PreferenceActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (hasHeaders()) {  //如有header,则在最下面加一个button。本例无论平板还是phone,都返回true
            Button button = new Button(this);
            button.setText("Exit");
            setListFooter(button);
        }

    }

    @Override
    public void onBuildHeaders(List<Header> target) {
        super.onBuildHeaders(target);
        loadHeadersFromResource(R.xml.preference_heads, target);
    }

    /**
     * API 19以上的安全机制,需要重写isValidFragment()
     * <p>
     * 1.直接return true
     * <p>
     * 2.return [YOUR_FRAGMENT_NAME].class.getName().equals(fragmentName);
     *
     * @param fragmentName
     * @return
     */
    @Override
    protected boolean isValidFragment(String fragmentName) {
        return true;
    }

这里写图片描述

==Preference注意事项==

并非我们第一次打开相应界面之后就会自动创建对应的SharedPreferences文件,而是在我们改变了原有状态时候

并非所有的Preference及其子类都会创建,仅仅针对需要记录状态的Preference.

使用PreferenceActivity时,setContentView()方法的layout文件里必须包含id为android.R.id.list的listView,否则会报E/AndroidRuntime: Caused by: Java.lang.RuntimeException: Your content must have a ListView whose id attribute is ‘android.R.id.list’,再调用addPreferencesFromResource来完成Preference界面的构建;建议直接调用addPreferencesFromResource方法.

扩展:

使用Intent

在某些情况下,您可能需要首选项来打开不同的 Activity(而不是网络浏览器等设置屏幕)或查看网页.要在用户选择首选项时调用 Intent,请将 元素添加为相应 元素的子元素.


//使用首选项打开网页,属性与我们Activity使用Intent类似

<Preference android:title="@string/prefs_web_page" >
    <intent android:action="android.intent.action.VIEW"
            android:data="http://www.example.com" />
</Preference>

保存和恢复首选项的状态

  • 类似Activity的横竖屏切换恢复状态,Preference 子类也负责保存并恢复其状态.要正确保存并恢复 Preference 类的状态,您必须实现生命周期回调方法 onSaveInstanceState() 和 onRestoreInstanceState().

  • Preference 的状态由实现 Parcelable 接口的对象定义.Android 框架为您提供此类对象,作为定义状态对象(Preference.BaseSavedState 类)的起点.

  • 要定义 Preference 类保存其状态的方式,您应该扩展 Preference.BaseSavedState 类.您只需重写几种方法并定义 CREATOR 对象.

  • 对于大多数应用,如果 Preference 子类保存除整型数以外的其他数据类型,则可复制下列实现并直接更改处理 value 的行.

private static class SavedState extends BaseSavedState {
    // Member that holds the setting's value
    // Change this data type to match the type saved by your Preference
    int value;

    public SavedState(Parcelable superState) {
        super(superState);
    }

    public SavedState(Parcel source) {
        super(source);
        // Get the current preference's value
        value = source.readInt();  // Change this to read the appropriate data type
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        super.writeToParcel(dest, flags);
        // Write the preference's value
        dest.writeInt(value);  // Change this to write the appropriate data type
    }

    // Standard creator object using an instance of this class
    public static final Parcelable.Creator<SavedState> CREATOR =
            new Parcelable.Creator<SavedState>() {

        public SavedState createFromParcel(Parcel in) {
            return new SavedState(in);
        }

        public SavedState[] newArray(int size) {
            return new SavedState[size];
        }
    };
}

如果将上述 Preference.BaseSavedState 实现添加到您的应用(通常,作为 Preference 子类的子类),则需要为 Preference 子类实现 onSaveInstanceState() 和 onRestoreInstanceState() 方法。

@Override
protected Parcelable onSaveInstanceState() {
    final Parcelable superState = super.onSaveInstanceState();
    // Check whether this Preference is persistent (continually saved)
    if (isPersistent()) {
        // No need to save instance state since it's persistent,
        // use superclass state
        return superState;
    }

    // Create instance of custom BaseSavedState
    final SavedState myState = new SavedState(superState);
    // Set the state's value with the class member that holds current
    // setting value
    myState.value = mNewValue;
    return myState;
}

@Override
protected void onRestoreInstanceState(Parcelable state) {
    // Check whether we saved the state in onSaveInstanceState
    if (state == null || !state.getClass().equals(SavedState.class)) {
        // Didn't save the state, so call superclass
        super.onRestoreInstanceState(state);
        return;
    }

    // Cast state to custom BaseSavedState and pass to superclass
    SavedState myState = (SavedState) state;
    super.onRestoreInstanceState(myState.getSuperState());

    // Set this Preference's widget to reflect the restored state
    mNumberPicker.setValue(myState.value);
}

总结:

本篇到这里就完结了,喜欢的同学欢迎关注,后续会不断更新文章,也欢迎评论,支出错误,共同进步

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章