studio preferencias preferencia pantalla ejemplo editar create crear configuracion archivos android android-support-library appcompat preference-v7

preferencias - preferenceactivity android



Inference PreferenceScreen no se abre con PreferenceFragmentCompat (5)

Mi PreferenceScreen interno de PreferenceFragmentCompat no se muestra, o parece ignorar los eventos de tapping.

MyPreferenceFragment que extends PreferenceFragmentCompat

public class MyPreferenceFragment extends PreferenceFragmentCompat { @Override public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { addPreferencesFromResource(R.xml.preferences); } }

Luego cambié mi tema en styles.xml como

<style name="AppTheme" parent="@style/Theme.AppCompat.Light"> <item name="preferenceTheme">@style/PreferenceThemeOverlay</item> </style>

Y finalmente crear mi archivo preferences.xml como

<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"> <CheckBoxPreference android:title="Check Me"/> <PreferenceScreen android:title="My Screen"> <!-- This is not opening --> <EditTextPreference android:title="Edit text" /> </PreferenceScreen> </PreferenceScreen>

En el build.gradle he añadido ambos:

compile ''com.android.support:appcompat-v7:23.0.1'' compile ''com.android.support:preference-v7:23.0.1''

código de la Actividad

public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } }

activity_main.xml

<fragment xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/fragment" android:name="com.mando.preferenceapp.MyPreferenceFragment" android:layout_width="match_parent" android:layout_height="match_parent" />

Probando el código anterior no puedo abrir / entrar en la pantalla de preferencias. ¿Me estoy perdiendo de algo? ¿Por qué esto no está funcionando?


Basado en la solución @squirrel Intent, lo hice funcionar de esta manera. Requiere incluso menos piratería.
Actividad:

import android.support.v7.app.AppCompatActivity; public class SettingsActivity extends AppCompatActivity { public static final String TARGET_SETTING_PAGE = "target"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); SettingsFragment settingsFragment = new SettingsFragment(); Intent intent = getIntent(); if (intent != null) { String rootKey = intent.getStringExtra(TARGET_SETTING_PAGE); if (rootKey != null) { settingsFragment.setArguments(Bundler.single(TARGET_SETTING_PAGE, rootKey)); } } getFragmentManager().beginTransaction() .replace(android.R.id.content, settingsFragment) .commit(); } }

Fragmento:

import android.support.v14.preference.PreferenceFragment; public class SettingsFragment extends PreferenceFragment { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Bundle arguments = getArguments(); if (arguments != null && arguments.getString(TARGET_SETTING_PAGE) != null) { setPreferencesFromResource(R.xml.preferences, arguments.getString(TARGET_SETTING_PAGE)); } else { addPreferencesFromResource(R.xml.preferences); } } @Override public void onNavigateToScreen(PreferenceScreen preferenceScreen) { Intent intent = new Intent(getActivity(), SettingsActivity.class) .putExtra(TARGET_SETTING_PAGE, preferenceScreen.getKey()); startActivity(intent); super.onNavigateToScreen(preferenceScreen); } }

Es triste que necesites tantos trucos en las bibliotecas de compatibilidad de aplicaciones para algo que funcione sin problemas en Android estándar.


Después de pasar muchas horas con intentos, buscando y afortunadamente con la ayuda de los creadores de la biblioteca de soporte. Me las he arreglado para hacerlo funcionar.

Paso 1. Activity

public class MyActivity extends AppCompatActivity implements PreferenceFragmentCompat.OnPreferenceStartScreenCallback { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); if (savedInstanceState == null) { // Create the fragment only when the activity is created for the first time. // ie. not after orientation changes Fragment fragment = getSupportFragmentManager().findFragmentByTag(MyPreferenceFragment.FRAGMENT_TAG); if (fragment == null) { fragment = new MyPreferenceFragment(); } FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); ft.replace(R.id.fragment_container, fragment, MyPreferenceFragment.FRAGMENT_TAG); ft.commit(); } } @Override public boolean onPreferenceStartScreen(PreferenceFragmentCompat preferenceFragmentCompat, PreferenceScreen preferenceScreen) { FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); MyPreferenceFragment fragment = new MyPreferenceFragment(); Bundle args = new Bundle(); args.putString(PreferenceFragmentCompat.ARG_PREFERENCE_ROOT, preferenceScreen.getKey()); fragment.setArguments(args); ft.replace(R.id.fragment_container, fragment, preferenceScreen.getKey()); ft.addToBackStack(preferenceScreen.getKey()); ft.commit(); return true; } }

Consejos.

