Tengo una aplicación que administra colecciones de libros (como listas de reproducción).
Quiero mostrar una lista de colección con un RecyclerView vertical y dentro de cada fila, una lista de libros en un RecyclerView horizontal.
Cuando configuro el layout_height del RecyclerView horizontal interno en 300dp, se muestra correctamente, pero cuando lo configuro en wrap_content, no muestra nada. Necesito usar wrap_content porque quiero poder cambiar el administrador de diseño mediante programación para cambiar entre visualización vertical y horizontal.
¿Sabes lo que estoy haciendo mal?
Diseño de mi fragmento:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white">
<com.twibit.ui.view.CustomSwipeToRefreshLayout
android:id="@+id/swipe_container"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.v7.widget.RecyclerView
android:id="@+id/shelf_collection_listview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="10dp"/>
</LinearLayout>
</com.twibit.ui.view.CustomSwipeToRefreshLayout>
</LinearLayout>
Diseño del elemento de colección:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#FFF">
<!-- Simple Header -->
</RelativeLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="@string/empty_collection"
android:id="@+id/empty_collection_tv"
android:visibility="gone"
android:gravity="center"/>
<android.support.v7.widget.RecyclerView
android:id="@+id/collection_book_listview"
android:layout_width="match_parent"
android:layout_height="wrap_content"/> <!-- android:layout_height="300dp" -->
</FrameLayout>
</LinearLayout>
Artículo de la lista de libros:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="180dp"
android:layout_height="220dp"
android:layout_gravity="center">
<ImageView
android:id="@+id/shelf_item_cover"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:maxWidth="150dp"
android:maxHeight="200dp"
android:src="@drawable/placeholder"
android:contentDescription="@string/cover"
android:adjustViewBounds="true"
android:background="@android:drawable/dialog_holo_light_frame"/>
</FrameLayout>
Aquí está mi adaptador de colección:
private class CollectionsListAdapter extends RecyclerView.Adapter<CollectionsListAdapter.ViewHolder> {
private final String TAG = CollectionsListAdapter.class.getSimpleName();
private Context mContext;
// Create the ViewHolder class to keep references to your views
class ViewHolder extends RecyclerView.ViewHolder {
private final TextView mHeaderTitleTextView;
private final TextView mHeaderCountTextView;
private final RecyclerView mHorizontalListView;
private final TextView mEmptyTextView;
public ViewHolder(View view) {
super(view);
mHeaderTitleTextView = (TextView) view.findViewById(R.id.collection_header_tv);
mHeaderCountTextView = (TextView) view.findViewById(R.id.collection_header_count_tv);
mHorizontalListView = (RecyclerView) view.findViewById(R.id.collection_book_listview);
mEmptyTextView = (TextView) view.findViewById(R.id.empty_collection_tv);
}
}
public CollectionsListAdapter(Context context) {
mContext = context;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int i) {
Log.d(TAG, "CollectionsListAdapter.onCreateViewHolder(" + parent.getId() + ", " + i + ")");
// Create a new view by inflating the row item xml.
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.shelf_collection, parent, false);
// Set the view to the ViewHolder
ViewHolder holder = new ViewHolder(v);
holder.mHorizontalListView.setHasFixedSize(false);
holder.mHorizontalListView.setHorizontalScrollBarEnabled(true);
// use a linear layout manager
LinearLayoutManager mLayoutManager = new LinearLayoutManager(mContext);
mLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
holder.mHorizontalListView.setLayoutManager(mLayoutManager);
return holder;
}
@Override
public void onBindViewHolder(ViewHolder holder, int i) {
Log.d(TAG, "CollectionsListAdapter.onBindViewHolder(" + holder.getPosition() + ", " + i + ")");
Collection collection = mCollectionList.get(i);
Log.d(TAG, "Collection : " + collection.getLabel());
holder.mHeaderTitleTextView.setText(collection.getLabel());
holder.mHeaderCountTextView.setText("" + collection.getBooks().size());
// Create an adapter if none exists
if (!mBookListAdapterMap.containsKey(collection.getCollectionId())) {
mBookListAdapterMap.put(collection.getCollectionId(), new BookListAdapter(getActivity(), collection));
}
holder.mHorizontalListView.setAdapter(mBookListAdapterMap.get(collection.getCollectionId()));
}
@Override
public int getItemCount() {
return mCollectionList.size();
}
}
Y finalmente, el adaptador de libro:
private class BookListAdapter extends RecyclerView.Adapter<BookListAdapter.ViewHolder> implements View.OnClickListener {
private final String TAG = BookListAdapter.class.getSimpleName();
// Create the ViewHolder class to keep references to your views
class ViewHolder extends RecyclerView.ViewHolder {
public ImageView mCoverImageView;
public ViewHolder(View view) {
super(view);
mCoverImageView = (ImageView) view.findViewById(R.id.shelf_item_cover);
}
}
@Override
public void onClick(View v) {
BookListAdapter.ViewHolder holder = (BookListAdapter.ViewHolder) v.getTag();
int position = holder.getPosition();
final Book book = mCollection.getBooks().get(position);
// Click on cover image
if (v.getId() == holder.mCoverImageView.getId()) {
downloadOrOpenBook(book);
return;
}
}
private void downloadOrOpenBook(final Book book) {
// do stuff
}
private Context mContext;
private Collection mCollection;
public BookListAdapter(Context context, Collection collection) {
Log.d(TAG, "BookListAdapter(" + context + ", " + collection + ")");
mCollection = collection;
mContext = context;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int i) {
Log.d(TAG, "onCreateViewHolder(" + parent.getId() + ", " + i + ")");
// Create a new view by inflating the row item xml.
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.shelf_grid_item, parent, false);
// Set the view to the ViewHolder
ViewHolder holder = new ViewHolder(v);
holder.mCoverImageView.setOnClickListener(BookListAdapter.this); // Download or Open
holder.mCoverImageView.setTag(holder);
return holder;
}
@Override
public void onBindViewHolder(ViewHolder holder, int i) {
Log.d(TAG, "onBindViewHolder(" + holder.getPosition() + ", " + i + ")");
Book book = mCollection.getBooks().get(i);
ImageView imageView = holder.mCoverImageView;
ImageLoader.getInstance().displayImage(book.getCoverUrl(), imageView);
}
@Override
public int getItemCount() {
return mCollection.getBooks().size();
}
}
Respuestas:
Actualizar
Muchos problemas relacionados con esta característica en la versión 23.2.0 se han solucionado en 23.2.1, actualícelo en su lugar.
Con el lanzamiento de la Biblioteca de soporte versión 23.2, ¡
RecyclerView
ahora es compatible!Actualizar
build.gradle
a:o cualquier versión más allá de eso.
Esto puede deshabilitarse
setAutoMeasurementEnabled()
si es necesario. Consulte en detalle aquí .fuente
23.2.1
de RecyclerView parece haber solucionado varios errores. Funciona muy bien para nosotros. Si no, incluso puedes probar con24.0.0-alpha1
La solución @ user2302510 no funciona tan bien como se esperaba. La solución completa para ambas orientaciones y cambios dinámicos de datos es:
fuente
El código anterior no funciona bien cuando necesita hacer que sus elementos sean "wrap_content", ya que mide la altura y el ancho de los elementos con MeasureSpec.UNSPECIFIED. Después de algunos problemas, modifiqué esa solución para que ahora los elementos puedan expandirse. La única diferencia es que proporciona la altura o el ancho de los padres. MeasureSpec depende de la orientación del diseño.
fuente
wrap_content
switch (heightMode) { case View.MeasureSpec.AT_MOST: if (height < heightSize) break; case View.MeasureSpec.EXACTLY: height = heightSize; case View.MeasureSpec.UNSPECIFIED: }
El administrador de diseño existente aún no admite contenido de ajuste.
Puede crear un nuevo LayoutManager que amplíe el existente y anule el método onMeasure para medir el contenido de ajuste.
fuente
setHasFixedSize(boolean)
inútil con los administradores de diseño predeterminados? De todos modos, es bueno saber que está en la hoja de ruta. Espero que salga pronto.Como @yiğit mencionó, debe anular onMeasure (). Tanto @ user2302510 como @DenisNek tienen buenas respuestas, pero si desea admitir ItemDecoration puede usar este administrador de diseño personalizado.
Y otras respuestas no pueden desplazarse cuando hay más elementos de los que se pueden mostrar en la pantalla. Este está usando la implementación predeterminada de onMeasure () cuando hay más elementos que el tamaño de la pantalla.
Y si desea usarlo con GridLayoutManager, simplemente extiéndalo desde GridLayoutManager y cambie
a
fuente
gridlayoutmanager
ystaggeredgridlayoutmanager
teniendo en cuenta el recuento de períodos en mente?ACTUALIZACIÓN Marzo 2016
Por la biblioteca de soporte de Android 23.2.1 de una versión de biblioteca de soporte. Por lo tanto, todo WRAP_CONTENT debería funcionar correctamente.
Actualice la versión de una biblioteca en el archivo gradle.
Esto permite que un RecyclerView se dimensione en función del tamaño de su contenido. Esto significa que los escenarios que antes no estaban disponibles, como el uso de WRAP_CONTENT para una dimensión de RecyclerView, ahora son posibles .
deberás llamar a setAutoMeasureEnabled (verdadero)
Se corrigieron errores relacionados con varios métodos de medida y especificación en la actualización
Consulte https://developer.android.com/topic/libraries/support-library/features.html
fuente
Esta respuesta se basa en la solución dada por Denis Nek. Resuelve el problema de no tener en cuenta decoraciones como separadores.
}
fuente
Una alternativa para extender LayoutManager puede ser simplemente establecer el tamaño de la vista manualmente.
Número de elementos por altura de fila (si todos los elementos tienen la misma altura y el separador está incluido en la fila)
Todavía es una solución, pero para casos básicos funciona.
fuente
Aquí he encontrado una solución: https://code.google.com/p/android/issues/detail?id=74772
De ninguna manera es mi solución. Acabo de copiarlo desde allí, pero espero que ayude a alguien tanto como a mí cuando implementé RecyclerView horizontal y la altura wrap_content (también debería funcionar para el vertical y el ancho wrap_content)
La solución es extender el LayoutManager y anular su método onMeasure como sugirió @yigit.
Aquí está el código en caso de que el enlace muera:
fuente
Solución utilizada de @ sinan-kozak, excepto que se corrigieron algunos errores. En concreto, no hay que usar
View.MeasureSpec.UNSPECIFIED
para tanto la anchura y la altura al llamarmeasureScrapChild
como que no va a tener debidamente en cuenta el texto ajustado en el niño. En su lugar, pasaremos por los modos de ancho y alto desde el padre, lo que permitirá que las cosas funcionen para diseños horizontales y verticales.``
fuente
RecyclerView
está anidado en aLinearLayout
. No funciona conRelativeLayout
.Sí, la solución que se muestra en todas las respuestas es correcta, es decir, necesitamos personalizar el administrador de diseño lineal para calcular la altura de sus elementos secundarios dinámicamente en tiempo de ejecución. Pero todas las respuestas no funcionan como se esperaba. Por favor, responda a continuación para el administrador de diseño personalizado con todo el soporte de orientación.
fuente
Android Support Library ahora también maneja la propiedad WRAP_CONTENT. Solo importa esto en tu gradle.
¡Y hecho!
fuente
Basado en el trabajo de Denis Nek, funciona bien si la suma de los anchos del artículo es menor que el tamaño del contenedor. aparte de eso, hará que la vista del reciclador no sea desplazable y solo mostrará un subconjunto de datos.
Para resolver este problema, modifiqué un poco la solución para que elija el mínimo del tamaño proporcionado y el tamaño calculado. vea abajo:
fuente
He probado todas las soluciones, son muy útiles, pero esto solo funciona bien para mí.
fuente
Simplemente envuelva el contenido usando RecyclerView con el diseño de cuadrícula
Imagen: Reciclador como diseño GridView
Simplemente use el GridLayoutManager de esta manera:
Puede establecer cuántos elementos deben aparecer en una fila (reemplace los 3).
fuente