Preference 使用小結

在Symbian實現類似如下配置參數的設置界面

clip_image002

需要複雜的自定義列表來實現,在android中由於SDK封裝和提供了一套基於Preference的類,使用Preference通過編輯xml配置文件,只要很少的代碼就可以實現了,而且Preference本身已經實現了參數保存,不需要我們再考慮將參數保存文件,下面讓我們來認識下Preference。

PreferenceActivity佈局文件

Preference需要通過Activity才能顯示出來,SDK封裝了一個抽象類PreferenceActivity專門提供我們派生自己需要的Activity。和Activtiy需要layout佈局一樣,這裏的PreferenceActivity實例化的時候也是需要XML佈局文件,該佈局文件可以通過“File”“New”“Android XML File”菜單彈出如下對話框來生成

clip_image004

在資源類型中選擇Preference,在root element中保持默認即選擇PreferenceScreen,否則在Activity中綁定該資源時,將報“java.lang.RuntimeException: Unable to start activity……”的類似錯誤。

其實在Preference XML資源文件中,元素標籤(element)類型主要有有兩類:一類是管理佈局的有PreferenceScreen和PreferenceCategory;另一類是具體的設置元素,有CheckBoxPreference、ListPreference、EditTextPreference和RingtonePreference等。假設我們要實現如下圖所示的效果

clip_image006

首先,我們需要生成一個Preference資源文件,命名爲preferencescategory.xml,具體內容如下

<?xml version="1.0" encoding="utf-8"?>

<PreferenceScreen

xmlns:android="http://schemas.android.com/apk/res/android"

android:title="Settings">

<PreferenceCategory

xmlns:android="http://schemas.android.com/apk/res/android"

android:title="Emotions"

android:summary="settings about emotions">

<CheckBoxPreference

android:title="Love me?"

android:summaryOn="Yes,I love you!"

android:summaryOff="No,I am sorry."

android:defaultValue="true" android:key="@string/category_loveme_key">

</CheckBoxPreference>

<CheckBoxPreference

android:title="Hate me?"

android:summaryOn="Yes,I hate you!"

android:summaryOff="No,you are a good person."

android:defaultValue="false">

</CheckBoxPreference>

</PreferenceCategory>

<PreferenceCategory

xmlns:android="http://schemas.android.com/apk/res/android"

android:title="Relations"

android:summary="settings about relations">

<CheckBoxPreference

android:title="Family?"

android:summaryOn="Yes,we are family!"

android:summaryOff="No,I am sorry."

android:defaultValue="true">

</CheckBoxPreference>

<CheckBoxPreference

android:title="Friends?"

android:summaryOn="Yes,we are friends!"

android:summaryOff="No,I am sorry."

android:defaultValue="false">

</CheckBoxPreference>

</PreferenceCategory>

</PreferenceScreen>

其次,我們從PreferenceActivity派生一個PreferenceCategoryActivity類,具體代碼如下

public class PreferenceCategoryActivity extends PreferenceActivity

{

@Override

public void onCreate(Bundle savedInstanceState)

{

super.onCreate(savedInstanceState);

addPreferencesFromResource(R.xml.preferencescategory);

}

}

再次,將這個Activity添加到AndroidManifest.xml中,假設該Activity被設置爲起始Activity,那麼程序一運行就呈現上述界面。

Preference配置參數的保存

其實通過上述的列子,我們可以看到當修改參數配置後,退出程序,再重新進入程序,出現的配置參數是更改以後,而並非我們初始化設置的。這個參數配置保存功能是怎麼實現的呢?答案是SDK提供了一個SharedPreferences來實現上述功能的。所以很多參考書中將SharedPreferences與文件和SQLite一起被放置在Android數據存儲章節,用以獲取和修改持久化存儲的數據。需要注意的是這種方式主要用來存儲比較簡單的一些數據,而且是標準的Boolean、Int、Float、Long、String等類型

