如何监听偏好片段中的偏好更改?

给你所述,我对 PreferenceFragment 进行了子类化,并将其显示在一个 Activity 中。该文档解释了如何监听首选项更改 给你,但只有在子类化 PreferenceActivity 的情况下才能监听。既然我没有这样做,那么我如何监听偏好的变化呢?

我曾经尝试在我的 PreferenceFragment 中实现 OnSharedPreferenceChangeListener,但是它似乎不起作用(onSharedPreferenceChanged似乎从来没有被调用过)。

到目前为止,这是我的代码:

设置 Activity.java

public class SettingsActivity extends Activity
{
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);


// Display the fragment as the main content.
getFragmentManager().beginTransaction().replace(android.R.id.content, new SettingsFragment()).commit();
}
}

设置片段.java

public class SettingsFragment extends PreferenceFragment implements OnSharedPreferenceChangeListener
{
public static final String KEY_PREF_EXERCISES = "pref_number_of_exercises";


@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);


// Load the preferences from an XML resource
addPreferencesFromResource(R.xml.preferences);
}


@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key)
{
//IT NEVER GETS IN HERE!
if (key.equals(KEY_PREF_EXERCISES))
{
// Set summary to be the user-description for the selected value
Preference exercisesPref = findPreference(key);
exercisesPref.setSummary(sharedPreferences.getString(key, ""));
}
}
}

Xml

<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" >


<EditTextPreference
android:defaultValue="15"
android:enabled="true"
android:key="pref_number_of_exercises"
android:numeric="integer"
android:title="Number of exercises" />


</PreferenceScreen>

另外,PreferenceFragment 是否是监听首选项更改的正确位置,或者我应该在 Activity 中进行监听?

72998 次浏览

I believe you just need to register/unregister the Listener in your PreferenceFragment and it will work.

@Override
public void onResume() {
super.onResume();
getPreferenceManager().getSharedPreferences().registerOnSharedPreferenceChangeListener(this);


}


@Override
public void onPause() {
getPreferenceManager().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this);
super.onPause();
}

Depending on what you want to do you may not need to use a listener. Changes to the preferences are committed to SharedPreferences automatically.

The solution of antew works well, here you can see a full preference activity for Android v11 onwards:

import android.app.Activity;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.os.Bundle;
import android.preference.ListPreference;
import android.preference.PreferenceFragment;


public class UserPreferencesV11 extends Activity  {


@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);


// Display the fragment as the main content.
getFragmentManager().beginTransaction().replace(android.R.id.content,
new PrefsFragment()).commit();
}


public static class PrefsFragment extends PreferenceFragment implements OnSharedPreferenceChangeListener {


@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);


// Load the preferences from an XML resource
addPreferencesFromResource(R.xml.preferences);


// set texts correctly
onSharedPreferenceChanged(null, "");


}


@Override
public void onResume() {
super.onResume();
// Set up a listener whenever a key changes
getPreferenceScreen().getSharedPreferences().registerOnSharedPreferenceChangeListener(this);
}


@Override
public void onPause() {
super.onPause();
// Set up a listener whenever a key changes
getPreferenceScreen().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this);
}


@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
// just update all
ListPreference lp = (ListPreference) findPreference(PREF_YOUR_KEY);
lp.setSummary("dummy"); // required or will not update
lp.setSummary(getString(R.string.pref_yourKey) + ": %s");


}
}
}

All the other answers are correct. But I like this alternative better because you immediately have the Preference instance that caused the change.

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Preference pref = findPreference(getString(R.string.key_of_pref));
pref.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
// do whatever you want with new value


// true to update the state of the Preference with the new value
// in case you want to disallow the change return false
return true;
}
});
}

This worked for me from PreferenceFragment.onCreate()

OnSharedPreferenceChangeListener listener =
new SharedPreferences.OnSharedPreferenceChangeListener()
{
public void onSharedPreferenceChanged(SharedPreferences prefs, String key)
{
showDialog();
}
};

