如何使用新的 Android-X API 从当前的 PreferenceFragment 打开一个新的 PreferenceFragment?

and*_*per 8 android android-preferences android-fragments preferencefragment androidx

背景

在以前版本的支持库中,我们可以使用标题来拥有一个设置的主菜单屏幕,每个屏幕都会打开一个新的设置屏幕(片段)。

问题

现在标题消失了(如这里所写)一段时间,我认为在 android-x 上情况变得更糟:

你会注意到这里没有的一件事是首选项标题,你是完全正确的。然而,这并不意味着单个偏好列表需要跨越 10 英寸平板电脑屏幕。相反,您的 Activity 可以实现 OnPreferenceStartFragmentCallback ( link ) 以使用 app:fragment 属性或 OnPreferenceStartScreenCallback ( link ) 处理首选项以处理 PreferenceScreen 首选项。这允许您在一个窗格中构造“标题”样式 PreferenceFragmentCompat 并使用这些回调替换第二个窗格,而无需在两种不同类型的 XML 文件中工作。

问题是,我没有在新的 android-x API 上使用这些。

每个片段都有自己的首选项 XML 树(使用setPreferencesFromResourceonCreatePreferences),但我提出的每个解决方案要么什么都不做,要么崩溃。

以直观的方式来说,这就是我想要实现的目标:

在此处输入图片说明

由于有多个子设置屏幕,将所有子设置屏幕的所有首选项都放在主设置屏幕的一个 XML 文件中会非常麻烦。

我试过的

我唯一成功的是使用 PreferenceScreen 来保存应该显示的子屏幕的首选项。

这是此类事情的工作代码(可在此处获得项目):

首选项.xml

<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" android:title="Demo">

    <PreferenceScreen
        android:key="screen_preference" android:summary="Shows another screen of preferences"
        android:title="Screen preferenc">

        <CheckBoxPreference
            android:key="next_screen_checkbox_preference"
            android:summary="Preference that is on the next screen but same hierarchy"
            android:title="Toggle preference"/>

    </PreferenceScreen>

</PreferenceScreen>
Run Code Online (Sandbox Code Playgroud)

主活动.kt

class MainActivity : AppCompatActivity(), PreferenceFragmentCompat.OnPreferenceStartScreenCallback {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        supportActionBar!!.setDisplayHomeAsUpEnabled(true)
        if (savedInstanceState == null)
            supportFragmentManager.beginTransaction().replace(android.R.id.content, PrefsFragment()).commit()
    }

    override fun onPreferenceStartScreen(caller: PreferenceFragmentCompat, pref: PreferenceScreen): Boolean {
        val f = PrefsFragment()
        val args = Bundle(1)
        args.putString(PreferenceFragmentCompat.ARG_PREFERENCE_ROOT, pref.key)
        f.arguments = args
        supportFragmentManager.beginTransaction().replace(android.R.id.content, f).addToBackStack(null).commit()
        return true
    }

    class PrefsFragment : PreferenceFragmentCompat() {
        override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
            setPreferencesFromResource(R.xml.preferences, rootKey)
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

但是,正如我所写的,这不是我想要做的。我想要多个扩展 PreferenceFragmentCompat 的类,每个类都有自己的 XML 文件,这些文件将从主文件中打开。

以下是我尝试过(但失败了)的事情:

  1. 为 , 设置一个“android:fragment”PreferenceScreen以指向新的片段类,类似于标题。这根本没有做任何事情。

  2. 使用普通的 Preference 并为其设置单击侦听器,这将执行原始代码中显示的片段事务。这导致了类似“具有键 screen_preference 的 Preference object is not a PreferenceScreen” 之类的崩溃。

  3. 试图避免使用 ARG_PREFERENCE_ROOT ,但与 #2 上的崩溃相同。

  4. 正如这里所建议的,我试图返回this函数getCallbackFragment,但这根本没有帮助。

问题

是否可以让主要设置片段只让用户导航到其他片段,而没有任何其他属于它们的首选项(内部preferences.xml)?

如何?

Twi*_*ter 6

仅供参考,如果您使用导航抽屉+ androidx.appcompat,您可以:

1) 将每个 PreferenceScreen 子项拆分为与preference.xml 文件一样多的文件:即“Pref_general.xml”将是主要首选项,“pref_ServerSettings.xml”包含具有服务器设置的 PreferenceScreen 子项。2)为每个preference.xml创建一个PreferenceFragmentCompat:

“PrefFragment常规”

在您的 PrefFragmentGeneral.xml 文件中,为任何子 xml 添加一个 Preference 而不是像下面这样的 PreferenceScreen:

<Preference
    android:key="pref_serverPref"
    android:summary="@string/settings_serverPrefSum"
    android:title="@string/settings_serverPrefTitle"
    />
Run Code Online (Sandbox Code Playgroud)

“首选片段服务器”