那麼這些參數具體保存在哪裏呢?通過模擬器我們可以發現,在data/data/包名/shared_prefs/下面存在着若干xml文件,具體如下圖所示

clip_image008

Android就是靠這些文件來實現參數配置的保存的。我們如何通過SharedPreferences來實現對這些文件訪問和修改呢。

SDK提供了三個獲取SharedPreferences的函數,分別是

public SharedPreferences getPreferences (int mode)

public SharedPreferences getSharedPreferences (String name, int mode)

public static SharedPreferences getDefaultSharedPreferences (Context context)

前兩個是非靜態類,需要通過具體的Acitvity對象或者在Activity對象內調用,最後一個可以通過靜態方法調用。

第一個getPreferences函數,操作的是屬於Activity自身的Preference參數配置文件,文件名是Actvity的類名,一個Activity只能有一個該類配置文件,比如上述圖示中的PreferenceDemoActivity.xml就是屬於PreferenceDemoActivity參數配置文件。

第二個getSharedPreferences函數,操作的是屬於整個應用程序的參數配置文件,一個應用程序可以包含有多個該類配置文件,文件名是函數第一參數的name。在上述圖示中就是形如loveme.xml這類文件。

第三個getDefaultSharedPreferences函數,操作的也是屬於整個應用程序的參數配置文件,不過該類文件一個應用程序只有一個,文件名爲“包名_preferences”,這類配置文件就是用來保存Preference佈局文件中元素定義的參數配置的。

有了SharedPreferences之後,我們就可以通過其提供的如下接口函數來獲取儲存的配置參數。

boolean getBoolean(String key, boolean defValue);

float getFloat(String key, float defValue);

long getLong(String key, long defValue);

int getInt(String key, int defValue);

String getString(String key, String defValue);

Map<String, ?> getAll();

也可以通過SharedPreferences.Editor來修改配置參數,具體見如下代碼

SharedPreferences vPreferences = getSharedPreferences(checkbox_key, Activity.MODE_PRIVATE);

boolean vLoveme = vPreferences.getBoolean(checkbox_key, true);

SharedPreferences.Editor editor = vPreferences.edit();

editor.putBoolean(checkbox_key, false);

editor.commit();

攔截監聽接口

當PreferenceActivity中的內容改變時,Android系統會自動進行保存和持久化維護,我們只需要在要用的設置界面中需要數據的地方進行讀取就可以了。同時Android還提供了OnPreferenceClickListener和OnPreferenceChangeListener兩個與Preference相關的監聽接口,當PreferenceActivity中的某一個Preference進行了點擊或者改變的操作時,都會回調接口中的函數,這樣可以第一個時間向其他Activity等通知系統設置進行了改變。下面提供一份攔截監聽的代碼

public class PreferenceDemoActivity extends PreferenceActivity implements OnPreferenceChangeListener,

OnPreferenceClickListener

{

/** Called when the activity is first created. */

CheckBoxPreference vCheckBox;

String checkbox_key;

@Override

public void onCreate(Bundle savedInstanceState)

{

super.onCreate(savedInstanceState);

// setContentView(R.layout.main);

addPreferencesFromResource(R.xml.pref);

checkbox_key = getResources().getString(R.string.love_me);

vCheckBox = (CheckBoxPreference)findPreference(checkbox_key);

//註冊修改函數

vCheckBox.setOnPreferenceChangeListener(this);

vCheckBox.setOnPreferenceClickListener(this);

}

@Override

public boolean onPreferenceChange(Preference preference, Object newValue)

{

// TODO Auto-generated method stub

//判斷是哪個Preference改變了

if(preference.getKey().equals(checkbox_key))

{

}

else

{

//如果返回false表示不允許被改變

return false;

}

//返回true表示允許改變

return true;

}

@Override

public boolean onPreferenceClick(Preference preference) {

// TODO Auto-generated method stub

//判斷是哪個Preference被點擊了

if(preference.getKey().equals(checkbox_key))

{

}

else

{

return false;

}

return true;

}

}

