Settings

En este articulo se aprenderá a crear una pantalla de ajustes o configuración para nuestra aplicación o juego. Para el desarrollo de este articulo vamos a utilizar la clase y subclases de la API "Preference" que será la que nos permita crear la interface de configuración.

 Toda la información de este articulo disponible en: Settings


Tabla de contenidos
 
     1. Principios
   
     2. Archivo de preferencias XML
               2.1.1 Utilizar títulos
               2.1.2 Crear sub-pantallas
          2.2 Utilizar Intents

     3. Crear PreferenceActivity

     4. Crear PreferenceFragment

     5. Utilizar Headers
          5.1 Crear archivo XML Headers
          5.2 Cargar cada grupo de ajustes
          6.1 Crear archivo de ajustes  
          6.2 Configurar PreferenceActivity

     7. Leer Preferencias

     8. Utilizar un listener

     9. Ajustes por defecto

     10. Preferencia personalizada
          10.1 Interface de usuario
          10.2 Guardar el valor del ajuste
          10.3 Iniciar valor predeterminado
          10.4 Proporcionar valor por defecto
          10.5 Guardar y restaurar estado

     11. Mini aplicación




1. Principios

Como se comentaba al principio del articulo, para crear una pantalla de ajustes o configuración necesitamos utilizar la API "Preference". Cada ajuste de dicha pantalla consiste en un objeto Preference que será almacenado individualmente mediante una clave-valor en un archivo SharedPreferences.

Los valores que podremos almacenar son los siguientes:
  • Boolean
  • Float
  • Int
  • Long
  • String
  • String Set

A la hora de mostrar nuestra pantalla de ajustes sera necesario crear un fragmento o activity:
  • Si nuestro proyecto tiene un nivel de API 10 o inferior sera necesario crear una activity que extienda la clase "PreferenceActivity".
  • Por el contrario si nuestra aplicación tiene un nivel de API 11 o superior tendremos dos posibilidades, podremos crear una activity que aloje un "PreferenceFragment" o directamente extender la clase "PreferenceActivity" creando un diseño de dos paneles.

Los elementos Preference mas comunes son los siguientes:
  • CheckBoxPreference - muestra un ajuste con una casilla de verificación (esta preferencia guarda un valor booleano, true-activado, false-desactivado).
  • ListPreference - genera un dialogo de opciones que muestra una lista de Radio-Buttons (se puede almacenar cualquier valor mencionado antes).
  • EditTextPreference - genera un dialogo que muestra un EditText (el valor almacenado será un String).



2. Archivo de preferencias XML

Aunque se puede realizar una pantalla de ajustes en tiempo de ejecución, es recomendable crear un archivo de recursos XML que contenga una jerarquía de objetos Prefernce. En esta jerarquía cada ajuste debe ser declarado con un elemento de la clase Preference.

El archivo generado se debe guardar en la carpeta del proyecto "res/xml". Normalmente solo se necesita un archivo de este tipo y es recomendable llamarlo "preferences.xml", aunque en ocasiones puede ser necesario crear multi-pantallas de ajustes con varios archivos.

Vamos a ver un ejemplo:
<PreferenceScreen 
    xmlns:android="http://schemas.android.com/apk/res/android">
    
    <CheckBoxPreference
        android:key="pref_sonido"
        android:title="Sonido"
        android:summary="Activa o desactiva el audio"
        android:defaultValue="true"/>
    
    <ListPreference
        android:dependency="pref_sonido"
        android:key="pref_nivel"
        android:title="Volumen"
        android:summary="Establece el nivel de audio"
        android:dialogTitle="Volumen"
        android:entries="@array/pref_nivel_entradas"
        android:entryValues="@array/pref_nivel_valores"
        android:defaultValue="@string/pref_nivel_default"/>
    
</PreferenceScreen>
En este tipo de archivos se debe declarar un elemento raíz <PreferenceScreen> y dentro crearemos los todos los ajustes que necesitemos. En este caso solamente se han creado dos ajustes.

Los atributos mas importantes son los siguientes:
  • android:key - es una clave única (un String) que utiliza el sistema para guardar el valor del ajuste en el archivo SharedPreferences.
  • android:title - titulo que mostrara el ajuste o preferencia.
  • android:summary - subtitulo que describe el ajuste o preferencia.
  • android:defaultValue - el valor por defecto que tendrá el ajuste.