Another complete example so you see the entire picture.

public class SettingsActivity extends AppCompatPreferenceActivity {




/**
* A preference value change listener that updates the preference's summary
* to reflect its new value.
*/
private static Preference.OnPreferenceChangeListener
sBindPreferenceSummaryToValueListener =
new Preference.OnPreferenceChangeListener() {


@Override
public boolean onPreferenceChange(Preference preference, Object value) {
String stringValue = value.toString();


if (preference instanceof ListPreference) {
// For list preferences, look up the correct display value in
// the preference's 'entries' list.
ListPreference listPreference = (ListPreference) preference;
int index = listPreference.findIndexOfValue(stringValue);


// Set the summary to reflect the new value.
preference.setSummary(
index >= 0
? listPreference.getEntries()[index]
: null);


} else if (preference instanceof RingtonePreference) {
// For ringtone preferences, look up the correct display value
// using RingtoneManager.
if (TextUtils.isEmpty(stringValue)) {
// Empty values correspond to 'silent' (no ringtone).
preference.setSummary(R.string.pref_ringtone_silent);
} else {
Ringtone ringtone = RingtoneManager.getRingtone(
preference.getContext(), Uri.parse(stringValue));
if (ringtone == null) {
// Clear the summary if there was a lookup error.
preference.setSummary(null);
} else {
// Set the summary to reflect the new ringtone display
// name.
String name = ringtone.getTitle(preference.getContext());
preference.setSummary(name);
}
}


} else {
// For all other preferences, set the summary to the value's
// simple string representation.
preference.setSummary(stringValue);
}
return true;
}
};


/**
* Helper method to determine if the device has an extra-large screen. For
* example, 10" tablets are extra-large.
*/
private static boolean isXLargeTablet(Context context) {
return (context.getResources().getConfiguration().screenLayout
& Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_XLARGE;
}


/**
* Binds a preference's summary to its value. More specifically, when the
* preference's value is changed, its summary (line of text below the
* preference title) is updated to reflect the value. The summary is also
* immediately updated upon calling this method. The exact display format is
* dependent on the type of preference.
*
* @see #sBindPreferenceSummaryToValueListener
*/
private static void bindPreferenceSummaryToValue(Preference preference) {
// Set the listener to watch for value changes.
preference.setOnPreferenceChangeListener(sBindPreferenceSummaryToValueListener);


// Trigger the listener immediately with the preference's current value.
sBindPreferenceSummaryToValueListener.onPreferenceChange(preference,
PreferenceManager
.getDefaultSharedPreferences(preference.getContext())
.getString(preference.getKey(), ""));
}


@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setupActionBar();
}


/**
* Set up the {@link android.app.ActionBar}, if the API is available.
*/
private void setupActionBar() {
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
// Show the Up button in the action bar.
actionBar.setDisplayHomeAsUpEnabled(true);
}
}


@Override
public boolean onMenuItemSelected(int featureId, MenuItem item) {
int id = item.getItemId();
if (id == android.R.id.home) {
if (!super.onMenuItemSelected(featureId, item)) {
NavUtils.navigateUpFromSameTask(this);
}
return true;
}
return super.onMenuItemSelected(featureId, item);
}


/**
* {@inheritDoc}
*/
@Override
public boolean onIsMultiPane() {
return isXLargeTablet(this);
}


/**
* {@inheritDoc}
*/
@Override
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public void onBuildHeaders(List<Header> target) {
loadHeadersFromResource(R.xml.pref_headers, target);
}


/**
* This method stops fragment injection in malicious applications.
* Make sure to deny any unknown fragments here.
*/
protected boolean isValidFragment(String fragmentName) {
return PreferenceFragment.class.getName().equals(fragmentName)
|| GPSLocationPreferenceFragment.class.getName().equals(fragmentName)
|| DataSyncPreferenceFragment.class.getName().equals(fragmentName)
|| NotificationPreferenceFragment.class.getName().equals(fragmentName);
}