通過調用發現當點擊CheckBox時,先調用onPreferenceChange,之後再調用onPreferenceClick,所以個人感覺,很多情況如果只對參數感興趣,可以不用攔截點擊監聽。

以上是對Preference基礎使用的小結,系統提供的Preference畢竟太少,爲此我們通常需要用到自定義的Preference。下面就介紹自定義Preference的使用。

自定義Preference

系統提供的Preference樣式還是少了點,爲了呈現豐富的UI,很多時候我們需要自定義Preference,自定義Preference有兩種實現方法:第一種實現方法僅通過資源xml的修改來實現自定義Preference的效果;第二種方法是通過派生類的方法來實現自定義Preference的效果。下面分別闡述如下:

修改資源的方法

系統默認的Preference風格是黑底白字的樣式,有時候我們需要改變下字體顏色或者字體類型,抑或我們想要修改下CheckBox的圖標不是系統自帶的勾子,而是自定義的圖標,假設入下圖的效果

clip_image010

那麼該如何實現呢?

上述樣式的實現,藉助於每個Preference的android:layout和android:widgetLayout屬性,給其重新佈局,重新佈局的時候需要參考frameworks\base\core\res\res下面的原有佈局,否則在不清楚其資源格式的情況下進行修改往往達不到效果,通過這裏的嘗試,我發現假設自定義的資源在在加載過程中出錯或者類似解析不符,那麼系統會默認加載缺省的資源。

下面針對上圖自定義preference中的第一個CheckBoxPreference的實現,給出參考了frameworks\base\core\res\res\layout文件夾下面的preference.xml佈局文件,增添文本顏色等參數而重構的custom_preferece_layout.xml文件的內容(其中的紅色字體就是原有基礎上新增加的屬性)

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

android:layout_width="fill_parent"

android:layout_height="wrap_content"

android:minHeight="?android:attr/listPreferredItemHeight"

android:gravity="center_vertical"

android:paddingRight="?android:attr/scrollbarSize">

<RelativeLayout android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_marginLeft="15dip"

android:layout_marginRight="6dip"

android:layout_marginTop="6dip"

android:layout_marginBottom="6dip"

android:layout_weight="1">

<TextView android:id="@+android:id/title"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:singleLine="true"

android:textAppearance="?android:attr/textAppearanceLarge"

android:ellipsize="marquee"

android:fadingEdge="horizontal"

android:textColor="#00FF00" />

<TextView android:id="@+android:id/summary"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_below="@android:id/title"

android:layout_alignLeft="@android:id/title"

android:textAppearance="?android:attr/textAppearanceSmall"

android:textColor="#FF0000"

android:textStyle="bold|italic"

android:maxLines="4" />

</RelativeLayout>

<!-- Preference should place its actual preference widget here. -->

<LinearLayout android:id="@+android:id/widget_frame"

android:layout_width="wrap_content"

android:layout_height="fill_parent"

android:gravity="center_vertical"

android:orientation="vertical" />

</LinearLayout>

如上修改很少,但是效果達到了。至於將默認的打勾圖標,切換成自己的圖標,也是依樣畫葫蘆,找到源碼frameworks\base\core\res\res\layout文件夾下的preference_widget_checkbox.xml,然後重構爲custom_check_widget.xml,內容如下(其中紅色字體時原有佈局基礎上新增加的,關於其中button的實現就不貼出來,參考demo程序吧)

<?xml version="1.0" encoding="utf-8"?>

<CheckBox xmlns:android="http://schemas.android.com/apk/res/android"

android:id="@+android:id/checkbox"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:button="@drawable/selfcheckbtn"

android:layout_marginRight="4dip"

android:layout_gravity="center_vertical"

android:focusable="false"

android:clickable="false" />

在Preference的資源文件中定義這個CheckBoxPreference的代碼就如下所示

<CheckBoxPreference

android:defaultValue="true"

