Preference&Android原生设置界面

5,889 阅读6分钟

我平时项目开发必备框架

  1. Android上最强网络请求 Net
  2. Android上最强列表(包含StateLayout) BRV
  3. Android最强缺省页 StateLayout
  4. JSON和长文本日志打印工具 LogCat
  5. 支持异步和全局自定义的吐司工具 Tooltip
  6. 开发调试窗口工具 DebugKit
  7. 一行代码创建透明状态栏 StatusBar

基本上每个应用都有一个设置(首选项)界面, Google其实提供了默认的设置界面实现方式. 介绍下

Preference该类拥有多个直接或间接的子类, 这些子类可以组成不同内容的首选项界面. 和一般界面不同的

关键类:

  • Preference 普通
    • RingtonePreference 铃声
      • CheckBoxPreference 选择按钮
      • SwitchPreference 切换按钮
    • PreferenceGroup(抽象类)
      • PreferenceCategory 分类
      • PreferenceScreen 首选项界面
    • DialogPreference (抽象类)
      • ListPreference 列表
      • EditTextPreference 输入框
      • MultiSelectListPreference 多选
  • PreferenceActivity 首选项界面
  • PreferenceFragment 首选项片段
  • PreferenceViewHolder

首选项

可以通过在res/xml目录下创建一个XML文件来控制显示一个首选项界面内容, 一般命名为preference.xml.

PreferenceActivity

在android3.0以下应使用继承PreferenceActivity方式实现(因为Fragment是3.0之后才有的), 但是如果需要使用多面板模式就必须使用这种方法

public class MainActivity extends PreferenceActivity {

    @Override protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //setContentView(R.layout.activity_main); 不允许使用一般布局
        addPreferencesFromResource(R.xml.preference);
    }
}

PreferenceFragment

在Android3.0或以上应采用Fragment的形式, 相对Activity更加灵活和效率. 使用方法和一般Fragment一样.

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

        addPreferencesFromResource(R.xml.preferences);
    }
    ...
}

属性

以下介绍的是所有Preference标签通用的属性

  • android:key

    等同于SharePreference中的key值, 对于某些不需要存储数据的首选项可以不填写该属性(例如PreferenceCategory和PreferenceScreen)

  • android:title

    提供用户可见的标题

  • android:defaultValue

    在SharePreference中的默认值

  • android:summary

    描述性文字

  • android:icon

    图标

示例

演示所有的标签

<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" android:key="@string/app_name">
    <CheckBoxPreference android:title="CheckBoxPreference"/>
    <SwitchPreference android:title="SwitchPreference"/>
    <PreferenceCategory android:title="PreferenceCategory"/>
    <ListPreference android:title="ListPreference"/>
    <EditTextPreference android:title="EditTextPreference"/>
    <MultiSelectListPreference android:title="MultiSelectListPreference"/>
    <RingtonePreference android:title="RingtonePreference"/>
    <Preference android:title="Preference"/>
</PreferenceScreen>

PreferenceScreen

表示一个首选项界面(首选项布局必须以该标签为根标签)

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

PreferenceCategory

首选项界面中的分类, 分类的颜色默认是主题ColorAccount

该Preference没有分割线, 如果想去除分割线可以考虑自定义该PreferenceCategory

ListPreference

首选项中的列表

列表需要两个必须的选项.如果没有就Crash

  • 列表键(标题)
  • 列表值
<ListPreference
        android:entries="@array/setting_list"
        android:entryValues="@array/setting_list_values"
        android:title="ListPreference"/>

列表对话框还可以修改, 其实只要是DialogPreference的子类都支持这些属性.

这些属性分别对应对话框的

  1. 图标
  2. 布局
  3. 消息(和传统对话框一样如果设置了消息就没用了列表)
  4. 标题

EditTextPreference

输入首选项

void onSetInitialValue (boolean restoreValue, 
                Object defaultValue)

SwitchPreference

开关描述属性, 该属性CheckBoxPreference同样支持

    <SwitchPreference
        android:summaryOff="关闭"
        android:summaryOn="打开"
        android:title="SwitchPreference"/>

监听器

点击事件

