How to use PreferenceFragmentCompat in the preference header

大城市里の小女人 提交于 2019-12-23 19:04:21

问题


I'm trying to learn ways to build preference pages in the Xamarin Android application. I found a lot of examples with PreferenceFragment but it was marked as deprecated and it is difficult for me to rewrite them on the current stage.

I've created activity to represent headers. I added IntentFilter so I can access this activity from apps list in the settings menu. Also it has internal class to group some preferences together:

namespace droid.examples.Preferences
{
    [Activity(Label = "Settings activity", Theme = "@style/AppTheme", Name = "droid.examples.Preferences.SettingsActivity")]
    [IntentFilter(new string[] { "android.intent.action.APPLICATION_PREFERENCES" })]
    public class SettingsActivity : PreferenceActivity
    {
        public override void OnBuildHeaders(IList<Header> target)
        {
            base.OnBuildHeaders(target);
            LoadHeadersFromResource(Resource.Xml.preference_headers, target);
        }

        public class SettingsFragment : PreferenceFragmentCompat
        {
            public override void OnCreatePreferences(Bundle savedInstanceState, string rootKey)
            {
                // Load the Preferences from the XML file
                SetPreferencesFromResource(Resource.Xml.app_preferences, rootKey);
            }
        }
    }
}

My app_preferences.xml which I can't open by selecting "Prefs 1" header from preference_headers.xml:

<?xml version="1.0" encoding="utf-8" ?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
  <PreferenceCategory android:title="Category">
    <CheckBoxPreference
            android:key="checkbox_preference"
            android:title="Developer mode"
            android:summary="Allow user to see detailed messages" />
  </PreferenceCategory>
</PreferenceScreen>

I have preference_headers.xml. It opens when I click on gear wheel near application name. It looks like this:

<?xml version="1.0" encoding="utf-8" ?>
<preference-headers xmlns:android="http://schemas.android.com/apk/res/android">
  <header android:fragment="droid.examples.Preferences.SettingsActivity.SettingsFragment"
          android:title="Prefs 1"
          android:summary="An example of some preferences." />
</preference-headers>

My package name: droid.examples

I think that one problem related to the android:fragment attribute value. What is the rules to build that value?

I suppose that it must start from 'package name'. Should it contain namespace between class name and package name?

What does $ mean in the attribute value? Is it used to mark internal class? I saw in the several places next code:

android:fragment="com.example.android.apis.preference.PreferenceWithHeaders$Prefs1Fragment"

I hope you can help me find where I made a mistakes.

Source code from GitHub


回答1:


I spend a lot of time to investigate that issue and I want to make a summary.

We have to override IsValidFragment method in the SettingsActivity:

protected override bool IsValidFragment(string fragmentName)
{
    return fragmentName == "droid.examples.preferences.SettingsActivity.SettingsFragment";
}

My SettingsActivity extends PreferenceActivity. Thanks to @Jeremy advice about implementation of IOnPreferenceStartFragmentCallback I find out that base class already extends it.

public abstract class PreferenceActivity ...
{
    ...
    public virtual bool OnPreferenceStartFragment(PreferenceFragment caller, Preference pref);
    ...
}

So, I probably need to use PreferenceFragment instead of PreferenceFragmentCompat to make code consistent:

public class SettingsFragment : PreferenceFragment
{
    public override void OnCreate(Bundle savedInstanceState)
    {
        base.OnCreate(savedInstanceState);
        AddPreferencesFromResource(Resource.Xml.app_preferences_for_header);
    }
}

Also we have to add Register attribute to our fragment:

[Register("droid.examples.preferences.SettingsActivity.SettingsFragment")]
public class SettingsFragment : PreferenceFragment
{
}

Finally I updated preference_headers.xml

<?xml version="1.0" encoding="utf-8" ?>
<preference-headers xmlns:android="http://schemas.android.com/apk/res/android">
  <header android:fragment="droid.examples.preferences.SettingsActivity.SettingsFragment"
          android:title="Prefs 1"
          android:summary="An example of some preferences." />
</preference-headers>

android:fragment attribute value can contains '$' but '+' won't work because Register doesn't support it and we will get compilation error.

Thanks everyone who tried to help me




回答2:


Looks like the string you provide is just a message to send to your parent activity. Your parent activity is responsible for instantiating the right Fragment, and performing the ceremony to render it.

The platform docs seem to indicate as such:

When a user taps a Preference with an associated Fragment, the interface method PreferenceFragmentCompat.OnPreferenceStartFragmentCallback.onPreferenceStartFragment() is called

At time of writing, there's a code snippet on that page, which I've translated for my own project more-or-less as follows:

// This has to go in the activity which hosts the PreferenceFragment
// which owns the Preference that has the `android:fragment` attribute.

using Android.Support.V7.Preferences;
using Android.Support.V4.App;
partial class MyActivity :
   PreferenceFragmentCompat.IOnPreferenceStartFragmentCallback
{

   Fragment GetFragmentForPrefString(string prefFragment)
   {
      // you implement this
   }
   const int fragmentContainerId = Resource.Id.whatever;

   public bool OnPreferenceStartFragment(
      PreferenceFragmentCompat caller, Preference pref)
   {
      string prefString = pref.Fragment;
      var transaction = SupportFragmentManager.BeginTransaction();
      transaction.Replace(fragmentContainerId,
         GetFragmentForPrefString(prefString));

      // you'll probably also want to add it to the back stack,
      // but it's not strictly necessary I guess.

      transaction.Commit();
      return true;
   }
}

Their sample involves the Java API method getSupportFragmentManager().getFragmentFactory() which doesn't appear to be part of the V28 Xamarin support NuGet packages. But honestly I'm not sure why that level of indirection is necessary; I'd suggest you simply implement something like

switch (prefFragmentName)
{
   case "Fragment 1":
      return new Fragment1();
// etc


来源:https://stackoverflow.com/questions/54556298/how-to-use-preferencefragmentcompat-in-the-preference-header

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!