android:summaryOff="禁止自動搜索"

android:summaryOn="允許自動搜素"

android:key="auto_search_enable_key"

android:title="自動搜素"

android:disableDependentsState="false"

android:layout="@layout/custom_preferece_layout"

android:widgetLayout="@layout/custom_check_widget">

</CheckBoxPreference>

通過修改資源佈局來實現自定義Preference的樣式,假如對Android的資源深入瞭解後實現起來就能隨心所欲而且手到擒來了,具體就不展開了,詳見示例程序。

派生類的方法

上面方法僅僅從資源的角度去修改Preference的樣式,其實SDK提供了Preference基類,我們除了可以使用系統提供的CheckBoxPreference、ListPreference、EditTextPreference和RingtonePreference外,還可以自己定義我們需要的Preference。遵循從資源到代碼的邏輯,使用系統自帶的CheckBoxPreference我們可以方便地再xml文件中使用“CheckBoxPreference”元素來定義,那麼派生類Preference的資源元素該用什麼標籤呢?

自定義的Preference的資源元素標籤就是自定義Preference的包名加類名,下面我們實現一個自定義的帶圖標的設置項,由於類名爲ImageOptionPreference,而包名爲netease.frank.demo.selfImagePreference,所以資源中設置的元素名爲netease.frank.demo.selfImagePreference.ImageOptionPreference,具體的xml文件內容如下

<?xml version="1.0" encoding="utf-8"?>

<PreferenceScreen

xmlns:android="http://schemas.android.com/apk/res/android">

<netease.frank.demo.selfImagePreference.ImageOptionPreference

android:title="發送短信"

android:summary="點擊按鈕將切入短信發送界面"

android:key="game_pic"

android:widgetLayout="@layout/preference_widget_image"/>

</PreferenceScreen>

在xml中用到一個widgetLayout,其xml文件內容如下所示

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout

xmlns:android="http://schemas.android.com/apk/res/android"

android:orientation="vertical"

android:layout_width="fill_parent"

android:layout_height="fill_parent">

<ImageView

android:id="@+id/pref_current_img"

android:src="@drawable/icon"

android:layout_marginRight="4dip"

android:layout_height="54dip"

android:padding="2dip"

android:layout_width="54dip"

android:layout_gravity="center_vertical"

android:focusable="false"

android:clickable="false" />

</LinearLayout>

類定義代碼,我們需要提供一個構造函數和一個點擊時操作函數(在這裏我們讓其發送一條短信)既可,簡單代碼如下

public class ImageOptionPreference extends Preference

{

public ImageOptionPreference(Context context, AttributeSet attrs)

{

super(context, attrs);

}

@Override

protected void onClick()

{

// super.onClick();

Uri uri = Uri.parse("smsto:0800000123");

Intent it = new Intent(Intent.ACTION_SENDTO, uri);

it.putExtra("sms_body", "The SMS text");

this.getContext().startActivity(it);

}

}

這樣我們就可以如同SDK提供的CheckboxPreference一樣在PreferenceActivity中使用我們自己的定義的ImageOptionPreference。

上面這個自定義ImageOptionPreference派生自Preference,很多情況下我們不需要從這麼底層的類派生,而從DialogPreference派生我們的需求類就可以了,下面演示一個時間設置的空間。我們將這個時間設置類封裝爲類名爲TimePreference,而包名爲 netease.frank.demo.selfImagePreference,我們將其放置在上述這個ImageOptionPreference的下面一項,在selfimagepre.xml的定義就如下所示

<?xml version="1.0" encoding="utf-8"?>

<PreferenceScreen

xmlns:android="http://schemas.android.com/apk/res/android">

<netease.frank.demo.selfImagePreference.ImageOptionPreference

android:title="發送短信"

android:summary="點擊按鈕將切入短信發送界面"

android:key="game_pic"

android:widgetLayout="@layout/preference_widget_image"/>

<netease.frank.demo.selfImagePreference.TimePreference