Para conocer todos los atributos y que hace cada uno, podemos visitarla clase Preference. Hay que tener en cuenta que cada elemento Preference también tiene sus propios atributos.

En los siguientes puntos veremos como crear la activity o fragment y cargar este archivo de ajustes, pero vamos a ver el resultado antes de tiempo:




2.1 Crear grupos de ajustes

Si creamos una aplicación con multitud de ajustes, podrán estar separados por categorías o grupos. Android nos proporciona dos maneras de realizar la operación:

  • Utilizando títulos
  • O crear sub-pantallas


2.1.1 Utilizar títulos

Partiendo del ejemplo anterior vamos a ver como podemos poner un titulo a los dos ajustes que contiene nuestro archivo. Para crear un titulo simplemente utilizaremos el elemento <PreferenceCategory> como muestra el siguiente ejemplo:
<PreferenceScreen 
    xmlns:android="http://schemas.android.com/apk/res/android">

    <PreferenceCategory 
        android:title="Audio">

        <CheckBoxPreference
            android:key="pref_sonido"
            android:title="Sonido"
            android:summary="Activa o desactiva el audio"
            android:defaultValue="true"/>
    
        <ListPreference
            android:dependency="pref_sonido"
            android:key="pref_nivel"
            android:title="Volumen"
            android:summary="Establece el nivel de audio"
            android:dialogTitle="Volumen"
            android:entries="@array/pref_nivel_entradas"
            android:entryValues="@array/pref_nivel_valores"
            android:defaultValue="@string/pref_nivel_default"/>

    </PreferenceCategory>
    
</PreferenceScreen>
Si observamos el ejemplo, estamos creando un titulo a través del elemento <PreferenceCategory> que requiere de un titulo descriptivo y para ello utilizamos el atributo "android:key". Dentro de dicho elemento metemos nuestros dos ajustes del ejemplo anterior. El resultado es el siguiente:



2.1.2 Crear sub-pantallas

De igual manera que el ejemplo anterior podemos crear subpantallas enlazando elementos <PreferenceScreen>. Vamos a ver el ejemplo y lo comentamos a continuación:
<PreferenceScreen 
    xmlns:android="http://schemas.android.com/apk/res/android">

    <PreferenceScreen 
        android:title="Audio">

        <CheckBoxPreference
            android:key="pref_sonido"
            android:title="Sonido"
            android:summary="Activa o desactiva el audio"
            android:defaultValue="true"/>
    
        <ListPreference
            android:dependency="pref_sonido"
            android:key="pref_nivel"
            android:title="Volumen"
            android:summary="Establece el nivel de audio"
            android:dialogTitle="Volumen"
            android:entries="@array/pref_nivel_entradas"
            android:entryValues="@array/pref_nivel_valores"
            android:defaultValue="@string/pref_nivel_default"/>

    </PreferenceScreen>
    
</PreferenceScreen>
Igual que el ejemplo anterior pero en este caso utilizamos el elemento <PreferenceScreen>. El resultado sera una primera pantalla con el titulo "Audio" y una segunda pantalla con los dos ajustes.




2.2 Utilizar Intents

En ocasiones puede ser necesario abrir una determinada Activity o incluso enviar un email o enlazar nuestra web. Para ello crearemos un Intent de la siguiente manera:
<PreferenceScreen 
    xmlns:android="http://schemas.android.com/apk/res/android">
    
    <Preference 
        android:title="Pagina Web">
     <intent 
         android:action="android.intent.action.VIEW"   
         android:data="http://elbauldeandroid.blogspot.com.es/"/>
    </Preference>

</PreferenceScreen>

En este caso simplemente creamos el Intent que enlaza a nuestra pagina web.

Podremos utilizar los siguientes atributos para configurar el Intent:
  • android:action - establece la acción que tendrá el Intent, igual que el método "setAction()".
  • android:data - establece los datos del Intent de igual manera que el método "setData()".
  • android:mimeType - tipo de MIME, igual que el método "setType()".
  • android:targetClass - establece la clase, igual que el método "setComponent()".
  • android:targetPackage - establece el paquete, igual que el método "setComponent()".

El ejemplo tendrá la siguiente forma:




3. Crear PreferenceActivity

