page.title=Configurações page.tags=preferência,preferenceactivity,preferencefragment @jd:body
Geralmente os aplicativos contêm configurações que permitem aos usuários modificar características e comportamentos do aplicativo. Por exemplo: alguns aplicativos permitem aos usuários especificar se as notificações estão ativadas ou especificar a frequência com que o aplicativo sincroniza dados com a nuvem.
Para fornecer configurações ao aplicativo, é preciso usar as APIs {@link android.preference.Preference} do Android para programar uma interface coerente com a experiência do usuário em outros aplicativos Android (inclusive as configurações do sistema). Este documento descreve como programar as configurações do aplicativo por meio de APIs {@link android.preference.Preference}.
Projeto de configurações
Para obter mais informações sobre o projeto de configurações, leia o guia de projeto Configurações.
Em vez de usar objetos {@link android.view.View} para criar a interface do usuário, as configurações são criadas por meio de várias subclasses da classe {@link android.preference.Preference} declaradas em um arquivo XML.
Os objetos {@link android.preference.Preference} são as peças fundamentais de uma única configuração. Cada {@link android.preference.Preference} aparece como um item em uma lista e oferece a IU adequada para que os usuários modifiquem a configuração. Por exemplo: uma {@link android.preference.CheckBoxPreference} cria um item de lista que exibe uma caixa de seleção e uma {@link android.preference.ListPreference} cria um item que abre uma caixa de diálogo com uma lista de opções.
Cada {@link android.preference.Preference} adicionada tem um par de valor-chave correspondente que o sistema usa para salvar a configuração em um arquivo {@link android.content.SharedPreferences} padrão para as configurações do aplicativo. Quando o usuário altera uma configuração, o sistema atualiza o valor correspondente no arquivo {@link android.content.SharedPreferences}. O único momento em que se deve interagir diretamente com o arquivo {@link android.content.SharedPreferences} associado é no momento de ler o valor para determinar o comportamento do aplicativo com base na configuração do usuário.
O valor salvo em {@link android.content.SharedPreferences} para cada configuração pode ser um dos seguintes tipos de dados:
Como a IU de configurações do aplicativo é criada com objetos {@link android.preference.Preference} em vez de objetos {@link android.view.View}, é preciso usar uma subclasse {@link android.app.Activity} ou {@link android.app.Fragment} especializada para exibir as configurações de lista:
Veja como configurar a {@link android.preference.PreferenceActivity} e instâncias de {@link android.preference.PreferenceFragment} nas seções sobre a Criação de uma atividade de preferência e Uso de fragmentos de preferência.
Toda configuração do aplicativo é representada por uma subclasse específica da classe {@link android.preference.Preference}. Cada subclasse contém um conjunto de propriedades essenciais que permitem especificar itens como o título da configuração e o valor padrão. Cada subclasse também oferece suas propriedades e interface do usuário especializadas. Por exemplo: a figura 1 ilustra uma captura de tela das configurações do aplicativo Mensagens. Cada item de lista na tela de configurações tem, como fundo, um objeto {@link android.preference.Preference} diferente.
A seguir há algumas das preferências mais comuns:
true
se estiver selecionada).Consulte a classe {@link android.preference.Preference} para ver uma lista de todas as outras subclasses e as propriedades correspondentes.
É claro que as classes embutidas não acomodam todas as necessidades e o aplicativo pode exigir algo mais especializado. Por exemplo: a plataforma atualmente não fornece nenhuma classe {@link android.preference.Preference} para selecionar um número ou data. Portanto, pode ser necessário definir a própria subclasse {@link android.preference.Preference}. Veja mais informações na seção sobre Composição de uma preferência personalizada.
Embora se possa instanciar novos objetos {@link android.preference.Preference} em tempo de execução, deve-se definir uma lista de configurações no XML com uma hierarquia de objetos {@link android.preference.Preference}. Recomenda-se o uso de um arquivo XML para definir a coleção de configurações porque o arquivo oferece uma estrutura fácil de ler e simples de atualizar. Além disso, as configurações do aplicativo geralmente são predeterminadas, embora ainda seja possível modificar a coleção em tempo de execução.
Cada subclasse {@link android.preference.Preference} pode ser declarada com um elemento XML correspondente ao nome da classe, como {@code <CheckBoxPreference>}.
É preciso salvar o arquivo XML no diretório {@code res/xml/}. Embora seja possível nomear livremente o arquivo, é mais frequente vê-lo com o nome {@code preferences.xml}. Geralmente só é necessário um arquivo porque as ramificações na hierarquia (que abrem sua própria lista de configurações) são declaradas por meio de instâncias aninhadas de {@link android.preference.PreferenceScreen}.
Observação: se você deseja criar um layout de vários painéis para as configurações, serão necessários arquivos XML separados para cada fragmento.
O nó raiz do arquivo XML deve ser um elemento {@link android.preference.PreferenceScreen <PreferenceScreen>}. É dentro desse elemento que se adiciona cada {@link android.preference.Preference}. Cada filho adicionado dentro do elemento {@link android.preference.PreferenceScreen <PreferenceScreen>} é exibido com um item único na lista de configurações.
Por exemplo:
<?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>
Nesse exemplo, existe um {@link android.preference.CheckBoxPreference} e um {@link android.preference.ListPreference}. Os dois itens contêm estes três atributos:
As únicas instâncias em que esse atributo é dispensável ocorrem quando a preferência é um {@link android.preference.PreferenceCategory} ou {@link android.preference.PreferenceScreen}, ou quando a preferência especifica um {@link android.content.Intent} para invocar (com um elemento {@code <intent>}) ou um {@link android.app.Fragment} para exibir (com um atributo {@code android:fragment}).
Para mais informações sobre todos os outros atributos compatíveis, consulte a documentação {@link android.preference.Preference} (e subclasse respectiva).
Quando a lista de configurações excede cerca de 10 itens, pode ser necessário adicionar títulos para definir grupos de configurações ou exibir esses grupos em uma tela separada. Essas opções são descritas nas seções a seguir.
Se você apresentar uma lista de 10 ou mais configurações, os usuários podem ter dificuldade de percorrê-las, compreendê-las e processá-las. Para solucionar isso, pode-se dividir algumas ou todas as configurações em grupos, transformando uma longa lista em várias listas mais curtas. Um grupo de configurações relacionadas pode ser apresentado de uma das seguintes formas:
Pode-se usar uma ou ambas as técnicas de agrupamento para organizar as configurações do aplicativo. Ao decidir qual delas usar e como dividir as configurações, deve-se seguir as diretrizes do guia Configurações de Projeto do Android.
Para usar divisores com cabeçalhos entre grupos de configurações (como ilustrado na figura 2), coloca-se cada grupo de objetos {@link android.preference.Preference} dentro de {@link android.preference.PreferenceCategory}.
Por exemplo:
<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>
Para usar grupos de configurações em uma subtela (como ilustrado na figura 3), coloque o grupo de objetos {@link android.preference.Preference} dentro de {@link android.preference.PreferenceScreen}.
Por exemplo:
<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>
Em alguns casos, pode ser necessário que um item de preferência abra em um atividade diferente e não na tela de configuração, como um navegador da web para exibir uma página da web. Para invocar um {@link android.content.Intent} quando o usuário seleciona um item de preferência, adicione um elemento {@code <intent>} como filho do elemento {@code <Preference>} correspondente.
Por exemplo, a seguir apresenta-se como usar um item de preferência para abrir uma página da web:
<Preference android:title="@string/prefs_web_page" > <intent android:action="android.intent.action.VIEW" android:data="http://www.example.com" /> </Preference>
É possível criar intenções implícitas e explícitas usando os seguintes atributos:
Para exibir as configurações em uma atividade, estenda a classe {@link android.preference.PreferenceActivity}. É uma extensão da classe tradicional {@link android.app.Activity} que exibe uma lista de configurações com base em uma hierarquia de objetos {@link android.preference.Preference}. A {@link android.preference.PreferenceActivity} automaticamente persiste às configurações associadas a cada {@link android.preference.Preference} quando o usuário faz uma alteração.
Observação: ao desenvolver um aplicativo para Android 3.0 ou superior, deve-se usar o {@link android.preference.PreferenceFragment}. Consulte a próxima seção sobre o Uso de fragmentos de preferência.
O mais importante é não carregar um layout de vistas durante o retorno de chamada {@link android.preference.PreferenceActivity#onCreate onCreate()}. Em vez disso, chama-se {@link android.preference.PreferenceActivity#addPreferencesFromResource addPreferencesFromResource()} para adicionar à atividade as preferências declaradas em um arquivo XML. Por exemplo: abaixo há o código mínimo necessário para um {@link android.preference.PreferenceActivity} funcional:
public class SettingsActivity extends PreferenceActivity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); addPreferencesFromResource(R.xml.preferences); } }
Na verdade, esse código é suficiente para alguns aplicativos porque, assim que o usuário modifica uma preferência, o sistema salva as alterações em um arquivo padrão {@link android.content.SharedPreferences} que os componentes do outro aplicativo poderá ler quando for necessário verificar as configurações do usuário. No entanto, muitos aplicativos exigem um pouco mais de código para escutar as alterações que ocorrem nas preferências. Para informações sobre a escuda de alterações no arquivo {@link android.content.SharedPreferences}, consulte a seção sobre Leitura de preferências.
Ao desenvolver para Android 3.0 (nível da API 11) ou versões posteriores, deve-se usar um {@link android.preference.PreferenceFragment} para exibir a lista de objetos {@link android.preference.Preference}. Pode-se adicionar um {@link android.preference.PreferenceFragment} a qualquer atividade — não é necessário usar {@link android.preference.PreferenceActivity}.
Os fragmentos permitem uma arquitetura mais flexível para o aplicativo em comparação com o uso de apenas atividades para qualquer tipo de atividade criada. Assim, sugerimos usar {@link android.preference.PreferenceFragment} para controlar a exibição das configurações em vez de {@link android.preference.PreferenceActivity} sempre que possível.
A implementação de {@link android.preference.PreferenceFragment} pode ser tão simples quanto definir o método {@link android.preference.PreferenceFragment#onCreate onCreate()} para carregar um arquivo de preferências com {@link android.preference.PreferenceFragment#addPreferencesFromResource addPreferencesFromResource()}. Por exemplo:
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); } ... }
É possível adicionar esse fragmento a um {@link android.app.Activity} como se faria com qualquer outro {@link android.app.Fragment}. Por exemplo:
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(); } }
Observação: um {@link android.preference.PreferenceFragment} não tem seu próprio objeto {@link android.content.Context}. Se for necessário um objeto {@link android.content.Context} , é possível chamar {@link android.app.Fragment#getActivity()}. No entanto, tome cuidado para chamar {@link android.app.Fragment#getActivity()} somente quando o fragmento estiver anexado a uma atividade. Quando o fragmento ainda não estiver anexado, ou tiver sido separado durante o fim do seu ciclo de vida, {@link android.app.Fragment#getActivity()} retornará como nulo.
As preferências criadas provavelmente definem alguns comportamentos importantes do aplicativo, portanto é necessário inicializar o arquivo {@link android.content.SharedPreferences} associado com os valores padrão de cada {@link android.preference.Preference} quando o usuário abre o aplicativo pela primeira vez.
A primeira coisa a fazer é especificar o valor padrão de cada objeto {@link android.preference.Preference} no arquivo XML com o atributo {@code android:defaultValue}. O valor pode ser qualquer tipo de dados apropriado para o objeto {@link android.preference.Preference} correspondente. Por exemplo:
<!-- default value is a boolean --> <CheckBoxPreference android:defaultValue="true" ... /> <!-- default value is a string --> <ListPreference android:defaultValue="@string/pref_syncConnectionTypes_default" ... />
Em seguida, a partir do método {@link android.app.Activity#onCreate onCreate()} na atividade principal do aplicativo — e em qualquer outra atividade pela qual o usuário possa entrar no aplicativo pela primeira vez —, chame {@link android.preference.PreferenceManager#setDefaultValues setDefaultValues()}:
PreferenceManager.setDefaultValues(this, R.xml.advanced_preferences, false);
Essa chamada durante {@link android.app.Activity#onCreate onCreate()} garante que o aplicativo seja adequadamente inicializado com as configurações padrão, que o aplicativo pode precisar ler para determinar alguns comportamentos (se, por exemplo, baixará dados enquanto estiver em uma rede de celular).
Esse método usa três argumentos:
Quando false
, o sistema define os valores parão somente se esse método nunca tiver
sido chamado (ou se {@link android.preference.PreferenceManager#KEY_HAS_SET_DEFAULT_VALUES}
no arquivo de preferências compartilhadas do valor padrão for falso).
Enquanto o terceiro argumento estiver definido como false
, pode-se chamar esse método com segurança
toda vez que a atividade iniciar sem substituir as preferências salvas do usuário redefinindo-as
para os padrões. No entanto, se ele for definido como true
, todos os valores anteriores
serão substituídos pelos padrões.
Em casos raros, pode ser necessário projetar as configurações de forma que a primeira tela exiba somente uma lista de subtelas (como as do aplicativo Configurações conforme ilustrado nas figuras 4 e 5). Ao desenvolver um projeto desse tipo para Android 3.0 ou versão posterior, deve-se usar um novo recurso "cabeçalhos" no Android 3.0 em vez de criar subtelas com elementos {@link android.preference.PreferenceScreen} aninhados.
Para criar as configurações com cabeçalhos, é preciso:
Um grande benefício de usar esse modelo é que {@link android.preference.PreferenceActivity} automaticamente apresenta o layout de dois painéis ilustrado na figura 4 ao executar em telas grandes.
Mesmo se o aplicativo for compatível com versões de Android anteriores à 3.0, é possível programar o aplicativo para usar {@link android.preference.PreferenceFragment} para uma apresentação em dois painéis em dispositivos mais novos e ser compatível com a hierarquia tradicional multitelas em dispositivos mais antigos (veja a seção sobre Compatibilidade de versões mais antigas com cabeçalhos de preferência).
Cada grupo de configurações na lista de cabeçalhos é especificado por um único elemento {@code <header>} dentro de um elemento raiz {@code <preference-headers>}. Por exemplo:
<?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>
Com o atributo {@code android:fragment}, cada cabeçalho declara uma instância de {@link android.preference.PreferenceFragment} que deve abrir quando o usuário selecionar o cabeçalho.
O elemento {@code <extras>} permite passar os pares de valores-chave para o fragmento em um {@link android.os.Bundle}. O fragmento pode recuperar os argumentos chamando {@link android.app.Fragment#getArguments()}. Há vários motivos para passar argumentos ao fragmento, mas um bom motivo é reutilizar a mesma subclasse de {@link android.preference.PreferenceFragment} para cada grupo e usar o argumento para especificar o arquivo XML de preferências que o fragmento deve carregar.
Por exemplo, a seguir há um fragmento que pode ser reutilizado em vários grupos de configurações, quando cada cabeçalho define um argumento {@code <extra>} com a chave {@code "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); } } }
Para exibir os cabeçalhos de preferência, é preciso implementar o método de retorno de chamada {@link android.preference.PreferenceActivity#onBuildHeaders onBuildHeaders()} e chamar {@link android.preference.PreferenceActivity#loadHeadersFromResource loadHeadersFromResource()}. Por exemplo:
public class SettingsActivity extends PreferenceActivity { @Override public void onBuildHeaders(List<Header> target) { loadHeadersFromResource(R.xml.preference_headers, target); } }
Quando o usuário seleciona um item de uma lista de cabeçalhos, o sistema abre o {@link android.preference.PreferenceFragment} associado.
Observação: ao usar cabeçalhos de preferência, a subclasse de {@link android.preference.PreferenceActivity} não precisa implementar o método {@link android.preference.PreferenceActivity#onCreate onCreate()} porque a única tarefa necessária para a atividade é carregar os cabeçalhos.
Se o aplicativo for compatível com versões de Android anteriores à 3.0, ainda será possível usar cabeçalhos para fornecer um layout em dois painéis ao executar no Android 3.0 e versões posteriores. Basta criar um arquivo XML de preferências adicional que usa elementos básicos {@link android.preference.Preference <Preference>} que se comportam como os itens de cabeçalho (para uso das versões mais antigas do Android).
No entanto, em vez de abrir um novo {@link android.preference.PreferenceScreen}, cada elemento {@link android.preference.Preference <Preference>} envia um {@link android.content.Intent} ao {@link android.preference.PreferenceActivity} que especifica que arquivo XML de preferência carregar.
Por exemplo, abaixo há um arquivo XML de cabeçalhos de preferência usado no Android 3.0 e posterior ({@code 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>
E apresenta-se também um arquivo de preferências que fornece os mesmos cabeçalhos de versões de Android mais antigas que a 3.0 ({@code 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>
Como a compatibilidade com {@code <preference-headers>} foi adicionada no Android 3.0, o sistema chama {@link android.preference.PreferenceActivity#onBuildHeaders onBuildHeaders()} em seu {@link android.preference.PreferenceActivity} somente ao executar em Androd 3.0 ou posterior. Para carregar o arquivo de cabeçalhos de “legado" ({@code preference_headers_legacy.xml}), é preciso verificar a versãodo Android e, se a versão for mais antiga que o Android 3.0 ({@link android.os.Build.VERSION_CODES#HONEYCOMB}), chama {@link android.preference.PreferenceActivity#addPreferencesFromResource addPreferencesFromResource()} para carregar o arquivo de cabeçalho legado. Por exemplo:
@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); }
Depois só resta tratar o {@link android.content.Intent} passado para a atividade para identificar que arquivo de preferências carregar. Portanto, para recuperar a ação da intenção e compará-la com strings de ações conhecidas usadas nas tags de {@code <intent>} do XML de preferências:
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); } }
Observe que chamadas consecutivas a {@link android.preference.PreferenceActivity#addPreferencesFromResource addPreferencesFromResource()} empilharão todas as preferências em uma única lista, portanto certifique-se de que seja chamado somente uma vez, encadeando as condições com declarações else-if.
Por padrão, todas as preferências do aplicativo são salvas em um arquivo acessível de qualquer lugar dentro do aplicativo chamando o método estático {@link android.preference.PreferenceManager#getDefaultSharedPreferences PreferenceManager.getDefaultSharedPreferences()}. Isso retorna o objeto {@link android.content.SharedPreferences} que contém todos os pares de valores-chave associados aos objetos {@link android.preference.Preference} usados em {@link android.preference.PreferenceActivity}.
Por exemplo, abaixo apresenta-se como ler um dos valores de preferência de outra atividade no aplicativo:
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this); String syncConnPref = sharedPref.getString(SettingsActivity.KEY_PREF_SYNC_CONN, "");
Há alguns motivos pelos quais pode ser necessário ser notificado assim que o usuário altera uma das preferências. Para receber um retorno de chamada quando acontece uma alteração em alguma das preferências, implemente a interface {@link android.content.SharedPreferences.OnSharedPreferenceChangeListener SharedPreference.OnSharedPreferenceChangeListener} e registre a escuta para o objeto {@link android.content.SharedPreferences} chamando {@link android.content.SharedPreferences#registerOnSharedPreferenceChangeListener registerOnSharedPreferenceChangeListener()}.
A interface tem somente um método de retorno de chamada, {@link android.content.SharedPreferences.OnSharedPreferenceChangeListener#onSharedPreferenceChanged onSharedPreferenceChanged()}, e pode ser mais fácil implementar a interface como parte da atividade. Por exemplo:
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, "")); } } }
Nesse exemplo, o método verifica se a configuração alterada se destina a uma chave de preferência conhecida. Ele chama {@link android.preference.PreferenceActivity#findPreference findPreference()} para obter o objeto {@link android.preference.Preference} alterado para que possa modificar o sumário do item como uma descrição da seleção do usuário. Ou seja, quando a configuração for uma {@link android.preference.ListPreference} ou outra configuração de múltipla escolha, deve-se chamar {@link android.preference.Preference#setSummary setSummary()} quando a configuração for alterada para exibir o status atual (como a configuração Suspensão mostrada na figura 5).
Observação: conforme descrito no documento do Projeto para Android sobre Configurações, recomendamos atualizar o sumário de {@link android.preference.ListPreference} a cada vez que o usuário alterar a preferência para descrever a configuração atual.
Para um gerenciamento adequado do ciclo de vida na atividade, recomendamos registrar e remover o registro de {@link android.content.SharedPreferences.OnSharedPreferenceChangeListener} durante os retornos de chamada de {@link android.app.Activity#onResume} e {@link android.app.Activity#onPause} respectivamente:
@Override protected void onResume() { super.onResume(); getPreferenceScreen().getSharedPreferences() .registerOnSharedPreferenceChangeListener(this); } @Override protected void onPause() { super.onPause(); getPreferenceScreen().getSharedPreferences() .unregisterOnSharedPreferenceChangeListener(this); }
Atenção: ao chamar {@link android.content.SharedPreferences#registerOnSharedPreferenceChangeListener registerOnSharedPreferenceChangeListener()}, o gerenciador de preferências não armazena atualmente uma referência à escuta. É preciso armazenar uma referência forte à escuta, senão ela será suscetível à coleta de lixo. Recomendamos manter uma referência à escuta nos dados de instância de um objeto que existirá enquanto a escuta for necessária.
Por exemplo: no código a seguir, o autor da chamada não mantém nenhuma referência à escuta. Como resultado, a escuta estará sujeita à coleta de lixo e falhará futuramente em algum momento indeterminado:
prefs.registerOnSharedPreferenceChangeListener( // Bad! The listener is subject to garbage collection! new SharedPreferences.OnSharedPreferenceChangeListener() { public void onSharedPreferenceChanged(SharedPreferences prefs, String key) { // listener implementation } });
Em vez disso, armazene uma referência à escuta nos dados de instância de um objeto que existirá enquanto a escuta for necessária:
SharedPreferences.OnSharedPreferenceChangeListener listener = new SharedPreferences.OnSharedPreferenceChangeListener() { public void onSharedPreferenceChanged(SharedPreferences prefs, String key) { // listener implementation } }; prefs.registerOnSharedPreferenceChangeListener(listener);
A partir do Android 4.0, o aplicativo Configurações do sistema permite aos usuários ver o quanto de dados de rede que os aplicativos usam em primeiro e segundo plano. Portanto, os usuários podem desativar os dados em segundo plano de aplicativos individuais. Para evitar que os usuários desativem o acesso do aplicativo a dados em segundo plano, deve-se usar a conexão de dados de forma eficiente e permitir aos usuários refinar o uso de dados do aplicativo por meio das configurações do aplicativo.
Por exemplo: deve-se permitir ao usuário controlar a frequência de sincronização dos dados do aplicativo para uploads/downloads somente quando estiver em Wi-Fi, o aplicativo usar dados em deslocamento etc. Com esses controles disponíveis para eles, é bem menos provável que os usuários desativem o acesso do aplicativo a dados quando eles se aproximam dos limites que definem nas Configurações do sistema porque, em vez disso, podem controlar precisamente a quantidade de dados que o aplicativo usa.
Depois de adicionadas as preferências necessárias em {@link android.preference.PreferenceActivity} para controlar os hábitos de dados do aplicativo, deve-se adicionar um filtro de intenções para {@link android.content.Intent#ACTION_MANAGE_NETWORK_USAGE} no arquivo de manifesto. Por exemplo:
<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>
Esse filtro de intenções indica ao sistema que se trata da atividade que controla o uso de dados do aplicativo. Assim, quando o usuário inspeciona a quantidade de dados que o aplicativo está usando no aplicativo Configurações do sistema, um botão Exibir configurações de aplicativo fica disponível e inicia {@link android.preference.PreferenceActivity} para que o usuário refine a quantidade de dados que o aplicativo usa.
A estrutura do Android contém uma variedade de subclasses {@link android.preference.Preference} que permitem criar uma IU com diferentes tipos de configurações. No entanto, pode ser necessário descobrir uma configuração pra a qual não há nenhuma solução embutida, como um seletor de números ou seletor de datas. Nesse caso, será preciso criar uma preferência personalizada, estendendo a classe {@link android.preference.Preference} ou uma das outras subclasses.
Ao estender a classe {@link android.preference.Preference}, há algumas coisas importantes a fazer:
As seções a seguir descrevem como executar cada uma dessas tarefas.
Se a classe {@link android.preference.Preference} for estendida, será preciso implementar {@link android.preference.Preference#onClick()} para definir a ação que ocorre quando o usuário a seleciona. No entanto, a maioria das configurações personalizadas estendem {@link android.preference.DialogPreference} para exibir uma caixa de diálogo, o que simplifica o procedimento. Quando se estende {@link android.preference.DialogPreference}, é preciso chamar {@link android.preference.DialogPreference#setDialogLayoutResource setDialogLayoutResourcs()} na classe do construtor para especificar o layout da caixa de diálogo.
Por exemplo, eis o construtor de um {@link android.preference.DialogPreference} personalizado que declara o layout e especifica o texto dos botões padrão da caixa de diálogo positiva e negativa:
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); } ... }
Para salvar um valor da configuração a qualquer momento, chame um dos métodos {@code persist*()} da classe {@link android.preference.Preference}, como {@link android.preference.Preference#persistInt persistInt()} se o valor da configuração for um inteiro ou {@link android.preference.Preference#persistBoolean persistBoolean()} para salvar um booleano.
Observação: cada {@link android.preference.Preference} pode salvar somente um tipo de dados, portanto é preciso usar o método {@code persist*()} adequado para o tipo de dados usado pela {@link android.preference.Preference} personalizada.
Quando se opta por persistir, a configuração pode depender da classe {@link android.preference.Preference} estendida. Se {@link android.preference.DialogPreference} for estendida, deve-se persistir o valor somente quando a caixa de diálogo fecha devido a um resultado positivo (o usuário seleciona o botão "OK").
Quando uma {@link android.preference.DialogPreference} fecha, o sistema chama o método {@link
android.preference.DialogPreference#onDialogClosed onDialogClosed()}. O método contém um argumento
booleano que especifica se o resultado do usuário é "positivo" — se o valor é
true
e, em seguida, o usuário selecionou o botão positivo e você deve salvar o novo valor. Por
exemplo:
@Override protected void onDialogClosed(boolean positiveResult) { // When the user selects "OK", persist the new value if (positiveResult) { persistInt(mNewValue); } }
Nesse exemplo, mNewValue
é um membro da classe que retém o valor atual
da configuração. A chamada de {@link android.preference.Preference#persistInt persistInt()} salva o valor
no arquivo {@link android.content.SharedPreferences} (usando automaticamente a chave
especificada no arquivo XML dessa {@link android.preference.Preference}).
Quando o sistema adiciona o {@link android.preference.Preference} à tela, ele chama {@link android.preference.Preference#onSetInitialValue onSetInitialValue()} para notificar se a configuração tem um valor persistido. Se não houver valor persistido, essa chamada fornece o valor padrão.
O método {@link android.preference.Preference#onSetInitialValue onSetInitialValue()} passa
um booleano, restorePersistedValue
, para indicar se um valor já foi persistido
para a configuração. Se for true
, deve-se recuperar o valor persistindo chamando-se
um dos métodos {@code getPersisted*()} da classe {@link
android.preference.Preference}, como {@link
android.preference.Preference#getPersistedInt getPersistedInt()} para um valor inteiro. Geralmente
se recupera o valor persistido para atualizar adequadamente a IU de forma a refletir
o valor salvo anteriormente.
Se restorePersistedValue
for false
, deve-se
usar o valor padrão passado no 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 {@code getPersisted*()} pega um argumento que especifica o valor padrão a usar caso não haja nenhum valor persistido ou se a chave não existir. No exemplo acima, uma constante local é usada para especificar o valor padrão se {@link android.preference.Preference#getPersistedInt getPersistedInt()} não puder retornar um valor persistido.
Atenção: não é possível usar
defaultValue
como valor padrão no método {@code getPersisted*()} porque
seu valor é sempre nulo quando restorePersistedValue
é true
.
Se a instância da classe {@link android.preference.Preference} especificar um valor padrão (com o atributo {@code android:defaultValue}), o sistema chama {@link android.preference.Preference#onGetDefaultValue onGetDefaultValue()} quando instancia o objeto para recuperar o valor. É preciso implementar esse método para que o sistema salve o valor padrão em {@link android.content.SharedPreferences}. Por exemplo:
@Override protected Object onGetDefaultValue(TypedArray a, int index) { return a.getInteger(index, DEFAULT_VALUE); }
Os argumentos do método oferecem todo o necessário: a matriz de atributos e a posição do índice do {@code android:defaultValue}, que é preciso recuperar. É preciso implementar esse método para extrair o valor padrão do atributo porque deve-se especificar um valor padrão local para o atributo caso o valor seja indefinido.
Como um {@link android.view.View} em um layout, a subclasse {@link android.preference.Preference} é responsável por salvar e restaurar seu estado caso a atividade ou fragmento seja reiniciado (como ocorre quando o usuário gira a tela). Para salvar e restaurar adequadamente o estado da classe {@link android.preference.Preference}, é preciso implementar os métodos de retorno de chamada do ciclo de vida {@link android.preference.Preference#onSaveInstanceState onSaveInstanceState()} e {@link android.preference.Preference#onRestoreInstanceState onRestoreInstanceState()}.
O estado de {@link android.preference.Preference} é definido por um objeto que implementa a interface {@link android.os.Parcelable}. A estrutura do Android fornece esse objeto como um ponto inicial para definir o objeto de estado: a classe {@link android.preference.Preference.BaseSavedState}.
Para definir como a classe {@link android.preference.Preference} salva seu estado, deve-se estender a classe {@link android.preference.Preference.BaseSavedState}. É preciso substituir alguns métodos e definir o objeto {@link android.preference.Preference.BaseSavedState#CREATOR}.
Na maioria dos aplicativos, é possível copiar a implementação a seguir e simplesmente alterar as linhas que tratam o {@code value} se a subclasse {@link android.preference.Preference} salvar um tipo de dados que não seja um inteiro.
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]; } }; }
Com a implementação acima de {@link android.preference.Preference.BaseSavedState} adicionada ao aplicativo (geralmente como uma subclasse da subclasse {@link android.preference.Preference}), é preciso implementar os métodos {@link android.preference.Preference#onSaveInstanceState onSaveInstanceState()} e {@link android.preference.Preference#onRestoreInstanceState onRestoreInstanceState()} da subclasse {@link android.preference.Preference}.
Por exemplo:
@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); }