setting

应用程序呢,通常会有一些设置,用户可以修改app的特性和行为。比如,一些app,用户可以指定是否可以推送通知,或者多长时间与云数据发送同步。

要为app提供这类的设置功能呢,就需要使用android的Preference API来构建一个与其他android应用程序(包括系统的设置)一致的界面。

这篇文章,就介绍一下怎样通过Preference API构建自己的app的设置。

一、简述设置界面的设计理念

详情查看http://developer.android.com/design/patterns/settings.html


这是短信应用程序的设置,选择一项打开一个新界面来更改设置。

二、概述

SharedPreferences能保存的数据类型只有以下几种:

Boolean

Float

Int

Long

String

String Set

由于应用程序的设置界面是用Preference对象构建的,而不是View对象,所以我们需要使用专门的Activity或者Fragment的子类,来展示设置列表。

1、如果app支持的版本低于3.0(API 10),那么就必须构建一个继承PreferenceActivity类的activity。

2、android 3.0之后的版本,就可以用PreferenceFragment了,而不用传统的Activity。当然屏幕够大的话,也可用PreferenceActivity来创建两个容器layout展示双重设置组(两个设置界面联动)。

具体怎么用,将在本文下面介绍。

三、在XML文件中定义preference

这里的XML文件必须保存在res/xml/目录下,系统也会自动生成xml目录。一般呢,会将其命名为preferences.xml,当然可以随意命名。一般只需要这一个文件(当然也有特殊情况,当用到<header>的时候就需要使用分离的xml文件,下面介绍),因为preference的一些子类都是嵌套在preferenceScreen节点里面的,例如:

<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
    <CheckBoxPreference
        android:key="pref_sync"
        android:title="@string/pref_sync"
        android:summary="@string/pref_sync_summ"
        android:defaultValue="true" />
    <ListPreference
        android:dependency="pref_sync"
        android:key="pref_syncConnectionType"
        android:title="@string/pref_syncConnectionType"
        android:dialogTitle="@string/pref_syncConnectionType"
        android:entries="@array/pref_syncConnectionTypes_entries"
        android:entryValues="@array/pref_syncConnectionTypes_values"
        android:defaultValue="@string/pref_syncConnectionTypes_default" />
</PreferenceScreen>

在这个例子中,有一个checkboxpreference和一个listpreference,这两个item都有以下三个属性:

android:key

指定在sharedpreferences中存储的唯一性的key(一个字符串类型),系统用这个key来存储响应的设置的值。

    这个属性有些情况不必写:

   <PreferenceScreen/>

           <PreferenceCategory/>

           Preference指定了intent或者fragment。

    <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>
-----------------------------------------------------------------------
    <header 
        android:fragment="com.example.prefs.SettingsFragmentOne"
        android:title="@string/prefs_category_one"
        android:summary="@string/prefs_summ_category_one" />

andorid:title

       如果不指定summary,那么就是这个item的显示文本。

       如果指定了summary,那么summary将在title下面显示。

android:defaultValue

       建议每个setting设置defaultValue。

四、创建分组

当设置项超过10个时,就应该考虑分组了。分组有两种实现方式:

1、使用titles

2、使用子屏幕

使用titles就像下面这样:



<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
    <PreferenceCategory 
        android:title="@string/pref_sms_storage_title"
        android:key="pref_key_storage_settings">
        <CheckBoxPreference
            android:key="pref_key_auto_delete"
            android:summary="@string/pref_summary_auto_delete"
            android:title="@string/pref_title_auto_delete"
            android:defaultValue="false"... />
        <Preference 
            android:key="pref_key_sms_delete_limit"
            android:dependency="pref_key_auto_delete"
            android:summary="@string/pref_summary_delete_limit"
            android:title="@string/pref_title_sms_delete"... />
        <Preference 
            android:key="pref_key_mms_delete_limit"
            android:dependency="pref_key_auto_delete"
            android:summary="@string/pref_summary_delete_limit"
            android:title="@string/pref_title_mms_delete" ... />
    </PreferenceCategory>
    ...
</PreferenceScreen>

使用子屏幕,就像下面这样:



<PreferenceScreen  xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- opens a subscreen of settings -->
    <PreferenceScreen
        android:key="button_voicemail_category_key"
        android:title="@string/voicemail"
        android:persistent="false">
        <ListPreference
            android:key="button_voicemail_provider_key"
            android:title="@string/voicemail_provider" ... />
        <!-- opens another nested subscreen -->
        <PreferenceScreen
            android:key="button_voicemail_setting_key"
            android:title="@string/voicemail_settings"
            android:persistent="false">
            ...
        </PreferenceScreen>
        <RingtonePreference
            android:key="button_voicemail_ringtone_key"
            android:title="@string/voicemail_ringtone_title"
            android:ringtoneType="notification" ... />
        ...
    </PreferenceScreen>
    ...
</PreferenceScreen>

ps.android 3.0版本之后,就不用嵌套的PreferenceScreen了,改用header了,下面会介绍。


另外,还可以使用intent。 因为有的时候,想通过设置项跳转到其他的activity,比如说打开一个网页,那就可以用intent

<Preference android:title="@string/prefs_web_page" >
    <intent android:action="android.intent.action.VIEW"
            android:data="http://www.example.com" />
</Preference>

intent标签还包含以下属性:

android:action
The action to assign, as per the setAction() method.
android:data
The data to assign, as per the setData() method.
android:mimeType
The MIME type to assign, as per the setType() method.
android:targetClass
The class part of the component name, as per the setComponent() method.
android:targetPackage
The package part of the component name, as per the setComponent() method. 五、创建preferenceActivity

android版本在3.0之前,用到的preferenceActivity的创建,一段代码可以概括:

public class SettingsActivity extends PreferenceActivity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        addPreferencesFromResource(R.xml.preferences);
    }
}

六、使用preferenceFragment

android版本3.0之后,就建议使用fragment替代activity了,因为fragment是更灵活的层级结构,灵活体现在任何一个activity都可以随意的装载该fragment。

同样使用两段代码也能说明问题:

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

        // Load the preferences from an XML resource
        addPreferencesFromResource(R.xml.preferences);
    }
    ...
}

public class SettingsActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Display the fragment as the main content.
        getFragmentManager().beginTransaction()
                .replace(android.R.id.content, new SettingsFragment())
                .commit();
    }
}

七、设置默认值

XML文件中设置默认值:

<!-- default value is a boolean -->
<CheckBoxPreference
    android:defaultValue="true"
    ... />

<!-- default value is a string -->
<ListPreference
    android:defaultValue="@string/pref_syncConnectionTypes_default"
    ... />

代码中动态设置默认值,就需要在onCreate()方法中添加以下代码:

PreferenceManager.setDefaultValues(this, R.xml.advanced_preferences, false);

关于这个方法的三个参数重点说说第三个参数:

第三个参数:是一个boolean值,指示默认值是否可以被设置多次。

如果是false,那么默认值只会在第一次调用时设置,以后即使调用也不再设置了。

如果是true,那么默认值需要在activity每次获取值之前先设置默认值。

所以,安全的做法是:false.


八、使用Preference Header

在一些比较少的情况下,可能希望提一个设置界面只单纯得显示列表,具体的设置,要到另一个界面去设置。

android版本3.0之前“子屏幕”用嵌套的preferenceScreen,3.0之后,就引入了header。

要使用header,就得做以下改变:

1、将每个“设置组”分离到单独一个preferenceFragment实例中,当然这样就伴随着,需要将xml按组分离成多个xml文件,分配到每个preferenceFragment实例中。

2、创建一个xml header文件,列出每个设置组,并关联响应的fragment。

3、继承PreferenceActivity类,来掌管设置。

4、实现onBuildHeader()方法,来指定header的xml文件。


1. The headers are defined with an XML headers file. 
2. Each group of settings is defined by a PreferenceFragment that's specified by a <header> element in the headers file.



 A handset device with setting headers. When an item is selected, the associated PreferenceFragment replaces the headers.


九、创建headers文件

用一段代码说明一下:

<?xml version="1.0" encoding="utf-8"?>
<preference-headers xmlns:android="http://schemas.android.com/apk/res/android">
    <header 
        android:fragment="com.example.prefs.SettingsActivity$SettingsFragmentOne"
        android:title="@string/prefs_category_one"
        android:summary="@string/prefs_summ_category_one" />
    <header 
        android:fragment="com.example.prefs.SettingsActivity$SettingsFragmentTwo"
        android:title="@string/prefs_category_two"
        android:summary="@string/prefs_summ_category_two" >
        <!-- key/value pairs can be included as arguments for the fragment. -->
        <extra android:name="someKey" android:value="someHeaderValue" />
    </header>