Es la forma recomendada si estamos desarrollando una aplicación con un nivel de API 10 o inferior. Para mostrar nuestro archivo de ajustes XML tenemos que crear una activity que extienda la clase "PreferenceActivity". Dicha clase se encarga de guardar automáticamente cualquier cambio que haga el usuario en los ajustes.

Una vez creada la Activity sobreescribimos el método "onCreate()" y añadimos dentro el método "addPreferencesFromResources()" indicando como parámetro el archivo de recursos XML con los ajustes. El siguiente ejemplo muestra como realizar la operación:
public class EjemploBasico extends PreferenceActivity {
    
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        addPreferencesFromResource(R.xml.ejemplo_basico);
    }
 
}
De esta manera y simplemente con esas lineas de código si ejecutamos la aplicación se cargara la Activity con nuestro archivo de recursos XML.



4. Crear PreferenceFragment

Este método es recomendable para un nivel de API 11 o superior. Hay que realizar dos pasos: primero crearemos el fragmento con el archivo de ajustes y después lo podremos añadir a cualquier actividad de nuestro proyecto. Vamos a ver como se crea el fragmento:
public class FragmentAudio extends PreferenceFragment {
    
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        addPreferencesFromResource(R.xml.ejemplo_basico);
    }
 
}
Si observamos el ejemplo, se crea de forma silimar que el ejemplo anterior pero en este caso estamos creando un fragmento que deberemos añadir a cualquier actividad de nuestro proyecto. A continuación vamos a ver como añadir dicho fragmento a una actividad:
public class UtilizarFragment extends Activity {
    
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        getFragmentManager().beginTransaction()
                .replace(android.R.id.content, new FragmentAudio())
                .commit();
    }
 
}
Simplemente utilizamos una instancia de "FragmentManager" a la que le aplicamos una nueva transacción con el método "beginTransaction()" indicando el fragmento a cargar mediante el método "replace()" y terminamos la operación con el método "commit()".

Si ejecutamos la aplicación con dicha Activity veremos como se carga el fragmento con el archivo de ajustes XML.



5. Utilizar Headers

En raras ocasiones puede ser necesario crear una configuración de ajustes con una primera pantalla que solamente contenga una lista de subpantallas (por ejemplo la aplicación Ajustes del sistema Android). A esta nueva característica se le denomina "headers" o encabezados. Para hacer uso de esta característica necesitamos crear la aplicación con un nivel de API 11 o superior.

Los pasos mas importantes para crear headers son los siguientes:
  • Separar cada grupo de ajustes mediante un PreferenceFragment y un archivo de recursos XML.
  • Crear un archivo XML que contenga todos los headers o encabezados de cada grupo de ajustes y enlazar cada uno con el fragmento que utilizara para mostrar el grupo de ajustes.
  • Extender la clase PreferenceActivity y cargar el archivo XML que contiene todos los headers a través del método "onBuildHeaders()".

Un gran beneficio al utilizar este diseño en un PreferenceActivity es que podemos presentar automáticamente una disposición de ajustes con dos paneles como muestra la imagen:



Para hacer uso de esta característica deberemos sobreescribir el método "onIsMultiPane()" en nuestra clase PreferenceActivity. Este método devuelve un valor booleano por lo que tendremos que comprobar si el tamaño de pantalla es large o xlarge.

Ahora vamos a ver como crear todo el sistema de headers.


5.1 Crear archivo XML Headers

El primer paso es crear el archivo XML con todos los headers o encabezados, vamos a ver el ejemplo y lo explicamos a continuación:
<preference-headers 
    xmlns:android="http://schemas.android.com/apk/res/android">
    
    <header 
        android:fragment="com.datohosting.settings.FragmentAudio"
        android:title="Audio"
        android:icon="@drawable/audio" />
    
    <header 
        android:fragment="com.datohosting.settings.FragmentHeaders"
        android:title="Pantalla"
        android:icon="@drawable/pantalla" >
        <extra android:name="key_ajustes" android:value="header_pantalla" />
    </header>
    
</preference-headers>
En este caso el elemento raiz debe ser <preference-headers> y los elementos hijos o encabezados utilizaran el elemento <header>. En el ejemplo solamente estamos creando dos encabezados.