////////////////// NEW PREFERENCES ////////////////////////////


@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public static class GPSLocationPreferenceFragment extends PreferenceFragment {


Preference prefGPSServerAddr, prefGPSASDID, prefIsGPSSwitch;


@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.pref_gps_location);
setHasOptionsMenu(true);


// Bind the summaries of EditText/List/Dialog/Ringtone preferences
// to their values. When their values change, their summaries are
// updated to reflect the new value, per the Android Design
// guidelines.


bindPreferenceSummaryToValue(findPreference("gpsServer_Addr"));
bindPreferenceSummaryToValue(findPreference("gpsASD_ID"));




prefGPSServerAddr = findPreference("gpsServer_Addr");
prefGPSServerAddr.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {


try {
// do whatever you want with new value


}
catch (Exception ex)
{
Log.e("Preferences", ex.getMessage());
}


// true to update the state of the Preference with the new value
// in case you want to disallow the change return false
return true;
}
});


prefGPSASDID = findPreference("gpsASD_ID");
prefGPSASDID.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {


try {
// do whatever you want with new value


}
catch (Exception ex)
{
Log.e("Preferences", ex.getMessage());
}


// true to update the state of the Preference with the new value
// in case you want to disallow the change return false
return true;
}
});


prefIsGPSSwitch = findPreference("isGPS_Switch");
prefIsGPSSwitch.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {


try {
// do whatever you want with new value


}
catch (Exception ex)
{
Log.e("Preferences", ex.getMessage());
}


// true to update the state of the Preference with the new value
// in case you want to disallow the change return false
return true;
}
});
}


@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == android.R.id.home) {
boolean tabletSize = getResources().getBoolean(R.bool.isTablet);
if (tabletSize) {
startActivity(new Intent(getActivity(), MainActivity.class));
} else {
startActivity(new Intent(getActivity(), SettingsActivity.class));
}
return true;
}
return super.onOptionsItemSelected(item);
}
}


///////////////////////////////////////////////////////////////


/**
* This fragment shows notification preferences only. It is used when the
* activity is showing a two-pane settings UI.
*/
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public static class NotificationPreferenceFragment extends PreferenceFragment {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.pref_notification);
setHasOptionsMenu(true);


// Bind the summaries of EditText/List/Dialog/Ringtone preferences
// to their values. When their values change, their summaries are
// updated to reflect the new value, per the Android Design
// guidelines.
bindPreferenceSummaryToValue(findPreference("notifications_new_message_ringtone"));
}


@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == android.R.id.home) {
boolean tabletSize = getResources().getBoolean(R.bool.isTablet);
if (tabletSize) {
startActivity(new Intent(getActivity(), MainActivity.class));
} else {
startActivity(new Intent(getActivity(), SettingsActivity.class));
}
return true;
}
return super.onOptionsItemSelected(item);
}
}


/**
* This fragment shows data and sync preferences only. It is used when the
* activity is showing a two-pane settings UI.
*/
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public static class DataSyncPreferenceFragment extends PreferenceFragment {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.pref_data_sync);
setHasOptionsMenu(true);


// Bind the summaries of EditText/List/Dialog/Ringtone preferences
// to their values. When their values change, their summaries are
// updated to reflect the new value, per the Android Design
// guidelines.
bindPreferenceSummaryToValue(findPreference("sync_frequency"));
}


@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == android.R.id.home) {
boolean tabletSize = getResources().getBoolean(R.bool.isTablet);
if (tabletSize) {
startActivity(new Intent(getActivity(), MainActivity.class));
} else {
startActivity(new Intent(getActivity(), SettingsActivity.class));
}
return true;
}
return super.onOptionsItemSelected(item);
}
}
}

Here is one way to do it and avoid any potential memory leaks:

@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
addPreferencesFromResource(R.xml.pref_movies);


