En este documento:
- Información general
- Definición de preferencias en XML
- Creación de una actividad de preferencia
- Utilización de fragmentos de preferencia
- Configuración de valores predeterminados
- Utilización de encabezados de preferencia
- Lectura de preferencias
- Administración del uso de red
- Creación de una preferencia personalizada
Clases fundamentales
Consulta también:
Las aplicaciones generalmente incluyen una configuración que permite que los usuarios modifiquen las funciones y los comportamientos de las apps. Por ejemplo, algunas apps permiten que los usuarios especifiquen si se habilitan las notificaciones o que especifiquen con qué frecuencia la aplicación sincroniza los datos con la nube.
Si deseas proporcionar una configuración para tu app, debes utilizar las
Preference API de Android para crear una interfaz que sea coherente con
la experiencia de usuario de otras aplicaciones de Android (incluida la configuración del sistema). En este documento, se describe
la manera de crear la configuración de tu app usando las Preference API.
Diseño de configuración
Para obtener información sobre cómo diseñar la configuración, lee la guía de diseño Configuración.
Figura 1: Capturas de pantalla de la configuración de la app Centro de Mensajes
de Android. Al seleccionar un elemento definido por una Preference,
se abre una interfaz para cambiar la configuración.
Información general
En lugar de utilizar objetos View para crear la interfaz de usuario, la configuración se
crea utilizando diversas subclases de la clase Preference que tú
declaras en un archivo XML.
Un objeto Preference es el componente esencial de una
configuración. Cada Preference aparece como un elemento en una lista y proporciona la
interfaz de usuario (IU) correspondiente para que los usuarios modifiquen la configuración. Por ejemplo, una CheckBoxPreference crea un elemento de lista que muestra una casilla de verificación y una ListPreference crea un elemento que abre un cuadro de diálogo con una lista de opciones.
Cada Preference que agregas tiene un par clave-valor correspondiente que
el sistema utiliza para guardar la configuración en un archivo SharedPreferences
predeterminado para las configuraciones de tu app. Cuando el usuario cambia una configuración, el sistema actualiza el valor correspondiente
en el archivo SharedPreferences. El único momento en el que debes
interactuar directamente con el archivo SharedPreferences asociado es cuando
necesitas leer el valor para determinar el comportamiento de tu app de acuerdo con la configuración del usuario.
El valor guardado en SharedPreferences para cada configuración puede ser uno de los
siguientes tipos de datos:
- Booleano
- Flotante
- Int
- Largo
- String
- String
Set
Dado que la IU de configuración de tu app se crea utilizando objetos Preference
en lugar de objetos
View, debes utilizar una subclase especializada Activity o
Fragment para mostrar la configuración de lista:
- Si tu app admite versiones de Android anteriores a 3.0 (nivel de API 10 y niveles inferiores), debes
crear la actividad como una extensión de la clase
PreferenceActivity. - En cambio, en Android 3.0 y las versiones posteriores, debes utilizar una
Activitytradicional que aloje unPreferenceFragmentque muestre la configuración de tu app. Sin embargo, también puedes utilizar unaPreferenceActivitypara crear un diseño con dos paneles para pantallas grandes cuando tienes varios grupos de configuraciones.
La forma de configurar tu PreferenceActivity y las instancias de PreferenceFragment se explica en las secciones Creación de una actividad de preferencia y Uso
de fragmentos de preferencias.
Preferencias
Cada configuración de tu app está representada por una subclase específica de la clase Preference. Cada subclase incluye un conjunto de propiedades principales que te permiten
especificar, por ejemplo, un título para la configuración y el valor predeterminado. Cada subclase también proporciona
sus propias propiedades especializadas y su interfaz de usuario. Por ejemplo, en la figura 1 se muestra una captura de pantalla de
la configuración de la app Mensajes. Cada elemento de lista de la pantalla de configuración está respaldado por un objeto Preference diferente.
Algunas de las preferencias más comunes son las siguientes:
CheckBoxPreference- Muestra un elemento con una casilla de verificación para una configuración que está habilitada o inhabilitada. El valor
guardado es un booleano (
truesi está marcada). ListPreference- Abre un cuadro de diálogo con una lista de botones de selección. El valor guardado puede ser cualquiera de los tipos de valores admitidos (enumerados anteriormente).
EditTextPreference- Abre un cuadro de diálogo con un widget
EditText. El valor guardado es unString.
Consulta la clase Preference para obtener una lista de todas las otras subclases y sus
correspondientes propiedades.
Desde luego, las clases incorporadas no satisfacen todas las necesidades, y es posible que tu aplicación requiera
algo más especializado. Por ejemplo, la plataforma actualmente no proporciona una clase Preference para elegir un número o una fecha. Por lo tanto, es posible que debas definir
tu propia subclase Preference. Para obtener ayuda para hacerlo, consulta la sección Creación de una preferencia personalizada.
Definición de preferencias en XML
Si bien puedes crear instancias de objetos Preference nuevos en tiempo de ejecución,
debes definir tu lista de configuración en XML con una jerarquía de objetos Preference
. Se prefiere utilizar un archivo XML para definir tu colección de configuraciones porque el archivo
proporciona una estructura fácil de leer que se puede actualizar simplemente. Además, las configuraciones de tu app
generalmente están predeterminadas, aunque tú puedes modificar la colección en tiempo de ejecución.
Cada subclase Preference puede declararse con un elemento XML que
coincida con el nombre de la clase, como <CheckBoxPreference>.
Debes guardar el archivo XML en el directorio res/xml/. Si bien puedes asignar el nombre
que desees al archivo, tradicionalmente se llama preferences.xml. Por lo general, necesitas un solo archivo,
porque las ramas de la jerarquía (que abren su propia lista de configuraciones) se declaran utilizando instancias
anidadas de PreferenceScreen.
Nota: Si deseas crear un diseño multipanel para tu configuración, necesitas archivos XML separados para cada fragmento.
El nodo raíz del archivo XML debe ser un elemento <PreferenceScreen>. Dentro de este elemento, agregas cada Preference. Cada elemento secundario que agregas dentro del elemento
<PreferenceScreen> aparece como un
elemento en la lista de configuración.
Por ejemplo:
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<CheckBoxPreference
android:key="pref_sync"
android:title="@string/pref_sync"
android:summary="@string/pref_sync_summ"
android:defaultValue="true" />
<ListPreference
android:dependency="pref_sync"
android:key="pref_syncConnectionType"
android:title="@string/pref_syncConnectionType"
android:dialogTitle="@string/pref_syncConnectionType"
android:entries="@array/pref_syncConnectionTypes_entries"
android:entryValues="@array/pref_syncConnectionTypes_values"
android:defaultValue="@string/pref_syncConnectionTypes_default" />
</PreferenceScreen>
En este ejemplo, hay una CheckBoxPreference y una ListPreference. Ambos elementos incluyen los siguientes tres atributos:
android:key- Este atributo se requiere para las preferencias que continúan con un valor de datos. Especifica la clave
única (una string) que el sistema usa cuando guarda el valor de esta configuración en las
SharedPreferences.Este atributo no es necesario únicamente cuando la preferencia es una
PreferenceCategoryoPreferenceScreen, o la preferencia especifica unIntentpara invocar (con un elemento<intent>) o unFragmentpara mostrar (con un atributoandroid:fragment). android:title- Esto proporciona un nombre de la configuración visible para el usuario.
android:defaultValue- Esto especifica el valor inicial que el sistema debe configurar en el archivo
SharedPreferences. Debes proporcionar una valor predeterminado para todas las configuraciones.
Para obtener información sobre todos los demás atributos admitidos, consulta la documentación de Preference (y la subclase respectiva).
Figura 2: Configuración de categorías
con títulos.
1. La categoría se especifica con el elemento <PreferenceCategory>.
2. El título se
especifica con el atributo android:title.
Cuando la lista de configuración supera los 10 elementos, puedes agregar títulos para definir grupos de configuraciones o mostrar estos grupos en una pantalla separada. Estas opciones se describen en las secciones siguientes.
Creación de grupos de configuración
Si presentas una lista de 10 o más configuraciones, es posible que a los usuarios les resulte difícil explorarlos, comprenderlos y procesarlos. Para solucionar esto, puedes dividir algunas o todas las configuraciones en grupos, y convertir una lista larga en varias listas más cortas. Un grupo de configuraciones relacionadas puede presentarse de una de dos formas:
Puedes utilizar una de estas técnicas de agrupación, o ambas, para organizar la configuración de tu app. Para decidir qué técnica utilizar y cómo dividir las configuraciones, debes seguir las pautas de la guía de Configuración de Diseño Android.
Utilización de títulos
Si deseas proporcionar divisores con encabezados entre grupos de configuraciones (como se muestra en la figura 2),
coloca cada grupo de objetos Preference dentro de una PreferenceCategory.
Por ejemplo:
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceCategory
android:title="@string/pref_sms_storage_title"
android:key="pref_key_storage_settings">
<CheckBoxPreference
android:key="pref_key_auto_delete"
android:summary="@string/pref_summary_auto_delete"
android:title="@string/pref_title_auto_delete"
android:defaultValue="false"... />
<Preference
android:key="pref_key_sms_delete_limit"
android:dependency="pref_key_auto_delete"
android:summary="@string/pref_summary_delete_limit"
android:title="@string/pref_title_sms_delete"... />
<Preference
android:key="pref_key_mms_delete_limit"
android:dependency="pref_key_auto_delete"
android:summary="@string/pref_summary_delete_limit"
android:title="@string/pref_title_mms_delete" ... />
</PreferenceCategory>
...
</PreferenceScreen>
Utilización de subpantallas
Si deseas colocar grupos de configuraciones en una subpantalla (como se muestra en la figura 3), dispón el grupo
de objetos Preference dentro de una PreferenceScreen.
Figura 3: Configuración de subpantallas. El elemento <PreferenceScreen>
crea un elemento que, cuando se selecciona, abre una lista separada para mostrar las configuraciones anidadas.
Por ejemplo:
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<!-- opens a subscreen of settings -->
<PreferenceScreen
android:key="button_voicemail_category_key"
android:title="@string/voicemail"
android:persistent="false">
<ListPreference
android:key="button_voicemail_provider_key"
android:title="@string/voicemail_provider" ... />
<!-- opens another nested subscreen -->
<PreferenceScreen
android:key="button_voicemail_setting_key"
android:title="@string/voicemail_settings"
android:persistent="false">
...
</PreferenceScreen>
<RingtonePreference
android:key="button_voicemail_ringtone_key"
android:title="@string/voicemail_ringtone_title"
android:ringtoneType="notification" ... />
...
</PreferenceScreen>
...
</PreferenceScreen>
Utilización de intents
En algunos casos, quizá desees que un elemento de preferencia abra una actividad diferente, en lugar de una
pantalla de configuración, por ejemplo, un navegador web para ver una página web. Para invocar un Intent cuando el usuario seleccione un elemento de preferencia, agrega un elemento <intent>
como campo secundario del elemento <Preference> correspondiente.
Por ejemplo, a continuación se explica cómo puedes utilizar un elemento de preferencia para abrir una página web:
<Preference android:title="@string/prefs_web_page" >
<intent android:action="android.intent.action.VIEW"
android:data="http://www.example.com" />
</Preference>
Puedes crear intents implícitas y explícitas utilizando los siguientes atributos:
android:action- La acción que se ha de asignar, según el método
setAction(). android:data- Los datos que se han de asignar, según el método
setData(). android:mimeType- El tipo de MIME que se ha de asignar, según el método
setType(). android:targetClass- La parte de la clase del nombre del componente, según el método
setComponent(). android:targetPackage- Parte del paquete del nombre del componente, según el método
setComponent().
Creación de una actividad de preferencia
Para mostrar tu configuración en una actividad, extiende la clase PreferenceActivity. Es una extensión de la clase Activity tradicional que muestra una lista de configuración basada en una jerarquía de objetos Preference. PreferenceActivity
mantiene automáticamente la configuración asociada con cada Preference cuando el usuario realiza un cambio.
Nota: Si desarrollas una aplicación para Android 3.0 y
versiones superiores, debes utilizar PreferenceFragment. Consulta la sección
siguiente, Utilización de fragmentos de preferencia.
Lo más importante que debes recordar es que no se carga un diseño de vistas durante el callback onCreate(). En lugar de ello, se llama a addPreferencesFromResource() para
agregar las preferencias que declaraste en un archivo XML para la actividad. Por ejemplo, este es el código
mínimo vacío que se requiere para una PreferenceActivity funcional:
public class SettingsActivity extends PreferenceActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.preferences);
}
}
En realidad, este código es suficiente para algunas apps, porque apenas el usuario modifica una preferencia,
el sistema guarda los cambios en un archivo SharedPreferences predeterminado que los
otros componentes de tu aplicación pueden leer cuando necesitas comprobar la configuración del usuario. Sin embargo,
muchas apps requieren un poco más de código para escuchar los cambios que se realizan a las preferencias.
Para obtener información sobre la forma de escuchar los cambios en el archivo SharedPreferences,
consulta la sección Lectura de preferencias.
Utilización de fragmentos de preferencia
Si desarrollas una app para Android 3.0 (nivel de API 11) y versiones posteriores, debes usar un PreferenceFragment para mostrar tu lista de objetos
Preference. Puedes agregar un PreferenceFragment a cualquier actividad; no
necesitas utilizar una PreferenceActivity.
Los fragmentos proporcionan una
arquitectura más flexible para tu aplicación, en comparación con el uso exclusivo de actividades, independientemente de la clase
de actividad que esté desarrollando. Por lo tanto, usar PreferenceFragment para controlar la pantalla de tu configuración en lugar de PreferenceActivity siempre que sea posible.
Tu implementación de PreferenceFragment puede ser tan simple como
la definición del método onCreate() para cargar un
archivo de preferencias con addPreferencesFromResource(). Por ejemplo:
public static class SettingsFragment extends PreferenceFragment {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Load the preferences from an XML resource
addPreferencesFromResource(R.xml.preferences);
}
...
}
Luego puedes agregar este fragmento a una Activity de la misma forma que lo haría para cualquier otro
Fragment. Por ejemplo:
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();
}
}
Nota: Un PreferenceFragment no tiene
su propio objeto Context. Si necesitas un objeto Context
, puedes llamar a getActivity(). Sin embargo, solo debes llamar a
getActivity() cuando el fragmento está adjuntado a una actividad. Cuando
el fragmento aún no se haya anexado, o se haya separado durante el final de su ciclo de vida, getActivity() mostrará null.
Configuración de valores predeterminados
Las preferencias que creas probablemente definirán algunos comportamientos importantes para tu aplicación; por ende, es
necesario que inicialices el archivo SharedPreferences asociado con
valores predeterminados para cada Preference cuando el usuario abre por primera vez tu
aplicación.
Lo primero que debes hacer es especificar un valor predeterminado para cada objeto Preference
en tu archivo XML mediante el atributo android:defaultValue. El valor puede ser cualquier tipo de
de dato que sea adecuado para el objeto Preference correspondiente. Por
ejemplo:
<!-- default value is a boolean -->
<CheckBoxPreference
android:defaultValue="true"
... />
<!-- default value is a string -->
<ListPreference
android:defaultValue="@string/pref_syncConnectionTypes_default"
... />
Luego, desde el método onCreate() en la actividad principal de tu
aplicación ,y en cualquier otra actividad a través de la cual el usuario pueda ingresar en tu aplicación por
primera vez, llama a setDefaultValues():
PreferenceManager.setDefaultValues(this, R.xml.advanced_preferences, false);
Esta llamada durante onCreate() garantiza que tu
aplicación se inicialice correctamente con la configuración predeterminada, que tu aplicación quizá necesite
leer para determinar algunos comportamientos (por ejemplo, si se descargan datos mientras se utiliza
una red móvil).
Este método adopta tres argumentos:
- El
Contextde tu aplicación. - El ID de recurso del archivo XML de preferencia para el que deseas configurar los valores predeterminados.
- Un valor booleano que indica si los valores predeterminados deben configurarse más de una vez.
Cuando es
false, el sistema configura los valores predeterminados solo si este método nunca se ha llamado anteriormente (oKEY_HAS_SET_DEFAULT_VALUESen el archivo de preferencias compartidas del valor predeterminado es falso).
Siempre que configures el tercer argumento como false, puedes llamar de forma segura a este método
cada vez que se inicia tu actividad sin reemplazar las preferencias guardadas del usuario restableciéndolas con
los valores predeterminados. Sin embargo, si lo configuras como true, reemplazarás los valores
anteriores con los valores predeterminados.
Utilización de encabezados de preferencia
En casos aislados, quizá desees diseñar tu configuración de forma tal que la primera pantalla
muestre solo una lista de subpantallas (por ejemplo, en la app Configuración del sistema,
como se muestra en las figuras 4 y 5). Cuando desarrollas este diseño para Android 3.0 y versiones posteriores, debes
usar la función de “encabezados” en lugar de crear subpantallas con elementos
PreferenceScreen anidados.
Para crear tu configuración con encabezados, debes hacer lo siguiente:
- Separa cada grupo de configuraciones en instancias independientes de
PreferenceFragment. Es decir, cada grupo de configuraciones necesita un archivo XML independiente. - Crea un archivo de encabezados XML que enumere cada grupo de configuraciones y declare qué fragmento contiene la lista de configuraciones correspondiente.
- Extiende la clase
PreferenceActivitypara alojar tu configuración. - Implementa el callback
onBuildHeaders()para especificar el archivo de encabezados.
Un gran beneficio de utilizar este diseño es que PreferenceActivity
presenta automáticamente el diseño de dos paneles que se muestra en la figura 4 cuando se ejecuta en pantallas grandes.
Incluso si tu aplicación admite versiones de Android anteriores a 3.0, puedes crear tu
aplicación para utilizar PreferenceFragment para una presentación de dos paneles en
dispositivos más nuevos y a la vez, admitir una jerarquía tradicional multipantalla en dispositivos
más antiguos (consulta la sección Admisión de versiones anteriores con
encabezados de preferencia).
Figura 4: Diseño de dos paneles con encabezados.
1. Los
encabezados se definen con un archivo de encabezados XML.
2. Cada grupo de configuraciones se define con un
PreferenceFragment especificado por un elemento <header> en
el archivo de encabezados.
Figura 5: Teléfono celular con encabezados de configuración. Cuando se selecciona un
elemento, el PreferenceFragment asociado reemplaza los
encabezados.
Creación del archivo de encabezados
Un solo elemento <header>
dentro de un elemento raíz <preference-headers> especifica cada grupo de configuraciones de tu lista de encabezados. Por ejemplo:
<?xml version="1.0" encoding="utf-8"?>
<preference-headers xmlns:android="http://schemas.android.com/apk/res/android">
<header
android:fragment="com.example.prefs.SettingsActivity$SettingsFragmentOne"
android:title="@string/prefs_category_one"
android:summary="@string/prefs_summ_category_one" />
<header
android:fragment="com.example.prefs.SettingsActivity$SettingsFragmentTwo"
android:title="@string/prefs_category_two"
android:summary="@string/prefs_summ_category_two" >
<!-- key/value pairs can be included as arguments for the fragment. -->
<extra android:name="someKey" android:value="someHeaderValue" />
</header>
</preference-headers>
Con el atributo android:fragment, cada encabezado declara una instancia de PreferenceFragment que debe abrirse cuando el usuario selecciona el encabezado.
El elemento <extras> te permite pasar pares clave-valor al fragmento en un Bundle. El fragmento puede obtener los argumentos llamando a getArguments(). Puedes pasar argumentos al fragmento por diversas
razones, pero una buena razón es reutilizar la misma subclase de PreferenceFragment para cada grupo y emplear el argumento para especificar el
archivo XML de preferencias que el fragmento debe cargar.
Por ejemplo, a continuación se incluye un fragmento que se puede reutilizar para varios grupos de configuraciones cuando cada
encabezado define un argumento <extra> con la clave "settings":
public static class SettingsFragment extends PreferenceFragment {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String settings = getArguments().getString("settings");
if ("notifications".equals(settings)) {
addPreferencesFromResource(R.xml.settings_wifi);
} else if ("sync".equals(settings)) {
addPreferencesFromResource(R.xml.settings_sync);
}
}
}
Muestra de encabezados
Para mostrar los encabezados de preferencias, debes implementar el método de callback onBuildHeaders() y llamar a
loadHeadersFromResource(). Por ejemplo:
public class SettingsActivity extends PreferenceActivity {
@Override
public void onBuildHeaders(List<Header> target) {
loadHeadersFromResource(R.xml.preference_headers, target);
}
}
Cuando el usuario selecciona un elemento de la lista de encabezados, el sistema abre el PreferenceFragment asociado.
Nota: Cuando se usan encabezados de preferencias, tu subclase de PreferenceActivity no necesita implementar el método onCreate() porque la única tarea
que se requiere para la actividad es la carga de los encabezados.
Admisión de versiones anteriores con encabezados de preferencia
Si tu aplicación admite versiones de Android anteriores a 3.0, también puedes utilizar encabezados para
proporcionar un diseño de dos paneles cuando se ejecuta en Android 3.0 y versiones posteriores. Todo lo que debes hacer es crear un
archivo XML de preferencias adicional que utilice elementos <Preference> básicos que se comporten como los elementos de encabezado (para que sean utilizados por las versiones anteriores de Android
).
Sin embargo, en lugar de abrir una PreferenceScreen nueva, cada uno de los elementos <Preference> envía un Intent a
la PreferenceActivity que especifica el archivo XML de preferencias que debe
cargarse.
Por ejemplo, a continuación se incluye un archivo XML para los encabezados de preferencias que se usa en Android 3.0
y versiones posteriores (res/xml/preference_headers.xml):
<preference-headers xmlns:android="http://schemas.android.com/apk/res/android">
<header
android:fragment="com.example.prefs.SettingsFragmentOne"
android:title="@string/prefs_category_one"
android:summary="@string/prefs_summ_category_one" />
<header
android:fragment="com.example.prefs.SettingsFragmentTwo"
android:title="@string/prefs_category_two"
android:summary="@string/prefs_summ_category_two" />
</preference-headers>
El siguiente es un archivo de preferencias que proporciona los mismos encabezados para las versiones anteriores a
Android 3.0 (res/xml/preference_headers_legacy.xml):
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<Preference
android:title="@string/prefs_category_one"
android:summary="@string/prefs_summ_category_one" >
<intent
android:targetPackage="com.example.prefs"
android:targetClass="com.example.prefs.SettingsActivity"
android:action="com.example.prefs.PREFS_ONE" />
</Preference>
<Preference
android:title="@string/prefs_category_two"
android:summary="@string/prefs_summ_category_two" >
<intent
android:targetPackage="com.example.prefs"
android:targetClass="com.example.prefs.SettingsActivity"
android:action="com.example.prefs.PREFS_TWO" />
</Preference>
</PreferenceScreen>
Dado que la compatibilidad con los <preference-headers> se agregó en Android 3.0, el sistema llama a
onBuildHeaders() en tu PreferenceActivity solo cuando se ejecuta en Android 3.0 o versiones posteriores. Para cargar
el archivo de encabezados “heredado” (preference_headers_legacy.xml), debes comprobar la versión de Android
y si esta es anterior a la 3.0 (HONEYCOMB), llama a addPreferencesFromResource() para
cargar el archivo de encabezados heredado. Por ejemplo:
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
// Load the legacy preferences headers
addPreferencesFromResource(R.xml.preference_headers_legacy);
}
}
// Called only on Honeycomb and later
@Override
public void onBuildHeaders(List<Header> target) {
loadHeadersFromResource(R.xml.preference_headers, target);
}
Lo único que queda por hacer es manejar la Intent que se pasó a la
actividad para identificar qué archivo de preferencia se debe cargar. Recupera la acción de la intent y compárala
con las strings de acción conocidas que hayas usado en las etiquetas <intent> del archivo XML de preferencias:
final static String ACTION_PREFS_ONE = "com.example.prefs.PREFS_ONE";
...
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String action = getIntent().getAction();
if (action != null && action.equals(ACTION_PREFS_ONE)) {
addPreferencesFromResource(R.xml.preferences);
}
...
else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
// Load the legacy preferences headers
addPreferencesFromResource(R.xml.preference_headers_legacy);
}
}
Ten en cuenta que las llamadas consecutivas a addPreferencesFromResource() apilarán
todas las preferencias en una sola lista; por lo tanto, asegúrate de que solo se llame una vez encadenando las
condiciones con declaraciones else-if.
Lectura de preferencias
De forma predeterminada, todas las preferencias de tu app se guardan en un archivo al que se puede acceder desde cualquier lugar
dentro de tu aplicación mediante un llamado al método estático PreferenceManager.getDefaultSharedPreferences(). Esto devuelve el objeto SharedPreferences que contiene todos los pares clave-valor asociados
con los objetos Preference usados en tu PreferenceActivity.
Por ejemplo, a continuación se muestra cómo puedes leer uno de los valores de preferencia desde cualquier otra actividad en tu aplicación:
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this); String syncConnPref = sharedPref.getString(SettingsActivity.KEY_PREF_SYNC_CONN, "");
Escucha de los cambios de preferencias
Existen varios motivos por los que quizá desees recibir una notificación apenas el usuario cambia una de las
preferencias. Para recibir un callback cuando se realiza un cambio a cualquiera de las preferencias,
implementa la interfaz de SharedPreference.OnSharedPreferenceChangeListener y registra el receptor del objeto
SharedPreferences llamando a registerOnSharedPreferenceChangeListener().
La interfaz tiene un solo método de callback, onSharedPreferenceChanged() y quizá te resulte más fácil implementar la interfaz como parte de
tu actividad. Por ejemplo:
public class SettingsActivity extends PreferenceActivity
implements OnSharedPreferenceChangeListener {
public static final String KEY_PREF_SYNC_CONN = "pref_syncConnectionType";
...
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
String key) {
if (key.equals(KEY_PREF_SYNC_CONN)) {
Preference connectionPref = findPreference(key);
// Set summary to be the user-description for the selected value
connectionPref.setSummary(sharedPreferences.getString(key, ""));
}
}
}
En este ejemplo, el método comprueba si la configuración cambiada es de una clave de preferencia conocida. Llama
a findPreference() para obtener el objeto
Preference que se cambió y poder modificar el resumen del
elemento para que sea una descripción de la selección del usuario. Es decir, si la configuración es una ListPreference u otra configuración de varias opciones, debes llamar a setSummary() cuando se cambia la configuración para mostrar el
estado actual (como la configuración de suspensión que se muestra en la figura 5).
Nota: Como se describe en el documento Configuración de Diseño Android, te recomendamos que actualices el
resumen de una ListPreference cada vez que el usuario cambia la preferencia, a
fin de describir la configuración actual.
Para lograr una administración correcta del ciclo de vida en la actividad, te recomendamos registrar y anular el registro de
tu SharedPreferences.OnSharedPreferenceChangeListener durante los callbacks onResume() y onPause(), respectivamente:
@Override
protected void onResume() {
super.onResume();
getPreferenceScreen().getSharedPreferences()
.registerOnSharedPreferenceChangeListener(this);
}
@Override
protected void onPause() {
super.onPause();
getPreferenceScreen().getSharedPreferences()
.unregisterOnSharedPreferenceChangeListener(this);
}
Advertencia: Cuando llamas a registerOnSharedPreferenceChangeListener(), el administrador de preferencias actualmente no
almacena una referencia sólida al receptor. Debes almacenar una referencia
fuerte a la escucha (listener); de lo contrario, este será susceptible a la recolección de elementos no utilizados. Te
recomendamos que mantengas una referencia a la escucha (listener) en los datos de instancia de un objeto
que existirá mientras necesites a la escucha.
Por ejemplo, en el código siguiente, el emisor no mantiene una referencia con la escucha (listener). Como resultado, la escucha (listener) estará sujeta a la recolección de elementos no utilizados, y fallará en algún momento indeterminado del futuro:
prefs.registerOnSharedPreferenceChangeListener(
// Bad! The listener is subject to garbage collection!
new SharedPreferences.OnSharedPreferenceChangeListener() {
public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
// listener implementation
}
});
En cambio, almacena una referencia a la escucha (listener) en un campo de datos de instancia de un objeto que existirá mientras se necesite a la misma:
SharedPreferences.OnSharedPreferenceChangeListener listener =
new SharedPreferences.OnSharedPreferenceChangeListener() {
public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
// listener implementation
}
};
prefs.registerOnSharedPreferenceChangeListener(listener);
Administración del uso de red
A partir de Android 4.0, la aplicación Configuración del sistema permite que los usuarios vean cuántos datos de red utilizan sus aplicaciones en primer plano y en segundo plano. Los usuarios pueden inhabilitar el uso de los datos en segundo plano para apps individuales. Para evitar que los usuarios deshabiliten el acceso de tu app a los datos en segundo plano, debes utilizar la conexión de datos de forma eficiente y permitir que los usuarios definan mejor el uso de datos de tu app a través de la configuración de tu aplicación.
Por ejemplo, puedes permitir que el usuario controle la frecuencia con la que tu app sincroniza los datos, si tu aplicación realiza cargas y descargas solo cuando el dispositivo está conectado con Wi-Fi, si tu app utiliza datos con el servicio de itinerancia, etc. Con estos controles a su disposición, es mucho menos probable que los usuarios inhabiliten el acceso de tu app a los datos cuando se acercan a los límites que establecieron en la configuración del sistema, ya que pueden controlar precisamente la cantidad de datos que tu app usa.
Una vez que hayas agregado las preferencias necesarias en tu PreferenceActivity
para controlar los hábitos de datos de tu app, debes agregar un filtro de intents para ACTION_MANAGE_NETWORK_USAGE en tu archivo de manifiesto. Por ejemplo:
<activity android:name="SettingsActivity" ... >
<intent-filter>
<action android:name="android.intent.action.MANAGE_NETWORK_USAGE" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
Este filtro de intents le indica al sistema que esta es la actividad que controla el uso de datos
de tu aplicación. Por lo tanto, cuando el usuario inspecciona cuántos datos usa tu app desde la
app de configuración del sistema, se muestra un botón Ver configuración de la aplicación que lanza tu
PreferenceActivity para que el usuario pueda definir mejor la cantidad de datos que tu app
usa.
Creación de una preferencia personalizada
El framework de Android incluye una variedad de subclases Preference que
te permiten crear una IU para varios tipos de configuraciones.
Sin embargo, quizá descubras una configuración que necesitas para la cual no haya una solución incorporada, como un
selector de números o fechas. Si es así, deberás extender
la clase Preference o una de las demás subclases para crear una preferencia personalizada.
Cuando extiendes la clase Preference, hay algunas cosas importantes
que debes hacer:
- Especifica la interfaz de usuario que aparece cuando el usuario selecciona la configuración.
- Guarda el valor de configuración cuando corresponda.
- Inicializa la
Preferencecon el valor actual (o predeterminado) cuando aparece. - Proporciona el valor predeterminado cuando el sistema lo solicite.
- Si la
Preferenceproporciona su propia IU (por ejemplo, un cuadro de diálogo), guarda y restaura el estado para manejar los cambios del ciclo de vida (por ejemplo, cuando el usuario gira la pantalla).
En las siguientes secciones, se describe cómo realizar cada una de estas tareas.
Especificación de la interfaz de usuario
Si extiendes directamente la clase Preference, debes implementar
onClick() para definir la acción que ocurre cuando el usuario
selecciona el elemento. Sin embargo, la mayoría de las configuraciones personalizadas extienden DialogPreference para
mostrar un cuadro de diálogo, lo que simplifica el procedimiento. Cuando extiendes DialogPreference, debes llamar a setDialogLayoutResourcs() en el
constructor de la clase para especificar el diseño del cuadro de diálogo.
Por ejemplo, a continuación se muestra el constructor de una DialogPreference personalizada que declara el diseño, y especifica el texto de los
botones de diálogo positivos y negativos predeterminados:
public class NumberPickerPreference extends DialogPreference {
public NumberPickerPreference(Context context, AttributeSet attrs) {
super(context, attrs);
setDialogLayoutResource(R.layout.numberpicker_dialog);
setPositiveButtonText(android.R.string.ok);
setNegativeButtonText(android.R.string.cancel);
setDialogIcon(null);
}
...
}
Cómo guardar el valor de configuración
Puedes guardar un valor para la configuración en cualquier momento llamando a uno de los métodos persist*() de la clase Preference, como persistInt() si el valor de la configuración es un valor entero o
persistBoolean() para guardar un valor booleano.
Nota: Cada Preference puede guardar solo un
tipo de dato, por lo que debes usar el método persist*() adecuado para el tipo de dato que usa tu
Preference personalizada.
Cuando eliges continuar, la configuración puede depender de la clase Preference que extiendas. Si extiendes DialogPreference, debes continuar con el valor solo cuando el cuadro de diálogo
se cierra debido a un resultado positivo (el usuario selecciona el botón “Aceptar”).
Cuando una DialogPreference se cierra, el sistema llama al método onDialogClosed(). El método incluye un
argumento booleano que especifica si el resultado del usuario es "positivo"; si el valor es
true, significa que el usuario seleccionó el botón positivo y tú debes guardar el nuevo valor. Por
ejemplo:
@Override
protected void onDialogClosed(boolean positiveResult) {
// When the user selects "OK", persist the new value
if (positiveResult) {
persistInt(mNewValue);
}
}
En este ejemplo, mNewValue es un miembro de clase que contiene el valor actual
de la configuración. Al llamar a persistInt() se guarda el valor en
el archivo SharedPreferences (utilizando automáticamente la clave
especificada en el archivo XML para esta Preference).
Inicialización del valor actual
Cuando el sistema agrega tuPreference a la pantalla,
llama a onSetInitialValue() para notificarte
si la configuración tiene un valor persistente. Si no hay un valor persistente, esta llamada te proporciona
el valor predeterminado.
El método onSetInitialValue() pasa
un valor booleano, restorePersistedValue, para indicar si ya hay un valor persistente
para la configuración. Si es true, debes recuperar el valor persistente mediante un llamado a
uno de los métodos getPersisted*() de la clase Preference, como getPersistedInt() para un valor entero. Por lo general,
es recomendable que recuperes el valor persistente, de modo que puedas actualizar de forma adecuada la IU para reflejar el
valor guardado anteriormente.
Si restorePersistedValue es false, debes
utilizar el valor predeterminado que se pasa en el segundo argumento.
@Override
protected void onSetInitialValue(boolean restorePersistedValue, Object defaultValue) {
if (restorePersistedValue) {
// Restore existing state
mCurrentValue = this.getPersistedInt(DEFAULT_VALUE);
} else {
// Set default state from the XML attribute
mCurrentValue = (Integer) defaultValue;
persistInt(mCurrentValue);
}
}
Cada método getPersisted*() toma un argumento que especifica el
valor predeterminado que se usará en caso de que realmente no haya un valor persistente o la clave no exista. En
el ejemplo anterior, se usa una constante local para especificar el valor predeterminado en caso de que getPersistedInt() no pueda mostrar un valor persistente.
Advertencia: No puedes usar
defaultValue como valor predeterminado en el método getPersisted*() porque
su valor siempre es null cuando restorePersistedValue es true.
Provisión de un valor predeterminado
Si la instancia de tu clase Preference especifica un valor predeterminado
(con el atributo android:defaultValue), el
sistema llama a onGetDefaultValue() cuando crea una instancia del objeto para obtener el valor. Debes
implementar este método para que el sistema guarde el valor predeterminado en SharedPreferences. Por ejemplo:
@Override
protected Object onGetDefaultValue(TypedArray a, int index) {
return a.getInteger(index, DEFAULT_VALUE);
}
Los argumentos del método proporcionan todo lo que necesitas: la matriz de atributos y la posición
de índice de android:defaultValue, que debes recuperar. El motivo por el que debes
implementar este método para extraer el valor predeterminado del atributo es que debes especificar
un valor predeterminado local para el atributo en caso de que el valor no esté definido.
Cómo guardar y restaurar el estado de Preferencias
Al igual que una View en un diseño, tu subclase Preference
es responsable de guardar y restaurar su estado en caso de que la actividad o el fragmento se
reinicien (por ejemplo, cuando el usuario gira la pantalla). Para guardar y
restaurar correctamente el estado de tu clase Preference, debes implementar los métodos de
callback del ciclo de vida onSaveInstanceState() y onRestoreInstanceState().
El estado de tu Preference es definido por un objeto que implementa
la interfaz Parcelable. El framework de Android te proporciona este objeto
como punto de partida para definir tu objeto de estado: la clase Preference.BaseSavedState.
Para definir cómo tu clase Preference guarda tu estado, debes
extender la clase Preference.BaseSavedState. Debes reemplazar solo
algunos métodos y definir el objeto CREATOR
.
En la mayoría de las apps, puedes copiar la siguiente implementación y simplemente cambiar las líneas que
manejan el value si tu subclase Preference guarda un tipo
de datos que no sea un valor entero.
private static class SavedState extends BaseSavedState {
// Member that holds the setting's value
// Change this data type to match the type saved by your Preference
int value;
public SavedState(Parcelable superState) {
super(superState);
}
public SavedState(Parcel source) {
super(source);
// Get the current preference's value
value = source.readInt(); // Change this to read the appropriate data type
}
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
// Write the preference's value
dest.writeInt(value); // Change this to write the appropriate data type
}
// Standard creator object using an instance of this class
public static final Parcelable.Creator<SavedState> CREATOR =
new Parcelable.Creator<SavedState>() {
public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
}
Con la implementación anterior de Preference.BaseSavedState agregada
a tu app (generalmente como una subclase de tu subclase Preference),
debes implementar los métodos onSaveInstanceState() y onRestoreInstanceState() para tu subclase
Preference.
Por ejemplo:
@Override
protected Parcelable onSaveInstanceState() {
final Parcelable superState = super.onSaveInstanceState();
// Check whether this Preference is persistent (continually saved)
if (isPersistent()) {
// No need to save instance state since it's persistent,
// use superclass state
return superState;
}
// Create instance of custom BaseSavedState
final SavedState myState = new SavedState(superState);
// Set the state's value with the class member that holds current
// setting value
myState.value = mNewValue;
return myState;
}
@Override
protected void onRestoreInstanceState(Parcelable state) {
// Check whether we saved the state in onSaveInstanceState
if (state == null || !state.getClass().equals(SavedState.class)) {
// Didn't save the state, so call superclass
super.onRestoreInstanceState(state);
return;
}
// Cast state to custom BaseSavedState and pass to superclass
SavedState myState = (SavedState) state;
super.onRestoreInstanceState(myState.getSuperState());
// Set this Preference's widget to reflect the restored state
mNumberPicker.setValue(myState.value);
}