Para cargar el fragmento tenemos dos posibilidades (cada encabezado del ejemplo lo carga de una manera):
  1. (Primer header) Indicar el fragmento directamente a traves del atributo "android:fragment". De esta manera tendríamos que crear un fragmento por cada grupo de ajustes.
  2. (Segundo header) Reutilizar un fragmento e incluir en el todos los archivos XML de cada grupo de ajustes. De esta manera reutilizamos un solo fragmento e indicamos que archivo de ajustes debe cargar mediante un elemento <extra>. El ejemplo esta basado en este diseño por ser el mas recomendado.

Por lo tanto si creamos dos headers como el segundo ejemplo vamos a ver como se carga el archivo de preferencias para cada grupo de ajustes.


5.2 Cargar cada grupo de ajustes

public class FragmentHeaders extends PreferenceFragment {
 
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        String settings = getArguments().getString("key_ajustes");
       
        if ("header_audio".equals(settings)) {
            addPreferencesFromResource(R.xml.ejemplo_basico);
        } else if ("header_pantalla".equals(settings)) {
            addPreferencesFromResource(R.xml.pantalla);
        }
    }
}
Lo primero que hacemos en el fragmento es recuperar el argumento extra. Equivale al atributo "android:name" del elemento <extra> anterior. Por lo tanto todos los header de este fragmento deberían llamarse igual.

Continuamos comprobando el valor de ese argumento extra. Equivale al atributo "android:value" del elemento <extra> anterior. En este caso cada header deberá llamarse de manera distinta para diferenciar cada grupo de ajustes.

Una vez hechas las comprobaciones cargamos el archivo de ajustes correspondiende a cada header.


5.3 Cargar archivo headers

Para concluir el punto de encabezados tenemos que crear la PreferenceActivity y cargar el archivo con los headers. Para ello utilizaremos el método "onBuildHeaders()" como muestra el siguiente ejemplo:
public class UtilizarHeaders extends PreferenceActivity {
 
    // Requiere un nivel de API 11 o superior en el manifest
    @Override
    public void onBuildHeaders(List<header> target) {
        loadHeadersFromResource(R.xml.headers, target);
    }
     
}

Si hemos configurado todo correctamente podremos obtener las siguientes imágenes:




Y como se comentaba en el punto 7 si configuramos el método "onIsMultiPane()" podemos obtener el siguiente resultado en pantallas grandes (tablets):




6. Soporte para diferentes versiones Android

En este punto vamos a ver como adaptar el punto anterior para versiones de Android 10 o inferior. Los pasos mas importantes son los siguientes:
  • Crear un diseño de ajustes para un nivel de API 10 o inferior.
  • Crear un diseño de ajustes para un nivel de API 11 o superior.
  • Crear una PreferenceActivity encargada de diferenciar las versiones Android y manejar todos los archivos de ajustes.

Para el nivel de API 11 o superior simplemente partimos del ejemplo anterior del punto 7. Por lo tanto ya tendríamos configurada una parte. Vamos a ver como adaptarlo a un nivel de API 10 o inferior.


6.1 Crear archivo de ajustes

Practicamente es el archivo headers para dar soporte a versiones antiguas:
<PreferenceScreen 
    xmlns:android="http://schemas.android.com/apk/res/android">
    
    <Preference 
        android:title="Audio"
        android:icon="@drawable/audio">
        <intent 
            android:targetPackage="com.datohosting.settings"
            android:targetClass="com.datohosting.settings.DiferentesVersiones"
            android:action="com.datohosting.settings.PREFS_ONE" />
    </Preference>
    
    <Preference 
        android:title="Pantalla"
        android:icon="@drawable/pantalla" >
        <intent 
            android:targetPackage="com.datohosting.settings"
            android:targetClass="com.datohosting.settings.DiferentesVersiones"
            android:action="com.datohosting.settings.PREFS_TWO" />
    </Preference>
    
</PreferenceScreen>
Cosas a tener en cuenta en este archivo:
  • Cada grupo de ajustes corresponde a un elemento <Preference>. En dicho elemento especificaremos en nombre del ajuste mediante el atributo "android:title".
  • Cada grupo debe declarar un Intent compartiendo los dos primeros atributos "android:targetPackage" y "android:targetClass". Que indican el paquete del proyecto y la clase que manejara las peticiones de este archivo de ajustes.
  • Para terminar un tercer atributo "android:action" que utilizaremos para diferenciar cada tipo de ajustes.

El siguiente paso seria crear un archivo XML por cada grupo de ajustes (a estas alturas debe estar bastante claro como crear un archivo de ajustes así que omitimos este paso y vamos a configurar el ultimo paso).