getPreferenceScreen().setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
  @Override public boolean onPreferenceClick(Preference preference) {
    return false;
  }
});

属性

android:title // 标题

android:summary // 副标题

android:enabled // 是否可用

android:selectable // 是否可选, 如果处于不可选状态将没有分割线, 也不可用状态

android:icon // 图标

android:key // sharepreference key

android:defaultValue // 默认值

android:order // 排列顺序

android:shouldDisableView // 如果处于不可用状态, 是否"灰色"

android:persistent // 是否持久化(即存储到SharePreference)

android:fragment // 无效果

自定义

自定义视图

void setLayoutResource (int layoutResId)

void setWidgetLayoutResource (int widgetLayoutResId)

void setDialogLayoutResource (int dialogLayoutResId)

前面提到过onCreate里面不能setContent否则会报空指针, 其实只是因为找不到对应的id

@Override protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);

  setContentView(R.layout.activity_setting);
  addPreferencesFromResource(R.xml.demo);
}

layout/activity_setting.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >

    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:navigationIcon="@drawable/ic_action_back"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@color/colorPrimary"
        app:popupTheme="@style/Theme.AppCompat.Light"
        app:theme="@style/Theme.AppCompat" />

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:padding="15dp" >

        <ListView
            android:id="@android:id/list"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
      
    </FrameLayout>

</LinearLayout>

重写

介绍下官方重写示例: 自定义DialogPreference

自定义 DialogPreference 可以使用下面的构造函数来声明布局并为默认的肯定和否定对话框按钮指定文本:

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

或者你通过重写

    @Override protected View onCreateDialogView() {

        View view = View.inflate(getContext(), R.layout.dialog_preference, null);

        return view;
    }

保存设置的值

如果设置的值为整型数或是用于保存布尔值的 persistBoolean(),则可通过调用 Preference 类的一个 persist*() 方法(如 persistInt())随时保存该值。

**注:**每个 Preference 均只能保存一种数据类型,因此您必须使用适合自定义 Preference 所用数据类型的 persist*() 方法。

至于何时选择保留设置,则可能取决于要扩展的 Preference 类。如果扩展 DialogPreference,则只能在对话框因肯定结果(用户选择“确定”按钮)而关闭时保留该值。

DialogPreference 关闭时,系统会调用 onDialogClosed() 方法。该方法包括一个布尔参数,用于指定用户结果是否为“肯定”;如果值为 true,则表示用户选择的是肯定按钮且您应该保存新值。 例如:

@Override
protected void onDialogClosed(boolean positiveResult) {
    // 当点击确定时保存数据
    if (positiveResult) {
        persistInt(mNewValue);
    }
}

关于默认值

首先需要设置一个默认值

重写onGetDefaultValue查找该默认值并返回

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

重写onSetInitialValue判断是进行恢复数据还是初始化默认值.

如果没有实现上面的方法defaultValue是为null. 并且restorePersistedValue为false时并不会回调该方法

    /**
     * @param restorePersistedValue 是否有可恢复数据
     * @param defaultValue 默认值
     */
@Override
protected void onSetInitialValue(boolean restorePersistedValue, Object defaultValue) {
    if (restorePersistedValue) {
        // 恢复数据
        mCurrentValue = this.getPersistedInt(DEFAULT_VALUE);
    } else {
        // 初始化默认值
        mCurrentValue = (Integer) defaultValue;
        persistInt(mCurrentValue);
    }
}

属性

依赖

通过填写其他Preference的title可以依赖他. 如果是相同的Preference则会同步属性, 如果不同则会同步其是否可用状态.

android:dependency="notifications_new_message"

自定义视图

前面介绍过代码自定义, 这里介绍下XML Attributes

android:layout // 自定义显示内容

android:widgetLayout // 同样是自定义显示内容

区分:

layout

  		<img src="https://ws1.sinaimg.cn/large/006tNc79gy1fi3vx983pdj30u01hc3zw.jpg" width="250"/> 

widgetLayout

可以看出layout属于完全自定义, 而widgetLayou仍然被约束了边距. 分割线两者都有

更多

默认值

前面提过Preference标签都有一个defaultValue的属性, 该属性可以设置一个首选项的默认值.

