¿Cómo escuchar los cambios de preferencia dentro de un PreferenceFragment?

92

Como se describe aquí , estoy subclasificando PreferenceFragment y lo estoy mostrando dentro de una Activity. Ese documento explica cómo escuchar los cambios de preferencias aquí , pero solo si subclasifica PreferenceActivity. Dado que no estoy haciendo eso, ¿cómo escucho los cambios de preferencias?

Intenté implementar OnSharedPreferenceChangeListener en mi PreferenceFragment pero parece que no funciona ( onSharedPreferenceChangedparece que nunca se llama).

Este es mi código hasta ahora:

SettingsActivity.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();
    }
}

SettingsFragment.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, ""));
        }
    }
}

preferencias.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>

Además, ¿PreferenceFragment es el lugar correcto para escuchar los cambios de preferencia o debería hacerlo dentro de la Actividad?

XåpplI'-I0llwlg'I -
fuente
1
Para su última pregunta, todo depende de su marco de diseño. Usar un enfoque MVC o MVP es difícil de hacer con Android, pero trato de dejar que todas mis acciones tengan lugar en la actividad (Controlador) que aloja el fragmento, y el fragmento sea solo la interfaz de usuario (Ver / Presentador)
John Shelley

Respuestas:

149

Creo que solo necesita registrar / anular el registro Listeneren su PreferenceFragmenty funcionará.

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

}

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

Dependiendo de lo que desee hacer, es posible que no necesite utilizar un oyente. Los cambios en las preferencias se comprometen SharedPreferencesautomáticamente.

antew
fuente
Ah, ya veo. Eso funciona. Pero, ¿debería obtener SharedPreferences a través de getPreferenceManager(como lo ha hecho) o getPreferenceScreen? ¿Cual es la diferencia?
XåpplI'-I0llwlg'I -
Para ser honesto, no estoy seguro de cuál es la diferencia real, tal vez alguien más pueda opinar sobre ello, podría ser un buen tema para otra pregunta también.
Antew
2
Bien, aquí hay una respuesta a esa pregunta. Parece que no hay absolutamente ninguna diferencia funcional, pero getPreferenceManagergeneralmente es la opción preferida.
XåpplI'-I0llwlg'I -
cuando se crea la preferencia de configuración por primera vez, el resumen no se establece según el valor de preferencia almacenado. ¿Cómo resolver esto?
srv_sud
1
@srv_sud, ¿te refieres a cuál keydebería ser el parámetro? No he probado esto, tienes un ejemplo de la respuesta de Gunnar a continuación: onSharedPreferenceChanged(null, ""). Puede que no sea adecuado para sus necesidades. Quizás debería iterar en sus claves de preferencias que necesitan una actualización
Jose_GD
24

La solución de antew funciona bien, aquí puede ver una actividad de preferencia completa para Android v11 en adelante:

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");

    }
}
}
Gunnar Bernstein
fuente
¡No hay esto en una clase estática!
josef
14

Todas las demás respuestas son correctas. Pero me gusta más esta alternativa porque inmediatamente tienes la instancia de Preferencia que causó el cambio.

@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;
        }
    });
}
Zsolt Safrany
fuente
Correcto, aunque onSharedPreferenceChanged ()puedes acceder a la instancia de Preference fácilmente con findPreference(key). ¿Quizás la SharedPreferencesforma es la preferida debido a la cosa de registrarse / anular el registro?
Jose_GD
2

Esto funcionó para mí desde PreferenceFragment.onCreate ()

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

Aquí hay una forma de hacerlo y evitar posibles pérdidas de memoria:

@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);
}
Eaweb
fuente
0

Otro ejemplo completo para que veas la imagen completa.

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);
        }
    }
}
Desarrollador
fuente
0

Recientemente terminé de armar el mío PreferenceScreenusando el Preferences API, así que pensé en contribuir con mi propio Ejemplo completo. Esto incluye actualizar el SummaryValor nuevo / cambiado, así como escuchar y reaccionar a los cambios.

PD. Para responder a su última pregunta: para mostrar un valor predeterminado para Summaryla creación inicial de PreferenceScreen(antes de cualquier cambio en el valor), simplemente puede establecer el android:summaryvalor de su elección, desde el preferences.xmlarchivo directamente, y luego, una vez allí es un cambio en el Valor, se actualizará automáticamente usando el código contenido en mi Ejemplo a continuación. Personalmente , utilizo una breve explicación de Preferencecomo mi inicialSummary, Establecido dentro de mi preferences.xml, y luego una vez que el valor no se ve modificado por primera vez, simplemente se muestran el valor actual como Summarya partir de entonces ..

De todos modos , aquí está mi completa Ejemplo:

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();
}
}


¡Espero que esto ayude!
Cualquier comentario positivo es muy apreciado, ya que soy bastante nuevo en StackOverflow;) ¡
Feliz codificación!

Studio2bDesigns
fuente
En onPause, ¿se super.onPause()debe llamar antes o después de que se cancele el registro del oyente? ¿Hay siquiera una diferencia? He visto ambos en numerosos ejemplos
Bryan W
@BryanWalsh También he visto ejemplos de ambas formas ... Creo que anular el registro del listerner antes de llamar super.onPause();es probablemente la mejor manera de hacerlo.
Studio2bDesigns
0

Solo necesita eliminar la clase Prefernce especificada en su onResume()método. En mi caso, estaba usando SwitchPreferenceclass, por lo tanto, el código sería como: 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);

    }

Luego, en la actividad en la que desea usar el PrefernceFragmentvalor, simplemente use el SharedPreferenceobjeto para llamar a los valores y activarlo.

Prajwal W
fuente