Preference_Android原生設定介面

劉強東發表於2017-11-09

基本上每個應用都有一個設定(首選項)介面, Google其實提供了預設的設定介面實現方式. 介紹下

Preference該類擁有多個直接或間接的子類, 這些子類可以組成不同內容的首選項介面. 和一般介面不同的

關鍵類:

  • Preference 普通
    • RingtonePreference 鈴聲
      • CheckBoxPreference 選擇按鈕
      • SwitchPreference 切換按鈕
    • PreferenceGroup(抽象類)
      • PreferenceCategory 分類
      • PreferenceScreen 首選項介面
    • DialogPreference (抽象類)
      • ListPreference 列表
      • EditTextPreference 輸入框
      • MultiSelectListPreference 多選
  • PreferenceActivity 首選項介面
  • PreferenceFragment 首選項片段
  • PreferenceViewHolder

首選項

可以通過在res/xml目錄下建立一個XML檔案來控制顯示一個首選項介面內容, 一般命名為preference.xml.

Preference_Android原生設定介面

PreferenceActivity

在android3.0以下應使用繼承PreferenceActivity方式實現(因為Fragment是3.0之後才有的), 但是如果需要使用多皮膚模式就必須使用這種方法

public class MainActivity extends PreferenceActivity {

    @Override protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //setContentView(R.layout.activity_main); 不允許使用一般佈局
        addPreferencesFromResource(R.xml.preference);
    }
}
複製程式碼

PreferenceFragment

在Android3.0或以上應採用Fragment的形式, 相對Activity更加靈活和效率. 使用方法和一般Fragment一樣.

public static class SettingsFragment extends PreferenceFragment {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        addPreferencesFromResource(R.xml.preferences);
    }
    ...
}
複製程式碼

屬性

以下介紹的是所有Preference標籤通用的屬性

  • android:key

    等同於SharePreference中的key值, 對於某些不需要儲存資料的首選項可以不填寫該屬性(例如PreferenceCategory和PreferenceScreen)

  • android:title

    提供使用者可見的標題

  • android:defaultValue

    在SharePreference中的預設值

  • android:summary

    描述性文字

  • android:icon

    圖示

示例

演示所有的標籤

<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" android:key="@string/app_name">
    <CheckBoxPreference android:title="CheckBoxPreference"/>
    <SwitchPreference android:title="SwitchPreference"/>
    <PreferenceCategory android:title="PreferenceCategory"/>
    <ListPreference android:title="ListPreference"/>
    <EditTextPreference android:title="EditTextPreference"/>
    <MultiSelectListPreference android:title="MultiSelectListPreference"/>
    <RingtonePreference android:title="RingtonePreference"/>
    <Preference android:title="Preference"/>
</PreferenceScreen>
複製程式碼
Preference_Android原生設定介面

PreferenceScreen

表示一個首選項介面(首選項佈局必須以該標籤為根標籤)

<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
    
</PreferenceScreen>
複製程式碼

PreferenceCategory

首選項介面中的分類, 分類的顏色預設是主題ColorAccount

該Preference沒有分割線, 如果想去除分割線可以考慮自定義該PreferenceCategory

ListPreference

首選項中的列表

列表需要兩個必須的選項.如果沒有就Crash

  • 列表鍵(標題)
  • 列表值
<ListPreference
        android:entries="@array/setting_list"
        android:entryValues="@array/setting_list_values"
        android:title="ListPreference"/>
複製程式碼
Preference_Android原生設定介面

列表對話方塊還可以修改, 其實只要是DialogPreference的子類都支援這些屬性.

Preference_Android原生設定介面

這些屬性分別對應對話方塊的

  1. 圖示
  2. 佈局
  3. 訊息(和傳統對話方塊一樣如果設定了訊息就沒用了列表)
  4. 標題

EditTextPreference

輸入首選項

void onSetInitialValue (boolean restoreValue, 
                Object defaultValue)
複製程式碼

SwitchPreference

開關描述屬性, 該屬性CheckBoxPreference同樣支援

    <SwitchPreference
        android:summaryOff="關閉"
        android:summaryOn="開啟"
        android:title="SwitchPreference"/>
複製程式碼
Preference_Android原生設定介面

監聽器

點選事件

getPreferenceScreen().setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
  @Override public boolean onPreferenceClick(Preference preference) {
    return false;
  }
});
複製程式碼

屬性

android:title // 標題

android:summary // 副標題

android:enabled // 是否可用

android:selectable // 是否可選, 如果處於不可選狀態將沒有分割線, 也不可用狀態

android:icon // 圖示

android:key // sharepreference key

