17

我试图使用 support.v7.preference 为 AppCompat 应用程序实现首选项。我花了几天的时间来处理它,因为 support.v7.preference 与本机偏好有一些显着差异......一旦你知道这并不算太糟糕,但不幸的是那里几乎没有文档。我想我会分享我的发现,这样其他人就不必经历同样的痛苦。


所以......问题:

您如何最好地实现 AppCompat 应用程序的首选项(PreferenceFragment 和 AppCompatAcitivity 不兼容)?

这里有几个相关的问题:

官方文档在这里:

4

2 回答 2

52

解决方案 1:本机PreferenceFragmentAppCompatActivity

在 AndroidStudio 中,选择File > New Project >...> SettingsActivity。此模板使用一种变通方法,对原生进行改造PreferenceFragment以使用AppCompatActivity,类似于support.v4.Fragmentsupport.v7.PreferenceFragmentCompat.

  • 优点:您现在可以在 AppCompat应用程序中使用本机首选项功能。这是使用 AS 模板时的一种快速方法,您可以坚持现有的 Preference 文档和工作流程。
  • 缺点:改造不是很直观或干净。此外,由于通常建议在可用的情况下使用支持库,因此我不确定这种方法是否具有前瞻性。

解决方案2:support.v7.preference.PreferenceFragmentCompat使用AppCompatActivity

  • 优点:最大限度地提高兼容性
  • 缺点:有很多差距需要弥补。此外,这可能不适用于任何现有的偏好扩展库(例如ColorPickerFontPreferences)。

如果您选择不使用解决方案 1(我仍然不确定两者中哪一个更适合未来),使用support.v7.preference.

下面提到了使用解决方案 2 的重要缺点。

依赖项:

dependencies {
    ...
    compile 'com.android.support:appcompat-v7:23.1.1'
    compile 'com.android.support:preference-v7:23.1.1'
    compile 'com.android.support:support-v4:23.1.1'
}

主题: 您需要preferenceTheme在您的styles.xml 中定义a,否则运行您的应用程序将引发异常。

<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light">
    <!-- Customize your theme here. -->
    <item name="preferenceTheme">@style/PreferenceThemeOverlay</item>
</style>

您可能想将其拆分为 7+/14+/21+ 的不同样式。在撰写本文时,很多人抱怨这是错误的。这里有一个非常全面的答案。

行为变化:使用本机首选项非常简单:您需要做的就是定义/维护您的preferences.xmladdPreferencesFromResource(R.xml.preferences)在您的PreferenceFragment. 自定义首选项可以通过子类轻松完成DialogPreference,然后只需在preferences.xml... bam 中引用即可。

不幸的是,support.v7.preference已经Fragment剥离了与处理相关的所有内容,使其失去了很多内置功能。您现在不仅要维护 XML,还必须继承和覆盖很多东西,不幸的是,所有这些东西都没有记录在案。

PreferenceScreens: PreferenceScreens不再由框架管理。PreferenceScreen在您的preference.xml(如文档中所述)中定义 a将显示该条目,但单击它不会执行任何操作。现在由您来处理显示和导航子屏幕。无聊的。

有一种方法(在此处描述),将 a 添加PreferenceFragmentCompat.OnPreferenceStartScreenCallback到您的PreferenceFragmentCompat. 虽然这种方法可以快速实施,但它只是交换了现有偏好片段的内容。缺点是:没有后退导航,你总是“在顶部”,这对用户来说不是很直观。

在另一种方法中(在此处描述),您还必须管理后退堆栈以实现预期的后退导航。这preferenceScreen.getKey()用作每个新创建/显示的片段的根。

这样做时,您可能还会偶然发现PreferenceFragments默认情况下透明并奇怪地叠加在一起。人们倾向于覆盖PreferenceFragmentCompat.onViewCreated()以添加类似

// Set the default white background in the view so as to avoid transparency
view.setBackgroundColor(ContextCompat.getColor(getContext(), R.color.background_material_light));

自定义 DialogPreference:制作自己的偏好也从琐碎变得无聊。DialogPreference现在已经删除了处理实际对话框的任何内容。那一点现在住在PreferenceDialogFragmentCompat. 因此,您必须对两者进行子类化,然后处理创建对话框并自己显示它(在此处解释)。

查看源代码PreferenceFragmentCompat.onDisplayPreferenceDialog()表明它知道如何处理恰好 2 个对话框首选项(EditTextPreference, ListPreference),您必须使用OnPreferenceDisplayDialogCallbacks 自己实现的所有其他内容......一个奇迹,为什么没有处理子类的功能DialogPreference


以下是一些代码,它们实现了大多数这些变通方法并将它们装箱到 lib 模块中:

https://github.com/mstummer/extended-preferences-compat.git

主要意图是:

  • 无需在每个应用程序/项目中扩展Activity和摆弄。现在再次成为唯一需要更改/维护的每个项目文件。PreferenceFragmentpreference.xml
  • 按预期处理和显示PreferenceScreens(子屏幕)。
  • 取消拆分DialogPreference以恢复本机行为。
  • 处理和显示DialogPreference.

不要认为它足够干净以至于开箱即用,但在处理类似问题时它可能会给你一些提示。试一试,如果您有任何建议,请告诉我。

于 2016-01-25T01:49:47.260 回答
0

我对此有一个替代解决方案,我希望得到反馈。

我为我的偏好片段做了一个自定义布局,左上角有一个“返回”按钮。

首先,在“onCreatePreference”中,我存储了根 PreferenceScreen:

root = this.getPreferenceScreen();

然后,我如上所述添加 OnPreferenceStartScreenCallback 并在其他线程中使片段转到子屏幕,但在我的“onPreferenceStartScreen”中,我还将后退按钮设置为可见,如下所示:

    public boolean onPreferenceStartScreen(PreferenceFragmentCompat preferenceFragmentCompat, PreferenceScreen preferenceScreen) {
        preferenceFragmentCompat.setPreferenceScreen(preferenceScreen);
        backButton.setVisibility(View.VISIBLE);
        return true;
}

最后,backButton clickhandler:

    setPreferenceScreen(root);
    back.setVisibility(View.GONE);

这对我来说似乎很好。显然后退堆栈不起作用,但我可以忍受,因为有一个后退按钮。

不完美,但考虑到糟糕的 API,我觉得我很高兴。

如果有人认为这种方法有任何问题,我很想听听。

于 2017-11-02T09:16:15.150 回答