2) 确保覆盖“onCreatePreferences”以从您想要的 XML 文件中设置首选项:

public class PrefFragmentGeneral extends PreferenceFragmentCompat {
    @Override
    public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
        setPreferencesFromResource(R.xml.Pref_general, rootKey);
        //find your preference(s) using the same key
        Preference serverPref=findPreference("pref_serverPref");
        if(serverPref!=null){
            //Assign the click listener to navigate to the fragment using the navigation controller
            serverPref.setOnPreferenceClickListener(preference -> {
                NavController navController = Navigation.findNavController(getActivity(), R.id.nav_host_fragment);
                navController.navigate(R.id.nav_PrefFragmentServer);
                return true;
            });
        }
    }
//and the PrefFragmentServer 
public class PrefFragmentServer extends PreferenceFragmentCompat {
    @Override
    public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
        setPreferencesFromResource(R.xml.pref_ServerSettings,rootKey);
     }
}
Run Code Online (Sandbox Code Playgroud)

3)在导航抽屉中注册所有片段:

抽屉式导航

现在享受吧!

优点:当您返回时,您将返回到“常规”首选项,就像回到 PreferenceActivity 子项一样!并且您不会收到异常告诉您该片段不是 FragmentManager 的一部分。


小智 3

您在 1) 中尝试的方法是正确的方法 - 但您不应该<PreferenceScreen>为此使用标签。

您的 XML 资源应该如下所示:

<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">

    <Preference
        app:key="screen_preference" 
        app:summary="Shows another screen of preferences"
        app:title="Screen preference"
        app:fragment="com.example.user.myapplication.MainActivity$PrefsFragment2"/>

</PreferenceScreen>
Run Code Online (Sandbox Code Playgroud)

另外,如果您使用的 Preference 版本早于androidx.preference:preference:1.1.0-alpha01,则需要实现 onPreferenceStartFragment 来处理片段事务。(在 1.1.0 alpha01 中,此方法有默认实现,但仍然鼓励您使用自己的实现来自定义任何动画/过渡)

这应该看起来像:

override fun onPreferenceStartFragment(
        caller: PreferenceFragmentCompat,
        pref: Preference
): Boolean {
    // Instantiate the new Fragment
    val args = pref.extras
    val fragment = supportFragmentManager.fragmentFactory.instantiate(
            classLoader,
            pref.fragment,
            args
    ).apply {
        arguments = args
        setTargetFragment(caller, 0)
    }
    // Replace the existing Fragment with the new Fragment
    supportFragmentManager.beginTransaction()
            .replace(R.id.settings, fragment)
            .addToBackStack(null)
            .commit()
    return true
}
Run Code Online (Sandbox Code Playgroud)

有关更多信息,您可以查看设置指南和AndroidX 首选项示例


编辑:更新后第一个解决方案的示例,可在此处获取。

以下是它的工作原理(此处提供示例):

MainActivity.kt

class MainActivity : AppCompatActivity(), PreferenceFragmentCompat.OnPreferenceStartFragmentCallback {
    override fun onPreferenceStartFragment(caller: PreferenceFragmentCompat, pref: Preference): Boolean {
        //Note: this whole function won't be needed when using new version of fragment dependency (1.1.0 and above)
        val fragment = Fragment.instantiate(this, pref.fragment, pref.extras)
        fragment.setTargetFragment(caller, 0)
        supportFragmentManager.beginTransaction().replace(android.R.id.content, fragment).addToBackStack(null).commit()
        return true
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        supportActionBar!!.setDisplayHomeAsUpEnabled(true)
        if (savedInstanceState == null)
            supportFragmentManager.beginTransaction().replace(android.R.id.content, PrefsFragment()).commit()
    }

    class PrefsFragment : PreferenceFragmentCompat() {
        override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
            setPreferencesFromResource(R.xml.preferences, rootKey)
        }
    }

    class PrefsFragment2 : PreferenceFragmentCompat() {
        override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
            setPreferencesFromResource(R.xml.preferences2, null)
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

首选项.xml

  <PreferenceScreen xmlns:app="http://schemas.android.com/apk/res-auto">

    <Preference
      app:fragment="com.example.user.myapplication.MainActivity$PrefsFragment2" app:key="screen_preference" app:summary="Shows another screen of preferences"
      app:title="Screen preference"/>

  </PreferenceScreen>
Run Code Online (Sandbox Code Playgroud)

偏好2.xml

<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" android:title="Demo">

  <PreferenceCategory android:title="Category">
    <CheckBoxPreference
      android:key="next_screen_checkbox_preference" android:summary="AAAA" android:title="Toggle preference"/>
  </PreferenceCategory>

</PreferenceScreen>
Run Code Online (Sandbox Code Playgroud)

梯度依赖:

implementation 'androidx.appcompat:appcompat:1.0.2'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.preference:preference:1.0.0'
Run Code Online (Sandbox Code Playgroud)