android:defaultValue // 預設值

android:order // 排列順序

android:shouldDisableView // 如果處於不可用狀態, 是否"灰色"

android:persistent // 是否持久化(即儲存到SharePreference)

android:fragment // 無效果
複製程式碼

自定義

自定義檢視

void setLayoutResource (int layoutResId)

void setWidgetLayoutResource (int widgetLayoutResId)

void setDialogLayoutResource (int dialogLayoutResId)
複製程式碼

前面提到過onCreate裡面不能setContent否則會報空指標, 其實只是因為找不到對應的id

@Override protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);

  setContentView(R.layout.activity_setting);
  addPreferencesFromResource(R.xml.demo);
}
複製程式碼

layout/activity_setting.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >

    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:navigationIcon="@drawable/ic_action_back"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@color/colorPrimary"
        app:popupTheme="@style/Theme.AppCompat.Light"
        app:theme="@style/Theme.AppCompat" />

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:padding="15dp" >

        <ListView
            android:id="@android:id/list"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
      
    </FrameLayout>

</LinearLayout>
複製程式碼

重寫

介紹下官方重寫示例: 自定義DialogPreference

自定義 DialogPreference 可以使用下面的建構函式來宣告佈局併為預設的肯定和否定對話方塊按鈕指定文字:

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);
    }
    ...
}
複製程式碼

或者你通過重寫

    @Override protected View onCreateDialogView() {

        View view = View.inflate(getContext(), R.layout.dialog_preference, null);

        return view;
    }
複製程式碼

儲存設定的值

如果設定的值為整型數或是用於儲存布林值的 persistBoolean(),則可通過呼叫 Preference 類的一個 persist*() 方法(如 persistInt())隨時儲存該值。

**注:**每個 Preference 均只能儲存一種資料型別,因此您必須使用適合自定義 Preference 所用資料型別的 persist*() 方法。

至於何時選擇保留設定,則可能取決於要擴充套件的 Preference 類。如果擴充套件 DialogPreference,則只能在對話方塊因肯定結果(使用者選擇“確定”按鈕)而關閉時保留該值。

DialogPreference 關閉時,系統會呼叫 onDialogClosed() 方法。該方法包括一個布林引數,用於指定使用者結果是否為“肯定”;如果值為 true,則表示使用者選擇的是肯定按鈕且您應該儲存新值。 例如:

@Override
protected void onDialogClosed(boolean positiveResult) {
    // 當點選確定時儲存資料
    if (positiveResult) {
        persistInt(mNewValue);
    }
}
複製程式碼

關於預設值

首先需要設定一個預設值

Preference_Android原生設定介面

重寫onGetDefaultValue查詢該預設值並返回

@Override
protected Object onGetDefaultValue(TypedArray a, int index) {
    return a.getInteger(index, 0);
}
複製程式碼

重寫onSetInitialValue判斷是進行恢復資料還是初始化預設值.

如果沒有實現上面的方法defaultValue是為null. 並且restorePersistedValue為false時並不會回撥該方法

    /**
     * @param restorePersistedValue 是否有可恢復資料
     * @param defaultValue 預設值
     */
@Override
protected void onSetInitialValue(boolean restorePersistedValue, Object defaultValue) {
    if (restorePersistedValue) {
        // 恢復資料
        mCurrentValue = this.getPersistedInt(DEFAULT_VALUE);
    } else {
        // 初始化預設值
        mCurrentValue = (Integer) defaultValue;
        persistInt(mCurrentValue);
    }
}
複製程式碼

屬性

依賴

通過填寫其他Preference的title可以依賴他. 如果是相同的Preference則會同步屬性, 如果不同則會同步其是否可用狀態.

android:dependency="notifications_new_message"
複製程式碼

自定義檢視

前面介紹過程式碼自定義, 這裡介紹下XML Attributes

android:layout // 自定義顯示內容

android:widgetLayout // 同樣是自定義顯示內容
複製程式碼

區分:

layout

  		<img src="https://ws1.sinaimg.cn/large/006tNc79gy1fi3vx983pdj30u01hc3zw.jpg" width="250"/> 
複製程式碼

widgetLayout

Preference_Android原生設定介面

可以看出layout屬於完全自定義, 而widgetLayou仍然被約束了邊距. 分割線兩者都有

更多

預設值

前面提過Preference標籤都有一個defaultValue的屬性, 該屬性可以設定一個首選項的預設值.

除了在XML中設定還需要在Application或者主Activity(僅推薦非強制)中執行重置預設值操作

PreferenceManager.setDefaultValues(this, R.xml.advanced_preferences, false);
複製程式碼

