frank.sunny的个人技术空间
在互联网行业,实践总是走在理论的前面

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和原文下载

posted on 2011-10-21 09:57  frank.sunny  阅读(29762)  评论(1编辑  收藏  举报