  • No agregue el fragmento por xml , tendrá bloqueos en los cambios de orientación.
  • Maneje las recreaciones de actividad / adición de fragmentos en onCreate para evitar perder su fragmento cuando se encuentre dentro de una pantalla de preferencias.
  • La actividad del host del fragmento debe implementar PreferenceFragmentCompat.OnPreferenceStartScreenCallback y volver a crear fragmentos de la misma instancia.

Paso 2. PreferenceFragment

public class MyPreferenceFragment extends PreferenceFragmentCompat { public static final String FRAGMENT_TAG = "my_preference_fragment"; public MyPreferenceFragment() { } @Override public void onCreatePreferences(Bundle bundle, String rootKey) { setPreferencesFromResource(R.xml.preferences, rootKey); } }

Consejos.

  • Utilice el método setPreferencesFromResource y aproveche la rootKey de cada pantalla. De esta manera su código será reutilizado correctamente.
  • Tenga en cuenta que si tiene un código como findPreference en su fragmento, debería tener controles null , ya que cuando estaba en pantallas internas, esto no le dará nada.

Lo que falta ahora es la implementación de la flecha hacia atrás en la barra de acción (acción de inicio), pero esto nunca funciona por sí solo ;-)

También creé una aplicación de demostración que envuelve todo este código, puedes encontrarlo en github .


La solución es iniciar otro fragmento de la misma clase pero con una clave de raíz diferente. No hay acciones de actividad involucradas.

@Override public void onCreatePreferences(Bundle savedInstanceState, String rootKey){ if(getArguments() != null){ String key = getArguments().getString("rootKey"); setPreferencesFromResource(R.xml.preferences, key); }else{ setPreferencesFromResource(R.xml.preferences, rootKey); } } @Override public void onNavigateToScreen(PreferenceScreen preferenceScreen){ ApplicationPreferencesFragment applicationPreferencesFragment = new ApplicationPreferencesFragment(); Bundle args = new Bundle(); args.putString("rootKey", preferenceScreen.getKey()); applicationPreferencesFragment.setArguments(args); getFragmentManager() .beginTransaction() .replace(getId(), applicationPreferencesFragment) .addToBackStack(null) .commit(); }


Lo hice de forma ligeramente diferente, estoy lanzando una nueva actividad para cada pantalla. Esto parece requerir menos hacks: no hay necesidad de meterse con el intercambio de fragmentos y colores de fondo. ¡También obtienes animación de cambio de actividad como bonus!