引數:

  • 上下文Context

  • 首選項XML

  • 首選項XML中有一個鍵為KEY_HAS_SET_DEFAULT_VALUES的值表示是否設定過預設值.

    這第三個引數如果為true則不論是否設定過預設值都進行初始化重置, 如果為false則要判斷是否設定過預設值.

子螢幕顯示

經常可以看到應用的詳細設定不在首選項介面的主介面而是需要繼續開啟一個子螢幕進行更詳細的設定

標頭: 通過將preference-headers設為根標籤, 用header來控制多個Fragment作為子螢幕

<preference-headers xmlns:android="http://schemas.android.com/apk/res/android">
    <header android:fragment="com.tianyachuanmei.preperence.AboutFragment" android:title="關於" />
    <header android:fragment="com.tianyachuanmei.preperence.MoreFragment" android:title="更多"/>
</preference-headers>
複製程式碼

該方法不能通過以下方法載入, 同樣該方法也被廢棄.

addPreferencesFromResource(R.xml.pref_headers);
複製程式碼

通過重寫PreferenceActivity的方法來載入XML, 如果不是headers不需要重寫該方法()

    @Override @TargetApi(Build.VERSION_CODES.HONEYCOMB) public void onBuildHeaders(List<Header> target) {
        loadHeadersFromResource(R.xml.pref_general, target);
    }
複製程式碼

Tip: loadHeadersFromResource不能載入非HeadersXML檔案, 否則Crash.

點選事件**: 通過給Preference指定跳轉的Activity來實現子螢幕

<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
    <Preference
        android:title="@string/prefs_category_one"
        android:summary="@string/prefs_summ_category_one"  >
        <intent
            android:targetPackage="com.example.prefs"
            android:targetClass="com.example.prefs.SettingsActivity"
            android:action="com.example.prefs.PREFS_ONE" />
    </Preference>
    <Preference
        android:title="@string/prefs_category_two"
        android:summary="@string/prefs_summ_category_two" >
        <intent
            android:targetPackage="com.example.prefs"
            an droid:targetClass="com.example.prefs.SettingsActivity"
            android:action="com.example.prefs.PREFS_TWO" />
    </Preference>
</PreferenceScreen>
複製程式碼

多皮膚

Preference_Android原生設定介面

多皮膚需要使用PreferenceActivity來實現, 通過重寫onIsMultiPane返回true來啟用多皮膚模式

    @Override public boolean onIsMultiPane() {
      // 以下是判斷當前裝置是否是大螢幕
        return (getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_XLARGE;
    }
複製程式碼

意圖(Intent)

<PreferenceScreen
                  android:summary="summary_intent_preference"
                  android:title="title_intent_preference" >

  <intent
          android:action="android.intent.action.VIEW"
          android:data="http://www.android.com" />
</PreferenceScreen>
複製程式碼

您可以使用以下屬性建立隱式和顯式 Intent:

  • android:action

    要分配的操作(按照 setAction() 方法)。

  • android:data

    要分配的資料(按照 setData() 方法)。

  • android:mimeType

    要分配的 MIME 型別(按照 setType() 方法)。

  • android:targetClass

    元件名稱的類部分(按照 setComponent() 方法)。

  • android:targetPackage

    元件名稱的軟體包部分(按照 setComponent() 方法)。

<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
    <Preference
        android:title="prefs_category_one"
        android:summary="prefs_summary_category_one">
        <!-- 這裡android:targetPackage是應用程式的Context,而android:targetClass的路徑在子包settings下 -->
        <!-- 如果把 android:targetPackage="com.demo.artshell.uidemo.settings" 執行時找不到Activity -->
        <intent
            android:action="prefs_category_action_ONE"
            android:targetPackage="com.demo.artshell.uidemo"
            android:targetClass="com.demo.artshell.uidemo.settings.SupportOldVersionAndReusedActivityOrFragment$ReusedActivity">
            <!-- 官網沒有說明,但確實可以通過<extra>傳附加資訊 getIntent().getStringExtra("reused_key") -->
            <extra
                android:name="reused_key"
                android:value="reused_fragment_two"/>
        </intent>
    </Preference>

    <Preference
        android:title="prefs_category_two"
        android:summary="prefs_summary_category_two">
        <intent
            android:action="prefs_category_action_TWO"
            android:targetPackage="com.demo.artshell.uidemo"
            android:targetClass="com.demo.artshell.uidemo.settings.SupportOldVersionAndReusedActivityOrFragment$ReusedActivity"/>
    </Preference>
</PreferenceScreen>
複製程式碼

相關文章