6.2 Configurar PreferenceActivity

Para concluir este punto tenemos que configurar la actividad que manejara todas la peticiones, vamos a ver el ejemplo y lo explicamos a continuación:
public class DiferentesVersiones extends PreferenceActivity {

    final static String ACTION_PREFS_AUDIO = "com.datohosting.settings.PREFS_ONE";
    final static String ACTION_PREFS_PANTALLA = "com.datohosting.settings.PREFS_TWO";
 
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
     
        if (action == null) {
            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
                addPreferencesFromResource(R.xml.api11menos);
            }
        } 

        String action = getIntent().getAction();
     
        if (action != null && action.equals(ACTION_PREFS_AUDIO)) {
            addPreferencesFromResource(R.xml.ejemplo_basico);
        } else if (action != null && action.equals(ACTION_PREFS_PANTALLA)) {
            addPreferencesFromResource(R.xml.pantalla);
        }
    }
 
    @Override
    public void onBuildHeaders(List<header> target) {
        loadHeadersFromResource(R.xml.api11mas, target);
    }
 
}
Prácticamente el método "onCreate()" sera el utilizado por el dispositivo si tiene un nivel de API 10 o inferior y el método "onBuildHeaders()" para un nivel de API 11 o superior. Comentar que se han creado dos constantes "ACTION_PREFS_AUDIO" y "ACTION_PREFS_PANTALLA" que utilizaremos para diferenciar cada grupo de ajustes.

En el onCreate comprobamos si es dispositivo tiene un nivel de API inferior al 11. En caso de que sea verdad carga el archivo "headers". Lo siguiente es configurar todos los grupos de ajustes, en este caso solo tenemos dos así que cargamos el archivo correspondiende para cada grupo de ajustes.

Deberemos tener en cuenta que cada llamada al método "addPreferencesFromResource()" se apila una a una mostrando todas las llamadas en pantalla. Esto quiere decir que si se llama dos veces a este método y en cada llamada carga un archivo de ajustes, nosotros en pantalla veremos los dos archivos de ajustes apilados. Para comprobarlo podéis eliminar el bloque "if (action == null) {}".

Si ejecutamos esta parte del código por ejemplo en una maquina virtual que corre con Android 2.3.3 obtendremos el siguiente resultado:





7. Leer Preferencias

Por defecto el sistema guarda todos los cambios de los ajustes en un archivo SharedPreferences y para acceder a ellos tenemos que utilizar una instancia "PreferenceManager" de la siguiente manera:
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);

Una vez creada la instancia podremos recuperar cualquier valor a través de los métodos:
  • getBoolean
  • getFloat
  • getInt
  • getLong
  • getString
  • getStringSet

El siguiente ejemplo muestra como recuperar un valor booleano:
boolean sonido = sharedPref.getBoolean("pref_sonido", true);



8. Utilizar un listener

En ocasiones puede ser necesario capturar los cambios en los ajustes por lo que el sistema Android nos permite crear un listener implementando la interface "OnSharedPreferenceChangeListener". De este modo cuando el usuario haga algún cambio en algún ajuste, automáticamente lo sabremos y podremos actuar en consecuencia.

Una vez implementada la interface tenemos que sobreescribir el método "registerOnSharedPreferenceListener()" en el que configuraremos los cambios en los ajustes.

Y para terminar tendremos que registrar y anular el listener en los métodos "onResume()" y "onPause()" respectivamente.

Este sistema se suele utilizar cuando el ajuste muestra un dialogo con opciones, entonces cuando el usuario selecciona una opción automáticamente aparece esa opción como descripción del ajuste.

Vamos a ver el ejemplo y lo explicamos a continuación:
public class CrearListener extends PreferenceActivity 
                    implements OnSharedPreferenceChangeListener {
 
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        addPreferencesFromResource(R.xml.ejemplo_basico);
    }
 
    @Override
    public void onSharedPreferenceChanged(SharedPreferences sharedPref, String key) {
        if (key.equals("pref_nivel")) {
            Preference connectionPref = findPreference(key);
            connectionPref.setSummary(sharedPref.getString(key, ""));
        }
    }
 
    @Override
    protected void onResume() {
        super.onResume();
        getPreferenceScreen().getSharedPreferences()
                .registerOnSharedPreferenceChangeListener(this);
    }
 
    @Override
    protected void onPause() {
        super.onPause();
        getPreferenceScreen().getSharedPreferences()
                .unregisterOnSharedPreferenceChangeListener(this);
    }
 
}
Vamos a centrarnos en el método "onSharedPreferenceChanged()" en el que comprobamos a través de un bloque if si el ajuste "pref_nivel" a sido modificado. En caso positivo se carga dicho ajuste en un objeto Preference a través del método "findPreference()". Y para terminar establecemos la descripción del ajuste a través del método "setSummary()".

