如何使用 android.support.v7.preference 库创建自定义首选项?

How do I create custom preferences using android.support.v7.preference library?

我希望至少支持 api 10,我希望能够很好地设置我的偏好样式,我希望能够拥有 headers(或显示 PreferenceScreen秒)。 PreferenceActivity 似乎不完全支持 AppCompat 的着色,不适合。所以我正在尝试使用 AppCompatActivityPreferenceFragmentCompat.

public class Prefs extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (savedInstanceState == null)
            getSupportFragmentManager().beginTransaction()
                    .replace(android.R.id.content, new PreferencesFragment())
                    .commit();
    }

    public static class PreferencesFragment extends PreferenceFragmentCompat {
        @Override public void onCreate(final Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            addPreferencesFromResource(R.xml.preferences);
        }

        @Override
        public void onDisplayPreferenceDialog(Preference preference) {
            // the following call results in a dialogue being shown
            super.onDisplayPreferenceDialog(preference);
        }

        @Override public void onNavigateToScreen(PreferenceScreen preferenceScreen) {
            // I can probably use this to go to to a nested preference screen
            // I'm not sure...
        }
    }
}

现在,我想创建一个提供字体选择的自定义首选项。使用 PreferenceActivity,我可以简单地做

import android.preference.DialogPreference;

public class FontPreference extends DialogPreference {

    public FontPreference(Context context, AttributeSet attrs) {super(context, attrs);}

    @Override protected void onPrepareDialogBuilder(Builder builder) {
        super.onPrepareDialogBuilder(builder);
        // do something with builder and make a nice cute dialogue, for example, like this
        builder.setSingleChoiceItems(new FontAdapter(), 0, null);
    }
}

并用xml这样来显示

<my.app.FontPreference android:title="Choose font" android:summary="Unnecessary summary" />

但是现在,android.support.v7.preference.DialogPreference里面没有onPrepareDialogBuilder了。相反,它已移至 PreferenceDialogFragmentCompat。我发现关于如何使用那个东西的信息很少,而且我不确定如何从 xml 到显示它。 v14 首选项片段具有以下代码:

public void onDisplayPreferenceDialog(Preference preference) {
    ...

    final DialogFragment f;
    if (preference instanceof EditTextPreference)
        f = EditTextPreferenceDialogFragment.newInstance(preference.getKey());
    ...
    f.show(getFragmentManager(), DIALOG_FRAGMENT_TAG);
}

我尝试子类化 android.support.v7.preference.DialogPreference 并让 onDisplayPreferenceDialog 使用一段类似的代码来实例化一个虚拟 FontPreferenceFragment 但它失败了,出现以下异常。

java.lang.IllegalStateException: Target fragment must implement TargetFragment interface

在这一点上,我已经陷得太深了,不想再深究了。 Google 对此异常一无所知。无论如何,这种方法似乎过于复杂。那么,使用 android.support.v7.preference 库创建自定义首选项的最佳方法是什么?

当您的 FontPreferenceFragment 没有实现 DialogPreference.TargetFragment 时会导致异常。您需要确保您的片段实现了该接口。

Important note: Currently (v23.0.1 of the v7 library) there are still a lot of theme-issues with the 'PreferenceThemeOverlay'(see this issue). On Lollipop for example, you end up with Holo-styled category headers.

经过一些令人沮丧的时间,我终于成功创建了自定义 v7 首选项。创建自己的 Preference 似乎比您想象的要难。所以一定要花点时间。

起初您可能想知道为什么每个首选项类型都会找到 DialogPreferencePreferenceDialogFragmentCompat。事实证明,第一个是实际偏好,第二个是显示偏好的 DialogFragment。遗憾的是,您需要 subclass both[=其中 44=]。

别担心,您不需要更改任何代码。您只需要重新定位一些方法:

  • 所有首选项编辑方法(如 setTitle()persist*())都可以在 DialogPreference class.
  • 中找到
  • 所有对话框(编辑)方法(onBindDialogView(View)onDialogClosed(boolean))已移至 PreferenceDialogFragmentCompat

您可能希望现有的 class 扩展第一个,我认为这样您就不必进行太多更改。自动完成应该可以帮助您找到缺失的方法。

完成上述步骤后,就可以将这两个 class 绑定在一起了。在您的 xml 文件中,您将引用首选项部分。但是,Android 还不知道当您的自定义首选项需要时它必须膨胀哪个 Fragment。因此,您需要覆盖 onDisplayPreferenceDialog(Preference):

@Override
public void onDisplayPreferenceDialog(Preference preference) {
    DialogFragment fragment;
    if (preference instanceof LocationChooserDialog) {
        fragment = LocationChooserFragmentCompat.newInstance(preference);
        fragment.setTargetFragment(this, 0);
        fragment.show(getFragmentManager(),
                "android.support.v7.preference.PreferenceFragment.DIALOG");
    } else super.onDisplayPreferenceDialog(preference);
}

并且您的 DialogFragment 需要处理 'key':

public static YourPreferenceDialogFragmentCompat newInstance(Preference preference) {
    YourPreferenceDialogFragmentCompat fragment = new YourPreferenceDialogFragmentCompat();
    Bundle bundle = new Bundle(1);
    bundle.putString("key", preference.getKey());
    fragment.setArguments(bundle);
    return fragment;
}

这应该可以解决问题。如果您遇到问题,请尝试查看现有的子 classes 并查看 Android 如何解决它(在 Android Studio 中:键入 class' 名称并按 Ctrl+ b 查看反编译后的class)。希望对你有帮助。

有一个很好的教程和 Github 项目详细解释了如何创建扩展支持首选项库的自定义首选项 class:

重点是:

  • 您将需要自定义 DialogPreferenceListPreference,它控制首选项行的外观和功能。 (它还可以包含对应显示在启动对话框中的布局的引用)。将此 DialogPreference 添加到您的 XML 首选项文件中。

  • 您将需要一个自定义 PreferenceDialogFragmentCompat,它控制单击首选项行时对话框的启动。您可以在 onBindDialogView().

  • 中配置对话框的视图
  • 在扩展 PreferenceFragmentCompat 的首选项屏幕中,覆盖 onDisplayPreferenceDialog() 以启动自定义 PreferenceDialogFragmentCompat.

  • 您只能扩展支持 classes,而不是平台 classes。例如,扩展 androidx.preference.EditTextPreference 而不是 android.preference.EditTextPreference