¿Lista vertical en el paisaje y la lista horizontal en retrato dentro de la misma actividad?
Resultado deseado
Quiero tener Lista vertical con elementos personalizados en el lado izquierdo / derecho de la pantalla en modo horizontal y Lista horizontal en el lado superior / inferior de la pantalla en modo vertical. La Lista Horizontal / Vertical debe ser Fragment
para poder reutilizarla más tarde para la versión de smartphone. La versión mínima de SDK es 13 (Android 3.2).
- Evitar la actualización de la actividad en el cambio de orientación del fragmento android
- ¿Cómo utilizo un servicio para supervisar el cambio de orientación en Android?
- Bloquear la orientación hasta que termine Asíntate
- ¿Tengo que usar el acelerómetro en Android para que mis fotos se orienten correctamente?
- Problemas al manejar los cambios de orientación
Mi intento
Mi Activity
personalizada tiene solo LayersFragment
personalizado y otra View
. En modo retrato, el fragmento está alineado con la izquierda de los padres. En el modo de paisaje está alineado con el fondo de los padres.
LayersFragment
también tiene un diseño diferente para el modo retrato y paisaje. En modo de retrato es Gallery
y en modo de paisaje es ListView
.
Dado que Gallery
y ListView
son subclases de AdapterView<Adapter>
Utilizo esta clase padre y BaseAdapter
para llenar elementos y escuchar OnItemClicks
.
Detalles del recurso
Frag_layers.xml – Diseño XML para LayersFragment
en paisaje.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <ListView android:id="@android:id/list" android:layout_width="match_parent" android:layout_height="match_parent" /> </LinearLayout>
Frag_layers.xml – Diseño XML para LayersFragment
en modo retrato.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <Gallery android:id="@android:id/list" android:layout_width="match_parent" android:layout_height="match_parent" /> </LinearLayout>
Activity
– Diseño XML para mi Activity
personalizada en modo retrato. Layout para el modo horizontal en lugar de android:layout_alignParentBottom
tiene android:layout_alignParentLeft
.
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <fragment android:id="@+id/photo_editor_layouts" class="rs.ailic.android.heritage.ui.LayersFragment" android:layout_width="match_parent" android:layout_height="@dimen/photo_editor_layouts_size" android:layout_alignParentBottom="true" /> <!-- Not relevant. --> </RelativeLayout>
Detalles del código
Clase LayersFragment .
public class LayersFragment extends Fragment implements OnItemClickListener { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { return inflater.inflate(R.layout.frag_layers, container, false); } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); mLayersAdapter = new LayersAdapter(); mLayersView = (AdapterView<Adapter>) getView().findViewById(android.R.id.list); mLayersView.setOnItemClickListener(this); mLayersView.setAdapter(mLayersAdapter); } @Override public void onItemClick(AdapterView<?> adapter, View view, int position, long id) { //Not implemented } private class LayersAdapter extends BaseAdapter { //Not implemented. Returning 0 in getCount(). } }
Mi actividad personalizada
public class PhotoEditorActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_photo_editor); } //Not relevant }
Problema
Estoy obteniendo este ClassCastException al girar de Paisaje a Retrato (ListView -> Galería)
Caused by: java.lang.ClassCastException: android.widget.AbsListView$SavedState cannot be cast to android.widget.AbsSpinner$SavedState at android.widget.AbsSpinner.onRestoreInstanceState(AbsSpinner.java:421) at android.view.View.dispatchRestoreInstanceState(View.java:8341) at android.view.ViewGroup.dispatchThawSelfOnly(ViewGroup.java:2038) at android.widget.AdapterView.dispatchRestoreInstanceState(AdapterView.java:766) at android.view.ViewGroup.dispatchRestoreInstanceState(ViewGroup.java:2024) at android.view.View.restoreHierarchyState(View.java:8320) at android.app.Fragment.restoreViewState(Fragment.java:583) at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:801) at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:977) at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:960) at android.app.FragmentManagerImpl.dispatchStart(FragmentManager.java:1679) at android.app.Activity.performStart(Activity.java:4413) at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1791) ... 12 more
Y éste al girar de Retrato a Paisaje (Galería -> ListView)
Caused by: java.lang.ClassCastException: android.widget.AbsSpinner$SavedState cannot be cast to android.widget.AbsListView$SavedState at android.widget.AbsListView.onRestoreInstanceState(AbsListView.java:1650) at android.view.View.dispatchRestoreInstanceState(View.java:8341) at android.view.ViewGroup.dispatchThawSelfOnly(ViewGroup.java:2038) at android.widget.AdapterView.dispatchRestoreInstanceState(AdapterView.java:766)
¿Cómo puedo solucionar este problema o debo buscar otra solución?
Mi opinión
El problema aparece cuando cambia la orientación de la pantalla. Creo que el problema está en 'implementación por defecto' de ListView
y Gallery
. Intentan restaurar su SavedState
en onRestoreInstanceState
después del cambio de orientación, pero la View
ha cambiado y se ha lanzado ClassCastException.
Gracias,
Aleksandar Ilić
- Apoyo a las dos orientaciones del paisaje en Honeycomb
- Android ListFragment no guarda el paquete en onSaveInstanceState () / no recupera el bundle en onActivityCreated ()
- Cambiar la orientación de la pantalla mediante un botón
- El diálogo emergente de Spinner no ajusta su ancho en la rotación de la pantalla
- Cordova ignora el bloqueo de la orientación de la pantalla
- ¿Cómo forzar un cambio de configuración en una prueba Android Robolectric?
- Emulador de panal invertido pantalla en retrato
- Cambio de orientación de la pantalla de la tableta: no se ha encontrado ninguna vista para id para Fragment
Suponiendo que el fragmento no se coloca en el backstack y la instancia de fragmento no se retiene (principalmente porque no sé el efecto tendrá), onCreateView se ejecutará cada vez que cambie la orientación. Por lo tanto, puede designar qué diseño utilizar en función de la orientación actual. También es crítico tener IDs diferentes para ListView y Galería.
Utilice getFirstVisiblePosition y setSelection para recordar la posición actual del adaptador. Esto sólo funcionará de manera fiable si las posiciones de datos en el adaptador no cambian cuando el fragmento no está en el estado de reanudación. Si los datos cambian, tendrás que volver a calcular la posición apropiada para establecerla en AdapterView.
Frag_layers.xml – Diseño XML para LayersFragment en paisaje.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <ListView android:id="@android:id/listview" android:layout_width="match_parent" android:layout_height="match_parent" /> </LinearLayout>
Frag_layers.xml – Diseño XML para LayersFragment en modo retrato.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <Gallery android:id="@android:id/gallery" android:layout_width="match_parent" android:layout_height="match_parent" /> </LinearLayout>
Clase LayersFragment .
public class LayersFragment extends Fragment implements OnItemClickListener { private AdapterView<Adapter> mLayersView; private LayersAdapter mLayersAdapter; private int mVisiblePosition = 0; @Override public void onPause() { super.onPause(); SharedPreferences prefs = getActivity().getPreferences(Context.MODE_PRIVATE); SharedPreferences.Editor editor = prefs.edit(); if (mLayersView != null) { editor.putInt("visiblePosition", mLayersView.getFirstVisiblePosition()); } else { editor.putInt("visiblePosition", 0); } editor.commit(); } @Override public void onResume() { super.onResume(); SharedPreferences prefs = getActivity().getPreferences(Context.MODE_PRIVATE); mVisiblePosition = prefs.getInt("visiblePosition", 0); // -- Set the position that was stored in onPause. mLayersView.setSelection(mVisiblePosition); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { ViewGroup rootView; rootView = (ViewGroup)inflater.inflate(R.layout.frag_layers, container, false); switch (getActivity().getResources().getConfiguration().orientation ) { case Configuration.ORIENTATION_LANDSCAPE: mLayersView = (AdapterView<Adapter>)rootView.findViewById(R.id.gallery); break; default: mLayersView = (AdapterView<Adapter>)rootView.findViewById(R.id.listView); break; } mLayersAdapter = new LayersAdapter(); mLayersView.setOnItemClickListener(this); mLayersView.setAdapter(mLayersAdapter); return rootView; } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); // -- Populate mLayersAdapter here or through a data ready listener registered with the activity? } @Override public void onItemClick(AdapterView<?> adapter, View view, int position, long id) { //Not implemented } private class LayersAdapter extends BaseAdapter { //Not implemented. Returning 0 in getCount(). }
No he probado este código, pero es una implementación similar que uso. Para mi implementación, quite el código en onResume y onPause excepto para las súper llamadas. Durante la onPause, en lugar de almacenar la primera posición visible en las preferencias, la almaceno directamente con mis datos que el fragmento puede utilizar más tarde cuando carga los datos en el adaptador. Esto también hace que sea relativamente sencillo calcular la nueva posición si se añaden datos antes o después de la posición actual. Cualquier actualización de datos es entonces relativamente sencilla. El fragmento aprende de la actualización a través de un listener, modifica el adaptador basado en los cambios, y establece la posición correcta ajustada de la AdapterView.
Dependiendo de cuántos cambios se realizan al adaptador al mismo tiempo, también puede utilizar Adapter.setNotifyOnChange (false) al crear un nuevo adaptador y utilizar Adapter.notifyDataSetChanged () después de las actualizaciones del adaptador. Esto evita que se notifiquen al AdapterView que los datos han cambiado hasta después de realizar todos los cambios.
Ese es un enfoque muy interesante. Pre-fragmentos, yo diría que usted tenía su trabajo cortado para usted, porque el tipo de código de espera naturalmente variante de archivos de diseño XML para tener los mismos tipos de controles con los mismos identificadores. Pero, como ustedes saben, con un Fragmento, todo lo que tienen que hacer es decir adónde va y vincularlo a una clase. No veo ninguna razón por la que no podría tener diferentes diseños (por ejemplo, retrato y paisaje) instanciar diferentes clases de fragmentos.
En lugar de intentar vincular vistas alternas a un BaseAdapter, ¿por qué no utilizar dos adaptadores adecuados y simplemente pasar la información relevante entre las diferentes vistas.
Algo como esto:
mListView.setPosition(mGallery.getFirstVisiblePosition());
Y viceversa. Es probable que necesite guardar esta información en onPause () ya que no sé si puede hacer referencia a la primera posición visible de una vista que ya no es visible.
Nota
Solución escrita a continuación es "solución de luz" y sólo evita ClassCastException
, no es final todavía. La sintonización fina es ciertamente necesaria. Dado que Java Reflection se utiliza y los nombres de los campos están codificados, es posible fallar en las plataformas donde los nombres se cambian o se implementan de manera diferente.
Actualizaré esta respuesta con más detalles tan pronto como termine de escribir mi solicitud.
Solución
Tienes que anular onRestoreInstanceState
en ListView
y Gallery
. En ambos tiene que hacer la conversión apropiada. En ListView
convierte dado Parceable
a AbsListView$SavedState
y en Gallery
a AbsSpinner$SavedSate
.
VerticalList – ListView
modificado
public class VerticalList extends ListView { public VerticalList(Context context) { super(context); } public VerticalList(Context context, AttributeSet attrs) { super(context, attrs); } public VerticalList(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } @Override public void onRestoreInstanceState(Parcelable state) { super.onRestoreInstanceState(SavedStateConversion.getAbsListViewSavedState(state)); } }
HorizontalList – Gallery
Modificada
public class HorizontalList extends Gallery { public HorizontalList(Context context) { super(context); } public HorizontalList(Context context, AttributeSet attrs) { super(context, attrs); } public HorizontalList(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } @Override public void onRestoreInstanceState(Parcelable state) { super.onRestoreInstanceState(SavedStateConversion.getAbsSpinnerSavedState(state)); } }
SavedStateConversion – AbsListView $ SavedState <-> AbsSpinner $ SavedState. La conversión se realiza mediante Java Reflection .
public class SavedStateConversion { private SavedStateConversion() {} /** * Converts <code>android.widget.AbsSpinner$SavedState</code> to <code>android.widget.AbsListView$SavedState</code>. * @param state parcelable representing <code>android.widget.AbsSpinnerSavedState</code> * @return parcelable representing <code>android.widget.AbsListView$SavedState</code> */ public static Parcelable getAbsListViewSavedState(Parcelable state) { try { Class<?> gss = Class.forName("android.widget.AbsSpinner$SavedState"); /* * List of all fields in AbsSpinner$SavedState: * * int position; * long selectedId; */ Field selectedIdField = gss.getDeclaredField("selectedId"); selectedIdField.setAccessible(true); Field positionField = gss.getDeclaredField("position"); positionField.setAccessible(true); Parcel parcel = Parcel.obtain(); parcel.writeLong(selectedIdField.getLong(state)); parcel.writeLong(0); parcel.writeInt(0); parcel.writeInt(positionField.getInt(state)); Class<?> lvss = Class.forName("android.widget.AbsListView$SavedState"); Constructor<?> constructors[] = lvss.getDeclaredConstructors(); Constructor<?> lvssConstructor = constructors[0]; lvssConstructor.setAccessible(true); return (Parcelable) lvssConstructor.newInstance(parcel); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } catch (SecurityException e) { e.printStackTrace(); } catch (NoSuchFieldException e) { e.printStackTrace(); } throw new RuntimeException("Conversion from AbsSpinner$SavedState to AbsListView$SavedState failed!"); } /** * Converts <code>android.widget.AbsListView$SavedState</code> to <code>android.widget.AbsSpinner$SavedState</code>. * @param state parcelable representing <code>android.widget.AbsListView$SavedState</code> * @return parcelable representing <code>android.widget.AbsSpinner$SavedState</code> */ public static Parcelable getAbsSpinnerSavedState(Parcelable state) { try { Class<?> lvss = Class.forName("android.widget.AbsListView$SavedState"); /* * List of all fields in AbsListView$SavedState: * * String filter; * long firstId; * int height; * int position; * long selectedId; * int viewTop; */ Field selectedIdField = lvss.getDeclaredField("selectedId"); selectedIdField.setAccessible(true); Field positionField = lvss.getDeclaredField("position"); positionField.setAccessible(true); Parcel parcel = Parcel.obtain(); parcel.writeLong(selectedIdField.getLong(state)); parcel.writeInt(positionField.getInt(state)); Class<?> gss = Class.forName("android.widget.AbsSpinner$SavedState"); Constructor<?> constructors[] = gss.getDeclaredConstructors(); Constructor<?> gssConstructor = constructors[0]; gssConstructor.setAccessible(true); return (Parcelable) gssConstructor.newInstance(parcel); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } catch (SecurityException e) { e.printStackTrace(); } catch (NoSuchFieldException e) { e.printStackTrace(); } throw new RuntimeException("Conversion from AbsListView$SavedState to AbsSpinner$SavedState failed!"); } }
Si está utilizando dos vistas de lista que son diferentes (una con expansible), asegúrese de que tienen id diferente en el diseño xml.
- ¿Cómo puedo escalar un personaje para que encaje en un Rect para dibujar texto?
- Más de un proveedor de ubicación al mismo tiempo