El resultado sera el siguiente, si el usuario elige una opción automáticamente se establecerá esa opción como descripción del ajuste:




9. Ajustes por defecto

A partir de Android 4.0 desde la aplicación del sistema "Ajustes" podemos acceder directamente a los ajustes de una aplicación o juego desde el menú "Uso de datos". La siguiente imagen muestra el botón utilizado para acceder a los ajustes de una aplicación o juego:


Para habilitar dicho botón tenemos que editar el archivo manifest y dentro de nuestra PreferenceActivity declaramos el siguiente filtro:

<activity 
    android:name="com.datohosting.settings.Settings"
    android:label="Settings">
    <intent-filter>
        <action android:name="android.intent.action.MANAGE_NETWORK_USAGE" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</activity>

NOTA: En principio esta característica esta enfocada a controlar el uso de datos de una aplicación pudiendo restringir las conexiones automáticas desde el menú "Uso de datos" o entrando directamente en los ajustes de la aplicación configurando allí el uso de datos de la aplicación.



10. Preferencia personalizada

En algún momento podemos encontrarnos con que el sistema Android no nos ofrece por defecto preferencias que se ajusten a nuestras necesidades por lo que nos veremos obligados a crear un ajuste personalizado.

Los pasos mas importantes que deberemos seguir son los siguientes:
  • Especificar el diseño que aparecerá cuando el usuario seleccione el ajuste.
  • Guardar el valor del ajuste cuando sea apropiado.
  • Iniciar el ajuste o preferencia con el valor actual o predeterminado.
  • Proporcionar el valor por defecto cuando lo solicite el sistema
  • Guardar y restaurar los cambios de estado (por ejemplo si el usuario gira la pantalla).


10.1 Interface de usuario

En este punto tenemos dos posibilidades:
  1. Extender directamente la clase Preference e implementar el método "onClick()" para manejar las acciones que se producirán cuando el usuario seleccione un ajuste o preferencia.
  2. Extender la clase DialogPreference  llamando al método "setDialogLayoutResource()" dentro del constructor de la clase.

Vamos a explicar el segundo método por ser el mas utilizado a la hora de crear ajustes personalizados a parte de simplificar bastante mas el procedimiento. Por lo tanto primero tenemos que crear el archivo de recursos XML con el ajuste personalizado y también creamos un layout con lo que seria el dialogo y a continuación creamos la siguiente clase haciendo referencia a ese layout:
public class MyCustomPreference extends DialogPreference 
                                            implements OnSeekBarChangeListener {
 
    private SeekBar seekBar;
    private int valor_seekBar = 50;
 
    public MyCustomPreference(Context context, AttributeSet attrs) {
        super(context, attrs);
        
        setDialogLayoutResource(R.layout.dialog_custom_preference);
        setPositiveButtonText(android.R.string.ok);
        setNegativeButtonText(android.R.string.cancel);
    }

}
El dialogo del ejemplo consiste en un simple SeekBar que utilizaremos para establecer el nivel de sonido.

Implementamos la interface "OnSeekBarChangeListener" que utilizaremos para manejar el valor del SeekBar en todo momento. Se han creado dos variable que utilizaremos a lo largo del código de la clase. Y nos vamos a centrar en el constructor, llamamos al método "setDialogLayoutResource()" para establecer el layout del dialogo y añadimos dos botones al dialogo (aceptar y cancelar).


10.2 Guardar el valor del ajuste

Para guardar el valor del ajuste o preferencia utilizaremos el método "onDialogClosed()" de la clase DialogPreference. Y tendremos que guardar el valor siempre que el usuario seleccione el botón Aceptar.

Para guardar los distintos tipos de datos utilizaremos las siguientes métodos:
  • persistBoolean()
  • persistFloat()
  • persistInt()
  • persistLong()
  • persistString()