</preference-headers>

根节点变了,不再是preferenceScreen了,变成了preference-header。

支节点也不再是preference的子类了,而是header。

<extra />元素可以向fragment传递Bundle类型的键值对。fragment呢,可以通过getArgument()方法重新获取传递值。


十、显示header

要显示header呢,就必须实现onBuildHeader()方法,并且调用loadHeadersFromResource()方法。

public class SettingsActivity extends PreferenceActivity {
    @Override
    public void onBuildHeaders(List<Header> target) {
        loadHeadersFromResource(R.xml.preference_headers, target);
    }
}

ps.使用preference header 上面继承preferenceActivity的SettingsActivity就不必实现onCreate()方法了。


十一、读取preference值

SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
String syncConnPref = sharedPref.getString(SettingsActivity.KEY_PREF_SYNC_CONN, "");


十二、监听preference值的改变

public class SettingsActivity extends PreferenceActivity
                              implements OnSharedPreferenceChangeListener {
    public static final String KEY_PREF_SYNC_CONN = "pref_syncConnectionType";
    ...

    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
        if (key.equals(KEY_PREF_SYNC_CONN)) {
            Preference connectionPref = findPreference(key);
            // Set summary to be the user-description for the selected value
            connectionPref.setSummary(sharedPreferences.getString(key, ""));
        }
    }
}

@Override
protected void onResume() {
    super.onResume();
    getPreferenceScreen().getSharedPreferences()
            .registerOnSharedPreferenceChangeListener(this);
}

@Override
protected void onPause() {
    super.onPause();
    getPreferenceScreen().getSharedPreferences()
            .unregisterOnSharedPreferenceChangeListener(this);
}

十三、自定义preference


自定义Preference

如果继承Preference,那需要实现onClick事件。然而一般继承DialogPreference,可以简化程序。

首先需要一个构造函数(系统会提示,必须要实现的),在构造函数里面需要调用setDialogLayoutResourse方法来设置布局文件。同时,可以setPositiveText或者setNagativeText

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);
    }
    ...
}

 

另外一点是,保存数据。
每个preference只能保存一种数据类型。所以保存数据时要指定保存的数据类型像:persistInt()、persistBoolen();

什么时候保存数据,也有讲究,必须继承自DialogPreference,那么就应该在dialog关闭的时候保存,进一步说是,点击ok按钮的时候保存。

@Override
protected void onDialogClosed(boolean positiveResult) {
    // When the user selects "OK", persist the new value
    if (positiveResult) {
        persistInt(mNewValue);
    }
}

persistInt()方法将自动保存到SharedPreferences文件中,key是在xml布局中指定的那个。

也就是说省去了edit.put和commit。

 

还有一点是,初始化当前值。

Preference中有个onSetInitialValue()方法。

系统添加Preference到屏幕显示时,系统会调用这个方法。这个方法可判断该settings时候已经有值了,若没有值,那么就设置为默认值(objectdefaultValue)。

这个方法有个参数:boolen restorePersistedValue.这个参数指示,此settings是否已经有值了。

具体细节看下段代码解释:

@Override
protected void onSetInitialValue(boolean restorePersistedValue, Object defaultValue) {
    if (restorePersistedValue) {
        // Restore existing state
        //protected int getPersistedInt (int defaultReturnValue) 
        //defaultReturnValue   The default value to return if either this Preference is not persistent or this Preference is not in the SharedPreferences.
        // You cannot use the defaultValue as the default value, because its value is always null when restorePersistedValue is true.
        mCurrentValue = this.getPersistedInt(DEFAULT_VALUE);
    } else {
        // Set default state from the XML attribute, in other words, Object defaultValue need to set in the xml file by android:defaultValue="";
        mCurrentValue = (Integer) defaultValue;
        persistInt(mCurrentValue);
    }
}

再者,提供默认值。

@Override
protected Object onGetDefaultValue(TypedArray a, int index) {
    return a.getInteger(index, DEFAULT_VALUE);
}

最后,保存和恢复Preference状态。

万一,用户旋屏,activity或者fragment restart()了,那就要考虑到保存和恢复状态的问题了。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值