android:key="time_test"

android:summary="打開修改時間"

android:title="時間設置"

android:defaultValue="1000000"/>

</PreferenceScreen>

上面的android:defaultValue是當參數沒有設置情況下的缺省值。自定義DialogPreferenc自然我們還需要一個滿足我們需求的對話框佈局文件,這裏的時間設置對話框佈局time_preference.xml文件,具體代碼如下所示

<?xml version="1.0" encoding="utf-8"?>

<RelativeLayout

xmlns:android="http://schemas.android.com/apk/res/android"

android:orientation="vertical"

android:layout_width="fill_parent"

android:layout_height="fill_parent">

<TimePicker

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:id="@+id/timePicker_preference"

android:layout_centerHorizontal="true">

</TimePicker>

</RelativeLayout>

而相應的類的實現代碼,如下所示:

public class TimePreference extends DialogPreference

{

TimePicker mPicker = null;

long mValue ;

public TimePreference(Context context, AttributeSet attrs)

{

super(context, attrs);

//設置對話框需要加載的佈局文件

setDialogLayoutResource(R.layout.time_preference);

}

//彈出對話框的時候進行初始化

@Override

protected void onBindDialogView(View view)

{

super.onBindDialogView(view);

mPicker = (TimePicker)view.findViewById(R.id.timePicker_preference);

if(mPicker != null)

{

mPicker.setIs24HourView(true);

long value = mValue;

Date d = new Date(value);

mPicker.setCurrentHour(d.getHours());

mPicker.setCurrentMinute(d.getMinutes());

}

}

//關閉對話框的時候保存

@Override

protected void onDialogClosed(boolean positiveResult)

{

super.onDialogClosed(positiveResult);

if(positiveResult)

{

Date d = new Date(0, 0, 0, mPicker.getCurrentHour(), mPicker.getCurrentMinute(), 0);

long value = d.getTime();

mValue = value;

if(callChangeListener(value))

{

SharedPreferences.Editor vEditor = getEditor();

vEditor.putLong(getKey(), value);//(checkbox_key, false);

vEditor.commit();

}

}

}

//獲取缺省的配置參數

@Override

protected Object onGetDefaultValue(TypedArray a, int index)

{

mValue = Long.parseLong(a.getString(index));

return mValue;

}

//獲取sharepreference中的配置參數,該函數在配置文件存在時纔會被調用

@Override

protected void onSetInitialValue(boolean restorePersistedValue, Object defaultValue)

{

long value;

if(restorePersistedValue)

value = getPersistedLong(1000000);

else

{

value = Long.parseLong(defaultValue.toString());

}

setDefaultValue(value);

mValue = value;

}

}

在上述的TimePreference類的代碼過程中,需要注意以下幾點:

onGetDefaultValue的調用,先於TimePreference的構造函數之前運行,從而確保缺省參數的獲得;

查看一下DialogPreference的onClick函數的實現,我們就知道在這裏我們爲什麼不用重載OnClick,而是重載了onBindDialogView函數就可以了,同理如果我們想重載OnClick函數也是可行的;

雖然獲取參數,系統幫我們實現了,但是保存參數的操作,還是需要我們自己來提供,所以在對話框關閉的onDialogClosed,我們對設置的時間值進行了保存;

關於“包名_preferences.xml”參數配置文件,程序一開始運行的時候是不存在的,所以第一次運行程序時,程序不會調用onSetInitialValue,只有當程序執行過一次保存後,參數配置文件才被創建,從而纔會被執行調用。

關於Preference就介紹到這裏,非常感謝老華的指點。

 

另,本小結提供三個demo程序,分別如下

PreferencesDemo 用於演示和說明系統原有的Preference的特性和使用

PreferenceDemo 用於演示和說明Preference的三種SharedPreferences保存類型

selfImagePreferenceDemo 用於演示和說明自定義Preference的兩種方法

demo和原文下載

發佈了4 篇原創文章 · 獲贊 5 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章