Por lo tanto continuamos con la clase del punto anterior y añadimos el método:
@Override
protected void onDialogClosed(boolean positiveResult) {
    if (positiveResult) {
        persistInt(valor_seekBar);
    } 
}
La variable "valor_seekBar" es la encargada de almacenar en todo momento el valor actual del SeekBar. Por lo tanto aplicando el método anterior, el valor del ajuste quedara guardado en el archivo SharedPreferences.


10.3 Iniciar valor predeterminado

En este punto vamos a establecer el valor inicial o predeterminado de nuestro ajuste personalizado. Para ello tenemos que sobreescribir el método "onSetInitialValue()" estableciendo dos resultados. El primer resultado recuperará el valor en caso de que haya sido guardado en algún momento en el archivo SharedPreferences, y el segundo resultado devolverá el valor por defecto.

Para recuperar los valores del archivo SharedPreferences utilizaremos los siguiente métodos:
  • getPersistedBoolean()
  • getPersistedFloat()
  • getPersistedInt()
  • getPersistedLong()
  • getPersistedString()

Por lo tanto vamos a crear el método e incluirlo en la clase que estamos creando:
@Override
protected void onSetInitialValue(boolean restorePersistedValue, Object defaultValue) {
    if (restorePersistedValue) {
        valor_seekBar = getPersistedInt(50);
    } else {
 valor_seekBar = (Integer) defaultValue;
 persistInt(valor_seekBar);
    }
}
De este modo nos devolverá el valor si lo encuentra en el archivo SharedPreferences, por el contrario nos devolverá el valor por defecto declarado en el archivo de recursos XML.


10.4 Proporcionar valor por defecto

Si en el archivo de recursos XML donde hemos creado el ajuste personalizado se a declarado el atributo "android:defaultValue" podemos sobreescribir el método "onGetDefaultValue()" que sera llamado cuando se cree la instancia de nuestro ajuste personalizado. Dicho método se encarga de guardar el valor predeterminado en el archivo SharedPreferences.

Por lo tanto vamos a ver el ejemplo de uso:
@Override
protected Object onGetDefaultValue(TypedArray a, int index) {
    return a.getInteger(index, 50);
}


10.5 Guardar y restaurar estado

Principalmente para guardar y restaurar el estado de nuestra aplicación (por ejemplo cuando el usuario gira la pantalla) tenemos que implementar los métodos "onSaveInstanceState()" y "onRestoreInstanceState()".

Por otro lado nuestro ajuste personalizado debe utilizar una clase que extienda de "BaseSavedState" agregando varios métodos y un objeto CREATOR.

Por lo tanto vamos a empezar con una clase genérica "BaseSavedState" que podríamos utilizar en cualquier ajuste personalizado teniendo en cuenta el tipo de datos que necesitemos guardar, en este caso manejamos un integer:
private static class SavedState extends BaseSavedState {

    int value;

    public SavedState(Parcelable superState) {
        super(superState);
    }

    public SavedState(Parcel source) {
        super(source);
        value = source.readInt();
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        super.writeToParcel(dest, flags);
        dest.writeInt(value);
    }

    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];
        }
    };

}

Y para terminar el punto deberemos sobreescribir los métodos "onSaveInstanceState()" y "onRestoreInstanceState()" en la clase de nuestro ajuste personalizado:
@Override
protected Parcelable onSaveInstanceState() {
    final Parcelable superState = super.onSaveInstanceState();
    if (isPersistent()) {
        return superState;
    }

    final SavedState myState = new SavedState(superState);
    myState.value = valor_seekBar;
    return myState;
}

@Override
protected void onRestoreInstanceState(Parcelable state) {
    if (state == null || !state.getClass().equals(SavedState.class)) {
        super.onRestoreInstanceState(state);
        return;
    }

    SavedState myState = (SavedState) state;
    super.onRestoreInstanceState(myState.getSuperState());
    
    seekBar.setProgress(myState.value);
}



11. Mini aplicación

Todo lo explicado en el articulo esta en forma de aplicación que podemos descargar en el siguiente enlace:

DESCARGAR: EJEMPLO SETTINGS


3 comentarios:

  1. Gracias por este estupendo trabajo Luis. Un eslabón mas en esta cadena ilimitada del aprender dia a dia. Felicitaciones

    ResponderEliminar
  2. Grandisimo trabajo! Enhorabuena y gracias por compartirlo con todos

    ResponderEliminar