除了在XML中设置还需要在Application或者主Activity(仅推荐非强制)中执行重置默认值操作

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

参数:

  • 上下文Context

  • 首选项XML

  • 首选项XML中有一个键为KEY_HAS_SET_DEFAULT_VALUES的值表示是否设置过默认值.

    这第三个参数如果为true则不论是否设置过默认值都进行初始化重置, 如果为false则要判断是否设置过默认值.

子屏幕显示

经常可以看到应用的详细设置不在首选项界面的主界面而是需要继续打开一个子屏幕进行更详细的设置

标头: 通过将preference-headers设为根标签, 用header来控制多个Fragment作为子屏幕

<preference-headers xmlns:android="http://schemas.android.com/apk/res/android">
    <header android:fragment="com.tianyachuanmei.preperence.AboutFragment" android:title="关于" />
    <header android:fragment="com.tianyachuanmei.preperence.MoreFragment" android:title="更多"/>
</preference-headers>

该方法不能通过以下方法加载, 同样该方法也被废弃.

addPreferencesFromResource(R.xml.pref_headers);

通过重写PreferenceActivity的方法来加载XML, 如果不是headers不需要重写该方法()

    @Override @TargetApi(Build.VERSION_CODES.HONEYCOMB) public void onBuildHeaders(List<Header> target) {
        loadHeadersFromResource(R.xml.pref_general, target);
    }

Tip: loadHeadersFromResource不能加载非HeadersXML文件, 否则Crash.

点击事件**: 通过给Preference指定跳转的Activity来实现子屏幕

<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
    <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>
    <Preference
        android:title="@string/prefs_category_two"
        android:summary="@string/prefs_summ_category_two" >
        <intent
            android:targetPackage="com.example.prefs"
            an droid:targetClass="com.example.prefs.SettingsActivity"
            android:action="com.example.prefs.PREFS_TWO" />
    </Preference>
</PreferenceScreen>

多面板

多面板需要使用PreferenceActivity来实现, 通过重写onIsMultiPane返回true来启用多面板模式

    @Override public boolean onIsMultiPane() {
      // 以下是判断当前设备是否是大屏幕
        return (getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_XLARGE;
    }

意图(Intent)

<PreferenceScreen
                  android:summary="summary_intent_preference"
                  android:title="title_intent_preference" >

  <intent
          android:action="android.intent.action.VIEW"
          android:data="http://www.android.com" />
</PreferenceScreen>

您可以使用以下属性创建隐式和显式 Intent:

  • android:action

    要分配的操作(按照 setAction() 方法)。

  • android:data

    要分配的数据(按照 setData() 方法)。

  • android:mimeType

    要分配的 MIME 类型(按照 setType() 方法)。

  • android:targetClass

    组件名称的类部分(按照 setComponent() 方法)。

  • android:targetPackage

    组件名称的软件包部分(按照 setComponent() 方法)。

<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
    <Preference
        android:title="prefs_category_one"
        android:summary="prefs_summary_category_one">
        <!-- 这里android:targetPackage是应用程序的Context,而android:targetClass的路径在子包settings下 -->
        <!-- 如果把 android:targetPackage="com.demo.artshell.uidemo.settings" 运行时找不到Activity -->
        <intent
            android:action="prefs_category_action_ONE"
            android:targetPackage="com.demo.artshell.uidemo"
            android:targetClass="com.demo.artshell.uidemo.settings.SupportOldVersionAndReusedActivityOrFragment$ReusedActivity">
            <!-- 官网没有说明,但确实可以通过<extra>传附加信息 getIntent().getStringExtra("reused_key") -->
            <extra
                android:name="reused_key"
                android:value="reused_fragment_two"/>
        </intent>
    </Preference>

    <Preference
        android:title="prefs_category_two"
        android:summary="prefs_summary_category_two">
        <intent
            android:action="prefs_category_action_TWO"
            android:targetPackage="com.demo.artshell.uidemo"
            android:targetClass="com.demo.artshell.uidemo.settings.SupportOldVersionAndReusedActivityOrFragment$ReusedActivity"/>
    </Preference>
</PreferenceScreen>