Android 中设置界面的思考

引言

Android™ 中大部分应用都需要一个 系统设置 界面,有很多开发者(或者开发组)都喜欢自己做一个 Activity 或者 Fragment 作为设置界面。但是谷歌本来就为 Android™ 的设置界面提供了一个解决方案,那就是 PreferenceFragment(PreferenceActivity 已经不推荐使用)。

遗憾的是,由于 Android™ 设备的碎片化,同样的代码,在不同的 API 中会有相当大的界面区别。

原生库

如下的 PreferenceScreenXML 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceCategory android:title="Alert Settings">
<CheckBoxPreference
android:key="preference_key_remind_the_same_day11"
android:summary="Alert in the birthday"
android:title="Alert first" />
<SwitchPreference
android:key="preference_key_remind_the_same_day31"
android:summary="Alert in three day before birthday"
android:title="Alert second" />
<EditTextPreference
android:key="preference_key_remind_the_same_daywwwd21"
android:summary="Alert in a week before the birthday"
android:title="Alert third" />
<SwitchPreference
android:key="preference_key_remind_the_same_day341"
android:summary="Alert in two weeks before the birthday"
android:title="Alert fourth" />
<ListPreference
android:entries="@array/alert_time_entry"
android:entryValues="@array/alert_time_value"
android:key="preference_key_alert_timess1"
android:summary="Select alert time"
android:title="Alert time"/>
<MultiSelectListPreference
android:entries="@array/alert_time_entry"
android:entryValues="@array/alert_time_value"
android:key="preference_key_alert_timeddwexss1"
android:summary="Select alert time"
android:title="Alert time" />
</PreferenceCategory>

</PreferenceScreen>

在不同设备的效果如图所示
original

左边的是 API 16 Android™ 4.1.1,右边的是 API 22 Android™ 5.1.1

也正是因为效果差别如此之大,所以使用系统的 Preference 并不是一个很好的选择。

解决方案

最近实习的公司也基本敲定了,也有空搞点有趣的东西,结合 Android-MaterialPreferencematerial 这两个第三方的 Material Design 兼容库,组合出来一个便于使用的 MDPreference

引入

在项目的主 build.gradle 中添加 jitpack 的仓库

1
2
3
4
5
6
allprojects {
repositories {
...
maven { url "https://jitpack.io" }
}
}

添加依赖

1
2
3
4
5
6
7
8
9
dependencies {
compile 'com.github.XhinLiang.MDPreference:mdpreference:0.3.1@aar'
// You should add this because the 'mdpreference' depend on this
compile 'com.github.XhinLiang.MDPreference:material:0.3.1@aar'
// You should add these because the 'material' depend on them
compile 'com.android.support:appcompat-v7:23.1.1'
compile 'com.android.support:cardview-v7:23.1.1'
compile 'com.android.support:recyclerview-v7:23.1.1'
}

这里还得说明一下

1
compile 'com.github.XhinLiang.MDPreference:material:0.3.1@aar'

这个依赖也必须添加,因为里面很多控件都是使用了 material 这个库,为什么不直接添加这个库本身的依赖,是因为这个库里面的接口有点乱,添加的是我重构删减过的版本。

效果

这里展示了在 API 16 设备中的效果
sample

对话框变色应该是截图软件引起的

使用

事实上这个库的使用跟 Android™ 官方的没什么区别,有区别的地方在 ListPreferenceMultiSelectListPreference。由于官方的 entries entryValues 这两个资源ID是私有的,所以只好自己另外定义资源ID。

对于 ListPreference,具体是这个样子的

1
2
3
4
5
6
7
<io.github.xhinliang.mdpreference.ListPreference
android:key="preference_key_alert_timess"
android:summary="Select alert time"
android:title="Alert time"
app:entry_arr="@array/alert_time_entry"
app:format_str="%s"
app:value_arr="@array/alert_time_value" />

其中 entry_arr 是展示给用户的那个数组,

format_str 就是一个 summary 的输出格式,它里面的 %s 在输出时会被替换为选择的那个 entry。例如

1
app:format_str="The Alert Time is %s"

在用户选择之前,Summary 为 “Select alert time”,而在用户选择了 “12:00” 之后,Summary 为 “The Alert Time is 12:00”。

value_arr 则是我们获取到的值的数组。也就是说,在用户选择了一个 entry 之后,我们通过 SharePreference 获取到的那个 String 的值,这其实跟官方的思路是一模一样的。

示例代码如下

1
2
SharedPreferences preferences = getSharedPreferences(getString(R.string.app_name), MODE_PRIVATE);
preferences.getString("preference_key_alert_timess", "");

对于 MultiSelectListPreference,具体是这个样子的

1
2
3
4
5
<io.github.xhinliang.mdpreference.MultiSelectListPreference
android:key="preference_key_alert_timeddwexss"
android:summary="Select alert time"
android:title="Alert time"
app:entry_arr="@array/alert_time_entry" />

注意这里跟官方的差别有点大,其中 entry_arr 是展示给用户的那个数组,而 value_arr 这个参数被我砍掉了。因为我觉得像官方一样把整个 value String 集合保存在 SharePreference 里是十分不优雅的。

我选择了另一种方式,将用户选择的 entry 的下标用 int 保存起来(这也就意味着,多选框的选项不能超过32个)。

也就是说,在用户选择了一个 entry 之后,我们只能通 SharePreference 获取到的一个 int ,这个 int 的每一个二进制位的 01 就代表了对应下标的选项的选择与否,为了减少二进制操作的麻烦,这里还提供了一个静态方法 MultiSelectListPreference.getSetByBit 可以将获得的 int 转换为对应的 Set ,这个 Set 里面包含了被选中的选项的下标。

示例代码如下

1
2
int bit = preferences.getInt("key_multi",0);
Set<Integer> set = MultiSelectListPreference.getSetByBit(bit);

思考

为什么在官方的实现里要使用 entry + value 这样的方案呢,事实上使用下标来代表是否选择,不是更加优雅吗?(事实上我有想把 ListPreference 里的方案也改掉的冲动)

这只是我个人的看法,如有错误欢迎指出。

Share