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);
}

總結:

本篇到這裏就完結了,喜歡的同學歡迎關注,後續會不斷更新文章,也歡迎評論,支出錯誤,共同進步

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