在app的开发中,让用户设置自己的偏好,能给用户很友好的体验。在android系统中,google很贴心的提供了SharePreference组件,方便开发者存储app的数据。SharePreference提供的API,对简单的基本类型数据,以键值对(key-value)的方式进行的存储,使用极其简单。具体使用教程可以留意google的官方文档,这篇blog主要是介绍使用简单工厂模式开发基于SharePreference的用户偏好设置界面。
先介绍基本的组件,SharePreference数据的可视化的组件PreferenceActivity,以及交互响应接口OnPreferenceChangeListener, OnPreferenceClickListener。
我们可以利用PreferenceActivity的可视化设计器,设计基本的UI控件,并设置其对应的信息,具体如何去设置请看官自己找资料了。
下面是我的一个例子:
<?xml version="1.0" encoding="utf-8"?> <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" android:title="@string/system_settings" > <PreferenceCategory android:title="@string/email_category" > <EditTextPreference android:dialogTitle="@string/change_email_dialog_title" android:key="@string/change_email_account_key" android:negativeButtonText="@string/cancel" android:positiveButtonText="@string/config" android:summary="@string/change_email_account_summary" android:title="@string/change_email_account_title" android:selectable="true"/> </PreferenceCategory> <PreferenceCategory android:title="@string/ring_category" > <CheckBoxPreference android:dependency="@string/receive_sms_command_key" android:enabled="true" android:key="@string/ring_key" android:summaryOff="@string/ring_summary_off" android:summaryOn="@string/ring_summary_on" android:title="@string/ring_title" android:selectable="true" android:defaultValue="true"/> </PreferenceCategory> </PreferenceScreen>
之后可以在PreferenceActivity通过方法addPreferencesFromResource加载该xml文件即可显示你设计好的偏好设置界面。
之后就是监听用户的交互动作,一般就是Preference的OnClick事件还有OnChange事件,并在对应的监听器中加以处理响应,完成设置流程。
这里值得注意的是,即使你不去监听并响应一般事件,Preference也会自动保存用户更改操作后的状态,你的app可以通过具体的Preference引用获取到其当前的value。当然你也可以通过在OnChange事件响应中,把具体的状态值键值对形式保存到SharePreference中,以便app能更方便地访问到用户设置的value。
可是这样子的话,问题就来了。我们得在监听器中为每一个Preference发生的事件进行监听并响应!假如Preference只有几个还比较容易维护,但是app的迭代周期是很短的,几次迭代后,需求可能会变化巨大,十几个Preference的事件响应代码就难以修改和维护了。而且这也违反了对修改封闭,对扩展开放的原则。
然而每个Preference的行为又是那么的简单,只有onClick和onChange!为了简便各Preference的行为响应,我们可以利用简单工厂模式以及Java强大的反射机制来进行设计!
简单工厂的原理如下:
我们的思路如下:
1、设计一个行为接口IPrefListener ,定义行为doWhenOnClick和doWhenChange:
public interface IPrefListener { public boolean doWhenChange(Context ctx, Object o); public boolean doWhenOnClick(Preference pref); }
2、通过实现IPrefListener,使具体的Preference具有各自的响应行为。
public class EmailPrefChangeListener implements IPrefListener { @Override public boolean doWhenChange(Context ctx, Object o) { String address = (String) o; boolean isOk = BaseUtils.isRightEmailAddress(address); if(isOk){ updateCache(ctx,address); showTipsForUser(ctx); } else { DialogFactory df = new DialogFactory(ctx); Dialog dialog = df.onCreateDialog(DialogFactory.DIALOG_ID_EMAIL_ERROR); dialog.show(); } return isOk; } private void updateCache(Context ctx, String account){ SharePrefHelper.getInstance(ctx).writeData(PrefConst.KEY_EMAIL_ACCOUNT, account); } private void showTipsForUser(Context ctx){ String tips = ctx.getResources().getString(R.string.toast_email_account_change_succ); Toast.makeText(ctx, tips, Toast.LENGTH_SHORT).show(); } @Override public boolean doWhenOnClick(Preference pref) { String defaultValue = SharePrefHelper.getInstance(pref.getContext()).readData(PrefConst.KEY_EMAIL_ACCOUNT, ""); EditTextPreference etPref = (EditTextPreference) pref; etPref.getEditText().setText(defaultValue); return true; } }
3、然后利用PreferenceActivity作为工厂,并实现OnPreferenceChangeListener, OnPreferenceClickListener接口响应各Preference的事件。
初始化布局中的Preference的监听器,并在事件响应方法中,利用Preference的key反射获得具体的IPrefListener 实现类的实例,再去调用对应的方法:
@Override public boolean onPreferenceChange(Preference preference, Object newValue) { Log.i(TAG, preference.getKey()+"had change, new value:"+newValue); boolean isChangeSucc = true; String key = preference.getKey(); try { //利用java的反射机制,new出一个负责当前设置变更响应的处理类实例 IPrefListener listener = (IPrefListener) this.getApplicationContext() .getClassLoader().loadClass(key).newInstance(); isChangeSucc = listener.doWhenChange(SettingActivity.this, newValue); } catch (Exception e) { e.printStackTrace(); } return isChangeSucc; } @Override public boolean onPreferenceClick(Preference preference) { Log.i(TAG, preference.getKey()+"had click"); boolean isChangeSucc = true; String key = preference.getKey(); try { //利用java的反射机制,new出一个负责当前设置变更响应的处理类实例 IPrefListener listener = (IPrefListener) this.getApplicationContext() .getClassLoader().loadClass(key).newInstance(); isChangeSucc = listener.doWhenOnClick(preference); } catch (Exception e) { e.printStackTrace(); } return isChangeSucc; }
上面我们有一个技巧,就是把具体的IPrefListener 实现类的完整类名作为Preference的key,通过这样,实现接口的类就和对应的Preference联系起来了!
以后再有偏好设置要增加进来,只需要在布局文件布局好,然后实现IPrefListener的对应类, 把其完整的类名配置到String.xml中,并于布局文件中引用即可,这样子实现响应的代码就与控制的代码分离了,结构更清晰,维护更简单,当然也符合对修改封闭,对扩展开放的原则!