¿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).

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 .

PhotoEditorActivity Modo de retratoPhotoEditorActivity Modo de paisaje

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ć

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 .

VerticalListListView 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)); } } 

HorizontalListGallery 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.

  • Actividad sólo en retrato o en reverso
  • Android: Guardar el estado de la aplicación en el cambio de orientación de la pantalla
  • Rotación del videobuffer real en Videoview
  • Cómo arreglar la orientación de la pantalla en vertical para toda mi aplicación PhoneGap
  • Fragmento manejo de la orientación de la pantalla con pestañas en barra de acción
  • Después de que los botones de cambio de orientación en un widget no responden
  • Establecer nuevo fragmento en la parte superior de la actividad no funciona con onConfiguration change
  • Android: Detectar la orientación cambiada
  • Cómo manejar los cambios de orientación de la pantalla cuando hay una asyntax ejecutando con android 4.x
  • Fragmentos anidados pierden llamadas a onCreateOptionsMenu después de rotación de pantalla
  • Problemas al manejar los cambios de orientación
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.