public class PreferencesActivity extends AppCompatActivity implements PreferenceFragmentCompat.OnPreferenceStartScreenCallback { final static private String KEY = "key"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.preferences); setSupportActionBar((Toolbar) findViewById(R.id.toolbar)); ActionBar actionBar = getSupportActionBar(); if (actionBar != null) actionBar.setDisplayHomeAsUpEnabled(true); if (savedInstanceState != null) return; Fragment p = new PreferencesFragment(); String key = getIntent().getStringExtra(KEY); if (key != null) { Bundle args = new Bundle(); args.putString(PreferenceFragmentCompat.ARG_PREFERENCE_ROOT, key); p.setArguments(args); } getSupportFragmentManager().beginTransaction() .add(R.id.preferences, p, null) .commit(); } @Override public boolean onPreferenceStartScreen(PreferenceFragmentCompat preferenceFragmentCompat, PreferenceScreen preferenceScreen) { Intent intent = new Intent(PreferencesActivity.this, PreferencesActivity.class); intent.putExtra(KEY, preferenceScreen.getKey()); startActivity(intent); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { if (item.getItemId() == android.R.id.home) { onBackPressed(); return true; } return super.onOptionsItemSelected(item); } public static class PreferencesFragment extends PreferenceFragmentCompat implements ... { private static final String FRAGMENT_DIALOG_TAG = "android.support.v7.preference.PreferenceFragment.DIALOG"; private String key; @Override public void onCreatePreferences(Bundle bundle, String key) { setPreferencesFromResource(R.xml.preferences, this.key = key); } // this only sets the title of the action bar @Override public void onActivityCreated(Bundle savedInstanceState) { ActionBar actionBar = ((AppCompatActivity) getActivity()).getSupportActionBar(); if (actionBar != null) actionBar.setTitle((key == null) ? "Settings" : findPreference(key).getTitle()); super.onActivityCreated(savedInstanceState); } } }

xml:

<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_margin="0dp" android:orientation="vertical" android:padding="0dp" android:id="@+id/preferences"> <android.support.v7.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" android:background="?attr/colorPrimary" /> <!-- preference fragment will be inserted here programmatically --> </LinearLayout>


Otra solución es hacer un seguimiento de las pantallas de preferencias y utilizar la API PreferenceFragmentCompat

Aquí está la solución básica. (No cubre todos los casos de borde, consulte la solución avanzada a continuación)

Asegúrate de que tienes configChanges = "orientación" para evitar crear / destruir

<activity android:name=".MyPreferencesActivity" android:configChanges="orientation" />

En la actividad, desea mantener una pila de pantallas de preferencia y push / pop según sea necesario

/* track the screens as a Stack */ private Stack<PreferenceScreen> preferenceScreens = new Stack<>(); // ensure your Activity implements PreferenceFragmentCompat.OnPreferenceStartScreenCallback @Override public boolean onPreferenceStartScreen(PreferenceFragmentCompat preferenceFragmentCompat, PreferenceScreen preferenceScreen) { preferenceScreens.push(preferenceFragmentCompat.getPreferenceScreen()); preferenceFragmentCompat.setPreferenceScreen(preferenceScreen); return true; } @Override public void onBackPressed() { if (preferenceScreens.empty()) { super.onBackPressed(); } else { prefsFragment.setPreferenceScreen(preferenceScreens.pop()); } }

Opcional: en su Fragmento que extiende PreferenceFragmentCompat, agregue setRetainInstance (true). (Tenga en cuenta que sin esto es probable que también funcione, pero que podría ''romperse de vez en cuando. Si establece'' No mantener Actividades ''en verdadero, y verá que se recopilará)

@Override public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { setRetainInstance(true); // Load the preferences from an XML resource setPreferencesFromResource(R.xml.preferences, rootKey); ...

¡Eso es! Excepto que si quieres cubrir casos de borde ...

Solución avanzada (si configura ''No mantener las actividades como verdaderas, deberá asegurarse de poder reconstruir todo desde el estado de instancia guardado)

Tenga en cuenta que la respuesta aceptada no conserva realmente el estado.

  1. establecer ''No mantener las actividades'' en True
  2. navegar a una pantalla de preferencia anidada
  3. Presiona home y luego regresa a la aplicación
  4. "Debería" seguir estando en la Pantalla de Preferencia Anidada, pero en realidad está en la raíz

Solución avanzada completa que utiliza la api de PreferenceFragmentCompat y preserva la pila PreferenceScreen

import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.support.v7.preference.PreferenceFragmentCompat; import android.support.v7.preference.PreferenceScreen; import java.util.ArrayList; import java.util.Objects; import java.util.Stack; /** * Class to Show the preference screen with Activity keeping state * @author Aaron Vargas */ public class MyPreferencesActivityStateful extends AppCompatActivity implements PreferenceFragmentCompat.OnPreferenceStartScreenCallback { private static final String PREFERENCE_SCREENS = "PREFERENCE_SCREENS"; private PrefsFragment prefsFragment; private Stack<PreferenceScreen> preferenceScreens = new Stack<>(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Display the fragment as the main content. Re-Use if possible String tag = PrefsFragment.class.getName(); prefsFragment = (PrefsFragment) getSupportFragmentManager().findFragmentByTag(tag); if (prefsFragment == null) prefsFragment = new PrefsFragment(); getSupportFragmentManager().beginTransaction().replace(android.R.id.content, prefsFragment, tag).commit(); } @Override protected void onRestoreInstanceState(Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); // rebuild preferenceScreen stack for (String screenKey : Objects.requireNonNull(savedInstanceState.getStringArrayList(PREFERENCE_SCREENS))) { preferenceScreens.push((PreferenceScreen) prefsFragment.findPreference(screenKey)); } PreferenceScreen preferenceScreen = preferenceScreens.pop(); if (preferenceScreen != prefsFragment.getPreferenceScreen()) { // optimize if same prefsFragment.setPreferenceScreen(preferenceScreen); } } @Override public boolean onPreferenceStartScreen(PreferenceFragmentCompat preferenceFragmentCompat, PreferenceScreen preferenceScreen) { preferenceScreens.push(preferenceFragmentCompat.getPreferenceScreen()); preferenceFragmentCompat.setPreferenceScreen(preferenceScreen); return true; } @Override public void onBackPressed() { // account for onRestore not getting called equally to onSave while (preferenceScreens.contains(prefsFragment.getPreferenceScreen())) { preferenceScreens.remove(prefsFragment.getPreferenceScreen()); } if (preferenceScreens.empty()) { super.onBackPressed(); } else { prefsFragment.setPreferenceScreen(preferenceScreens.pop()); } } @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); preferenceScreens.push(prefsFragment.getPreferenceScreen()); ArrayList<String> keys = new ArrayList<>(preferenceScreens.size()); for (PreferenceScreen screen : preferenceScreens) { keys.add(screen.getKey()); } outState.putStringArrayList(PREFERENCE_SCREENS, keys); } public static class PrefsFragment extends PreferenceFragmentCompat { @Override public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { setRetainInstance(true); // ensure in manifest - android:configChanges="orientation" // Load the preferences from an XML resource setPreferencesFromResource(R.xml.preferences, rootKey); } } }

También puede manejar todo esto en su Fragmento en lugar de la Actividad. Aquí hay un resumen de eso https://gist.github.com/aaronvargas/0f210ad8643b512efda4acfd524e1232