SharedPreferences sharedPreferences = getPreferenceScreen().getSharedPreferences();


//starts live change listener
sharedPreferences.registerOnSharedPreferenceChangeListener(this);
}


@Override
public void onDestroyView () {
super.onDestroyView();
//Unregisters listener here
PreferenceManager.getDefaultSharedPreferences(getContext())
.unregisterOnSharedPreferenceChangeListener(this);
}

I recently finished putting together my own PreferenceScreen using the Preferences API, so I thought I would contribute my own full Example.. This includes updating the Summary to new/changed Value, as well as Listening for and reacting to changes.

PS. To answer your last question: To show a default Value for Summary upon initial creation of the PreferenceScreen (prior to any change in the Value), you could simply set the android:summary to a Value of your choosing, from within the preferences.xml file directly - and then, once there is a change in the Value, it will update automatically by using the code contained in my Example below. PreferenceScreen0, I use a short explanation of the Preference as my initial Summary, set within my preferences.xml, and then once the Value PreferenceScreen1 get changed for the first time, it will simply show the current Value as the Summary from then on..

Anyhow, here is my full Example:

SettingsFragment.java

public class SettingsFragment extends PreferenceFragment {


public static final String PREF_NOTIFICATION_MODE = "pref_notificationMode";
private SharedPreferences.OnSharedPreferenceChangeListener preferenceChangeListener;


@Override
public void onCreate (@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.preferences);


final SharedPreferences getPrefs = PreferenceManager.getDefaultSharedPreferences(this.getActivity());


preferenceChangeListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {


if (key.equals(PREF_NOTIFICATION_MODE)) {
Preference notifModePref = findPreference(key);
notifModePref.setSummary(sharedPreferences.getString(key, ""));


//  DO SOMETHING ELSE HERE WHEN (PREF_NOTIFICATION_MODE) IS CHANGED
}
}
};
}


@Override
public void onResume() {
super.onResume();


getPreferenceScreen().getSharedPreferences().registerOnSharedPreferenceChangeListener(preferenceChangeListener);


Preference notifModePref = findPreference(PREF_NOTIFICATION_MODE);
notifModePref.setSummary(getPreferenceScreen().getSharedPreferences().getString(PREF_NOTIFICATION_MODE, ""));


}


@Override
public void onPause() {
getPreferenceScreen().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(preferenceChangeListener);


super.onPause();
}
}


I hope this helps!
Any positive feedback is greatly appreciated, as I'm fairly new to StackOverflow ;)
Happy coding!

You just need to delare the specified Prefernce class in your onResume() method. In my case I was using SwitchPreference class, therefore the code would be like- SettingsActivity.class

public static class PrivacyPreferenceFragment extends PreferenceFragment
{
public SwitchPreference switchPreference;


@Override
public void onResume() {
super.onResume();
switchPreference = (SwitchPreference) findPreference("privacy_notice_check");
}


@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.pref_privacy);
setHasOptionsMenu(true);


}

Then in the activity where you want to use the PrefernceFragment value, just use the SharedPreference object to call the values and trigger it.

If you only want to update the summary, using androidx preference library (see the official guides), and adding the following attribute in your layout xml will be enough:

<EditTextPreference
...
app:useSimpleSummaryProvider="true" />

Solution on Kotlin with PreferenceFragmentCompat

https://developer.android.com/reference/androidx/preference/PreferenceFragmentCompat

Listen when prefChange

class SettingsFragment : PreferenceFragmentCompat() {


override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.root_preferences, rootKey)
val prefListener =
OnSharedPreferenceChangeListener { prefs, key ->
val valueChanged = preferenceManager.sharedPreferences.getString(key,key)
when(valueChanged){
getString(R.string.value_lang_en) -> applyLang(valueChanged)
getString(R.string.value_lang_th) -> applyLang(valueChanged)
}
}
preferenceManager.
sharedPreferences.
registerOnSharedPreferenceChangeListener(prefListener)
}