我该如何使用android.support.v7.preference库创建自定义偏好设置?

24

我希望至少支持API 10,我希望能够漂亮地自定义偏好设置,我希望能够拥有标题(或显示PreferenceScreen)。看起来PreferenceActivityAppCompat的着色方面不被完全支持,不能满足我的需要。因此,我正在尝试使用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 preference fragment有以下代码:

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

我已经深陷其中,不想再深挖了。谷歌对这个异常一无所知。总之,这种方法似乎过于复杂。那么,使用android.support.v7.preference库创建自定义首选项的最佳方法是什么?

4个回答

40

重要提示:目前(v7库的v23.0.1版本),使用“PreferenceThemeOverlay”仍存在许多主题问题(请参见此问题)。例如,在Lollipop上,您最终将得到Holo风格的类别标题。

经过一些令人沮丧的小时,我终于成功地创建了一个自定义的v7 Preference。创建自己的Preference似乎比你想象中需要更难。所以一定要花些时间。

起初,你可能会想知道为什么每种偏好类型都有一个DialogPreference和一个PreferenceDialogFragmentCompat。事实证明,第一个是实际的偏好设置,第二个是偏好设置应显示的DialogFragment。遗憾的是,你需要同时对它们进行子类化。

不要担心,你不需要改变任何代码。你只需要重新安排一些方法:

  • 所有偏好设置方法(如setTitle()persist*())可以在DialogPreference类中找到。
  • 所有对话框(-编辑)方法(onBindDialogView(View)&onDialogClosed(boolean))已经移动到PreferenceDialogFragmentCompat中。

你可能希望让你的现有类扩展第一个类,这样你就不需要改变太多了。自动完成应该会帮助你找到缺失的方法。

当你完成上述步骤后,就可以将这两个类绑定在一起了。在你的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;
}

那应该可以解决问题了。如果你遇到问题,请尝试查看现有的子类,并看看Android是如何解决它的(在Android Studio中:键入类名并按Ctrl + b以查看反编译的类)。希望能帮到你。


6
几乎是完美的解决方案!我花了一些时间才意识到在偏好设置片段(继承自PreferenceFragmentCompat)中需要重写onDisplayPreferenceDialog方法。非常感谢! - mike.adc
2
对于任何阅读此内容的人,只是一个提示:如果您在PreferenceDialogFragmentCompat.java:57/58中遇到ClassCastException,请确保您的bundle.putString("key", preference.getKey());行中的“key”与上述相同。否则,getArguments().getString(ARG_KEY)将会出现ClassCastException错误。 - MCLLC
请问您是否有完整的示例可以发布?我已经成功实现了新的支持首选项,但是DialogPreferences非常棘手。谷歌没有提供任何关于它的文档。 - Yoann Hercouet
1
这是一个明智的答案。 但是,当我尝试实现它时,我得到了ClassCastException:'java.lang.ClassCastException:android.support.v7.preference.PreferenceScreen不能转换为android.support.v7.preference.DialogPreference' 异常在PreferenceDialogFragmentCompat的super.onCreate调用中抛出。 - Madeyedexter
@MCLLC 谢谢,你也试着把“key”改成自己喜欢的名字了吗?xD - user25
现在setTargetFragment被弃用了,那么它的替代方案是什么? - brassmookie

3

有一份很好的教程和Github项目详细讲解了如何制作一个扩展Support Preference库的自定义偏好类:

关键点是:

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

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

  • 在扩展PreferenceFragmentCompat的首选项屏幕中,覆盖onDisplayPreferenceDialog()以启动自定义的PreferenceDialogFragmentCompat

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


0

0

它确实实现了DialogPreference.TargetFragment。但即使实现了那个也只是一个解决方法。正确的方法肯定要更简单。 - squirrel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接