ejemplos - personalizar jtabbedpane java
Problemas con las pantallas de preferencias de doble panel (2)
Problema
Al girar un dispositivo desde una PreferenceScreen
de un panel a una PreferenceScreen
horizontal de dos paneles, el paisaje solo se muestra como un panel. NO ocurre cuando se ve la pantalla de encabezados.
Preparar
Esto es para ICS y solo arriba. Tengo una PreferenceActivity
que carga preference-headers
. Cada encabezado se vincula con un Fragment
, que a su vez carga una PreferenceScreen
. Bonita carrera de mil.
Detalles
Todo funcionó bien hasta que noté que Android solo cambiaría automáticamente a una apariencia de dos paneles para ciertas pantallas. Después de algunas investigaciones, aprendí de una publicación de Commonsware que Android solo lo hará para sw720dp. Un poco desperdiciado si me preguntas, ya que muchos dispositivos definitivamente tienen mucho espacio para dos paneles. Así que onIsMultiPane()
método onIsMultiPane()
para que sea verdadero para w600dp y superior. Funcionó como un encanto ... un poco.
Dado un dispositivo que mostrará un solo panel en vertical y doble panel en el paisaje; ver los encabezados en vertical y rotar a horizontal, funciona bien. Sin embargo, si uno selecciona un encabezado y carga su pantalla posterior en modo retrato, luego gira para que el dispositivo permanezca en un solo panel en lugar de volver a cambiar a doble panel. Si luego navega hacia atrás a la pantalla de encabezados, volverá a tener un aspecto de panel doble, excepto que no preseleccionará un encabezado. Como resultado, el panel detallado permanece en blanco.
¿Es este comportamiento intencionado? De todos modos para evitarlo? Traté de sobreescribir en onIsHidingHeaders()
también, pero eso solo hizo que todo mostrara una pantalla en blanco.
Código
Actividad de preferencia:
public class SettingsActivity extends PreferenceActivity {
@Override
public void onBuildHeaders(List<Header> target) {
super.onBuildHeaders(target);
loadHeadersFromResource(R.xml.preference, target);
}
@Override
public boolean onIsMultiPane() {
return getResources().getBoolean(R.bool.pref_prefer_dual_pane);
}
}
Un Fragmento de encabezado de preferencia:
public class ExpansionsFragment extends PreferenceFragment {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.pref_expansions);
}
public static ExpansionsFragment newInstance() {
ExpansionsFragment frag = new ExpansionsFragment();
return frag;
}
}
La idea clave detrás del siguiente código proviene de la entrada del blog de Commonsware vinculada en la pregunta, por lo que se siente relevante. Específicamente, tuve que extender el concepto para tratar un problema de cambio de orientación que suena muy similar al de la pregunta, así que espero que sea un buen comienzo.
La clase de configuración no debe tener ninguna relación con el problema de orientación, pero incluirlo de todos modos para que quede claro.
De acuerdo con mi comentario sobre el código, vea si la llamada a onCreate
en onCreate
ayudará en lo absoluto:
public class SettingsActivity
extends
PreferenceActivity
{
@SuppressWarnings("deprecation")
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Show settings without headers for single pane or pre-Honeycomb. Make sure to check the
// single pane or pre-Honeycomb condition again after orientation change.
if (checkNeedsResource()) {
MyApp app = (MyApp)getApplication();
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(app);
Settings settings = new Settings();
addPreferencesFromResource(R.xml.prefs_api);
settings.setupPreference(findPreference(MyApp.KEY_USERNAME), prefs.getString(MyApp.KEY_USERNAME, null), true);
settings.setupPreference(findPreference(MyApp.KEY_API_URL_ROOT), prefs.getString(MyApp.KEY_API_URL_ROOT, null), true);
if (this.isHoneycomb) {
// Do not delete this. We may yet have settings that only apply to Honeycomb or higher.
//addPreferencesFromResource(R.xml.prefs_general);
}
addPreferencesFromResource(R.xml.prefs_about);
settings.setupPreference(findPreference(MyApp.KEY_VERSION_NAME), app.getVersionName());
}
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
@Override
public void onBuildHeaders(List<Header> target) {
super.onBuildHeaders(target);
// This check will enable showing settings without headers for single pane or pre-Honeycomb.
if (!checkNeedsResource()) {
loadHeadersFromResource(R.xml.pref_headers, target);
}
}
private boolean checkNeedsResource() {
// This check will enable showing settings without headers for single pane or pre-Honeycomb.
return (!this.isHoneycomb || onIsHidingHeaders() || !onIsMultiPane());
}
private boolean isHoneycomb = (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB);
}
public class Settings {
public Settings() {
}
public void setupPreference(Preference pref, String summary, boolean setChangeListener) {
if (pref != null) {
if (summary != null) {
pref.setSummary(summary);
}
pref.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference pref, Object newValue) {
pref.setSummary(newValue.toString());
return true;
}
});
}
}
public void setupPreference(Preference pref, String summary) {
setupPreference(pref, summary, false);
}
}
Problema resuelto
Con lo popular que se ha vuelto esta pregunta, decidí volver a visitar este tema y ver si podía encontrar una resolución ... y lo hice. Encontré un pequeño y agradable trabajo que resuelve el panel único que muestra en lugar del panel doble y asegura que un encabezado siempre esté preseleccionado cuando se encuentre en el modo de panel doble.
Si no le interesa una explicación, puede pasar directamente al código. Si no te importa ICS, se puede eliminar una gran parte del código de seguimiento del encabezado ya que JB agregó un getter para la lista de la matriz de encabezados.
Problema de doble panel
Al ver la lista de encabezado de preferencia en modo de panel único o modo de panel doble, solo se crea una PreferenceActivity y es la misma actividad para ambos casos. Como resultado, nunca hay un problema al manejar rotaciones de pantalla que cambiarán el modo de panel.
Sin embargo, en el modo de panel único al hacer clic en un encabezado, el fragmento correspondiente se adjunta a una NUEVA actividad de preferencia. Este nuevo fragmento que contiene PreferenceActivity nunca invoca onBuildHeaders()
. ¿Y por qué? No necesita mostrarlos. Esto explica el problema.
Al girar ese fragmento en un modo de panel doble, no tiene ninguna lista de encabezado para mostrar, por lo que solo sigue mostrando el fragmento. Incluso si mostrara la lista del encabezado, tendrá algunos problemas de backstack, ya que ahora tendría dos copias de la actividad de preferencia que muestra los encabezados. Sigue haciendo clic en suficientes encabezados y obtendrás una gran cantidad de actividades para que el usuario pueda volver a navegar. Como resultado, la respuesta es simple. Simplemente finish()
la actividad. A continuación, cargará la actividad de preferencia original que tiene la lista de encabezado y mostrará correctamente el modo de panel doble.
Selección automática de encabezado
El siguiente problema que era necesario abordar era que el cambio entre el modo de panel simple a doble con la nueva solución no seleccionaba automáticamente un encabezado. Te dejaron una lista de encabezados y ningún fragmento de detalles cargado. Esta solución no es tan simple. Básicamente, solo tiene que hacer un seguimiento de qué encabezado se hizo clic por última vez y asegurarse de que durante la creación de PreferenceActivity ... siempre se seleccione un encabezado.
Esto termina siendo un poco molesto en ICS ya que la API no expone un captador para la lista de encabezados rastreados internamente. Android ya persiste en esa lista y puede recuperarla técnicamente utilizando la misma clave de cadena interna almacenada de forma privada, pero eso es solo una mala elección de diseño. En cambio, sugiero persistir manualmente de nuevo usted mismo.
Si no te importa ICS, entonces puedes usar el método getHeaders()
expuesto en JB y no preocuparte por nada de esto guardado / restaurar.
Código
public class SettingsActivity extends PreferenceActivity {
private static final String STATE_CUR_HEADER_POS = "Current Position";
private static final String STATE_HEADERS_LIST = "Headers List";
private int mCurPos = AdapterView.INVALID_POSITION; //Manually track selected header position for dual pane mode
private ArrayList<Header> mHeaders; //Manually track headers so we can select one. Required to support ICS. Otherwise JB exposes a getter instead.
@Override
public void onBuildHeaders(List<Header> target) {
loadHeadersFromResource(R.xml.preference, target);
mHeaders = (ArrayList<Header>) target; //Grab a ref of the headers list
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//This is the only code required for ensuring a dual pane mode shows after rotation of a single paned preference screen
if (onIsMultiPane() && onIsHidingHeaders()) {
finish();
}
}
@Override
public boolean onIsMultiPane() {
//Override this if you want dual pane to show up on smaller screens
return getResources().getBoolean(R.bool.pref_prefer_dual_pane);
}
@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
super.onListItemClick(l, v, position, id);
//Intercept a header click event to record its position.
mCurPos = position;
}
@Override
protected void onRestoreInstanceState(Bundle state) {
super.onRestoreInstanceState(state);
//Retrieve our saved header list and last clicked position and ensure we switch to the proper header.
mHeaders = state.getParcelableArrayList(STATE_HEADERS_LIST);
mCurPos = state.getInt(STATE_CUR_HEADER_POS);
if (mHeaders != null) {
if (mCurPos != AdapterView.INVALID_POSITION) {
switchToHeader(mHeaders.get(mCurPos));
} else {
switchToHeader(onGetInitialHeader());
}
}
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
//Persist our list and last clicked position
if (mHeaders != null && mHeaders.size() > 0) {
outState.putInt(STATE_CUR_HEADER_POS, mCurPos);
outState.putParcelableArrayList(STATE_HEADERS_LIST, mHeaders);
}
}
}