【问题标题】:PreferenceFragmentCompat custom layoutPreferenceFragmentCompat 自定义布局
【发布时间】:2016-06-28 06:28:32
【问题描述】:

我的 PreferenceFragmentCompat 需要一个自定义布局。在PreferenceFragmentCompat 的文档中,您似乎可以在 onCreateView() 中膨胀并返回一个视图。

但是 NPE 结果:-

Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'void android.support.v7.widget.RecyclerView.setAdapter(android.support.v7.widget.RecyclerView$Adapter)' on a null object reference
                                                                       at android.support.v7.preference.PreferenceFragmentCompat.bindPreferences(PreferenceFragmentCompat.java:511)
                                                                       at android.support.v7.preference.PreferenceFragmentCompat.onActivityCreated(PreferenceFragmentCompat.java:316)
                                                                       at com.cls.example.MyPrefFrag.onActivityCreated(MyPrefFrag.java:42) 

在我检查了 PreferenceFragmentCompat:onCreateView 的来源后,我发现了以下代码:-

 RecyclerView listView = this.onCreateRecyclerView(themedInflater, listContainer, savedInstanceState);
 if(listView == null) {
    throw new RuntimeException("Could not create RecyclerView");
 } else {
    this.mList = listView;   //problem
    ...
    return view;
 }

因此,如果您覆盖 onCreateView() 并返回自定义布局,则不会调用 onCreateRecyclerView() 并且不会设置 RecyclerView 私有字段 mList。所以 setAdapter() 上的 NPE 结果。

我是否应该假设 PreferenceFragmentCompat 无法使用自定义布局?

【问题讨论】:

  • PreferenceFragment(Compat) 旨在为设置提供标准 UI。请问,为什么要使用自定义布局?无论如何,如果您真的需要,您可以使用带有自定义首选项处理的自定义布局。
  • 我在自定义布局的开头得到了一个自定义视图,该视图会随着每个首选项的变化而变化。使用本机 PreferenceFragment 很容易。您只需在自定义布局中嵌入一个 id=android:id/list 的列表视图。 PreferencefragmentCompat 似乎没有类似的功能。
  • 您可以通过覆盖 onCreateRecyclerView(...) 来执行相同操作,这应该从您的布局中返回 RecyclerView。
  • 这不符合我的要求。回收站视图用于首选项。我需要自定义布局。
  • 那么你必须以自己的方式实现它。 PreferenceFragmentCompat 用于标准布局,它在幕后没有什么特别的作用,因此使用经典的 SharedPreferences 使用自定义业务逻辑实现自定义设置 UI 应该不是问题。

标签: android android-support-library preferencefragment


【解决方案1】:

我有同样的问题,但是我的要求有点不同,我只需要在设置列表上方显示一个布局,所以我这样做了

<NestedScrollView>
  <ConstrainLayout>
    <CustomView>
    <SettingsFragmentHolder/>
  </ConstraintLayout>
</NestedScrollView>

然后在代码中我只是这样做

FragmentManager.beginTransaction().replace(holder, PreferencesFragment()).commit()

需要注意的一点是,preferences 片段有自己的滚动视图或列表,这将导致 NestedScrollView 滚动到 PreferencesFragment 布局的开头,以修复将其添加到 PreferencesFragment 的父布局,在这种情况下它是约束布局

android:descendantFocusability="beforeDescendants"
android:focusableInTouchMode="true"

这将停止默认的滚动行为

【讨论】:

  • 对我来说 android:descendantFocusability="blocksDescendants" 成功了。
【解决方案2】:

您可以在主题中指定自定义布局。

例如:

styles.xml

<style name="YourTheme" parent="Theme.AppCompat">
    <!-- ... -->
    <item name="preferenceTheme">@style/YourTheme.PreferenceThemeOverlay</item>
</style>

<style name="YourTheme.PreferenceThemeOverlay" parent="@style/PreferenceThemeOverlay">
    <item name="android:layout">@layout/fragment_your_preferences</item>
</style>

fragment_your_preferences.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.design.widget.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <android.support.v7.widget.Toolbar
            android:id="@+id/custom_toolbar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />

    </android.support.design.widget.AppBarLayout>

    <!-- Required ViewGroup for PreferenceFragmentCompat -->
    <FrameLayout
        android:id="@android:id/list_container"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

    </FrameLayout>

</LinearLayout>

然后在片段类的onViewCreated() 中,您可以开始使用视图:

@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState)
{
    super.onViewCreated(view, savedInstanceState);

    Toolbar toolbar = (Toolbar)view.findViewById(R.id.custom_toolbar);

    if (toolbar != null)
    {
        getMainActivity().setSupportActionBar(toolbar);
    }
}

【讨论】:

  • 我确实尝试了在主题中仅使用 textview 和 list_container 视图组的自定义布局。它不起作用。偏好没有得到填充。此外,在滚动多个文本视图时滚动。为了进一步排除故障,我尝试使自定义布局与 preferencefragmentcompat 的库布局完全相同并尝试过。同样,偏好没有得到填充。这段代码对你有用吗?
  • 您是否在 onCreate() 中添加了首选项? @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); addPreferencesFromResource(R.xml.preferences); } 你在用PreferenceFragmentCompat吗?
  • 不,我在 onCreatePreferences() 中添加了它。我不知道我为什么那样做。我会将它添加到 onCreate() 并还原。我已经放弃并回到原生片段。
  • 不客气!如果将来您遇到类似的问题,您可以在 Android Studio 中打开类似PreferenceFragmentCompat.class 之类的问题的类。当您打开类文件时,它应该向您显示(反编译的)可读的 Java 代码。如果您没有看到 Java 代码,请在 Android Studio 设置中安装 Java Bytecode Decompiler 插件。之后,您可以开始挖掘问题所在:在这种情况下,您可以看到 PreferenceFragmentCompat.onCreateView 具有可样式化的布局并查找 R.id.listContainer
  • 我试过和你告诉我的一样。但它对我不起作用。