在Symbian實現類似如下配置參數的設置界面
需要複雜的自定義列表來實現,在android中由於SDK封裝和提供了一套基於Preference的類,使用Preference通過編輯xml配置文件,只要很少的代碼就可以實現了,而且Preference本身已經實現了參數保存,不需要我們再考慮將參數保存文件,下面讓我們來認識下Preference。
PreferenceActivity佈局文件
Preference需要通過Activity才能顯示出來,SDK封裝了一個抽象類PreferenceActivity專門提供我們派生自己需要的Activity。和Activtiy需要layout佈局一樣,這裏的PreferenceActivity實例化的時候也是需要XML佈局文件,該佈局文件可以通過“File”“New”“Android XML File”菜單彈出如下對話框來生成
在資源類型中選擇Preference,在root element中保持默認即選擇PreferenceScreen,否則在Activity中綁定該資源時,將報“java.lang.RuntimeException: Unable to start activity……”的類似錯誤。
其實在Preference XML資源文件中,元素標籤(element)類型主要有有兩類:一類是管理佈局的有PreferenceScreen和PreferenceCategory;另一類是具體的設置元素,有CheckBoxPreference、ListPreference、EditTextPreference和RingtonePreference等。假設我們要實現如下圖所示的效果
首先,我們需要生成一個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文件,具體如下圖所示
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的圖標不是系統自帶的勾子,而是自定義的圖標,假設入下圖的效果
那麼該如何實現呢?
上述樣式的實現,藉助於每個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的兩種方法