RecyclerView GridLayoutManager: cómo detectar el recuento de span?

Uso del nuevo GridLayoutManager: https://developer.android.com/reference/android/support/v7/widget/GridLayoutManager.html

Se necesita un recuento de span explícito, por lo que el problema ahora se convierte en: ¿cómo saber cuántos "spans" caben por fila? Esta es una rejilla, después de todo. Debe haber tantos tramos como el RecyclerView puede ajustar, basado en el ancho medido.

Usando el viejo GridView, usted apenas fijaría la característica "columnWidth" y auto-detectaría cuántas columnas caben. Esto es básicamente lo que voy a replicar para RecyclerView:

  • Agregue OnLayoutChangeListener en el RecyclerView
  • En esta devolución de llamada, inflar un solo "elemento de la cuadrícula" y medirlo
  • SpanCount = recyclerViewWidth / singleItemWidth;

Esto parece como un comportamiento bastante común, así que hay una manera más simple que no estoy viendo?

Personalmente no me gusta subclasificar RecyclerView para esto, porque para mí parece que hay la responsabilidad de GridLayoutManager para detectar el recuento de span. Así que después de algún código fuente android de excavación de RecyclerView y GridLayoutManager escribí mi propia clase extendida GridLayoutManager que hacer el trabajo:

public class GridAutofitLayoutManager extends GridLayoutManager { private int mColumnWidth; private boolean mColumnWidthChanged = true; public GridAutofitLayoutManager(Context context, int columnWidth) { /* Initially set spanCount to 1, will be changed automatically later. */ super(context, 1); setColumnWidth(checkedColumnWidth(context, columnWidth)); } public GridAutofitLayoutManager(Context context, int columnWidth, int orientation, boolean reverseLayout) { /* Initially set spanCount to 1, will be changed automatically later. */ super(context, 1, orientation, reverseLayout); setColumnWidth(checkedColumnWidth(context, columnWidth)); } private int checkedColumnWidth(Context context, int columnWidth) { if (columnWidth <= 0) { /* Set default columnWidth value (48dp here). It is better to move this constant to static constant on top, but we need context to convert it to dp, so can't really do so. */ columnWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 48, context.getResources().getDisplayMetrics()); } return columnWidth; } public void setColumnWidth(int newColumnWidth) { if (newColumnWidth > 0 && newColumnWidth != mColumnWidth) { mColumnWidth = newColumnWidth; mColumnWidthChanged = true; } } @Override public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) { int width = getWidth(); int height = getHeight(); if (mColumnWidthChanged && mColumnWidth > 0 && width > 0 && height > 0) { int totalSpace; if (getOrientation() == VERTICAL) { totalSpace = width - getPaddingRight() - getPaddingLeft(); } else { totalSpace = height - getPaddingTop() - getPaddingBottom(); } int spanCount = Math.max(1, totalSpace / mColumnWidth); setSpanCount(spanCount); mColumnWidthChanged = false; } super.onLayoutChildren(recycler, state); } } 

En realidad no recuerdo por qué elegí establecer la cuenta de span en onLayoutChildren, escribí esta clase hace algún tiempo. Pero el punto es que tenemos que hacerlo después de que la vista se mida. Así que podemos conseguirlo es altura y anchura.

EDIT: Corregir el error en el código causado a la cuenta incorrecta de span span. Gracias al usuario @Elyees Abouda por informar y sugerir soluciones .

Logré esto usando un observador de árbol de vista para obtener el ancho de la recylcerview una vez procesado y luego obtener las dimensiones fijas de mi vista de tarjeta de recursos y luego establecer el recuento de span después de hacer mis cálculos. Sólo es realmente aplicable si los elementos que se muestran son de un ancho fijo. Esto me ayudó a llenar automáticamente la cuadrícula independientemente del tamaño de la pantalla u orientación.

 mRecyclerView.getViewTreeObserver().addOnGlobalLayoutListener( new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { mRecyclerView.getViewTreeObserver().removeOnGLobalLayoutListener(this); int viewWidth = mRecyclerView.getMeasuredWidth(); float cardViewWidth = getActivity().getResources().getDimension(R.dimen.cardview_layout_width); int newSpanCount = (int) Math.floor(viewWidth / cardViewWidth); mLayoutManager.setSpanCount(newSpanCount); mLayoutManager.requestLayout(); } }); 

He extendido el RecyclerView y sobrepasado el método onMeasure.

Establecer un ancho de artículo (variable de miembro) tan pronto como pueda, con un valor predeterminado de 1. Esto también se actualiza en la configuración cambiada. Esto ahora tendrá tantas filas como pueda caber en retrato, paisaje, teléfono / tableta etc.

 @Override protected void onMeasure(int widthSpec, int heightSpec) { super.onMeasure(widthSpec, heightSpec); int width = MeasureSpec.getSize(widthSpec); if(width != 0){ int spans = width / mItemWidth; if(spans > 0){ mLayoutManager.setSpanCount(spans); } } } 

Bueno, esto es lo que usé, bastante básico, pero consigue el trabajo hecho para mí. Este código básicamente obtiene el ancho de la pantalla en las inmersiones y luego divide por 300 (o cualquier ancho que esté utilizando para el diseño de su adaptador). Así que los teléfonos más pequeños con 300-500 ancho de inmersión sólo muestran una columna, tabletas de 2-3 columnas, etc Simple, sin problemas y sin desventaja, por lo que puedo ver.

 Display display = getActivity().getWindowManager().getDefaultDisplay(); DisplayMetrics outMetrics = new DisplayMetrics(); display.getMetrics(outMetrics); float density = getResources().getDisplayMetrics().density; float dpWidth = outMetrics.widthPixels / density; int columns = Math.round(dpWidth/300); mLayoutManager = new GridLayoutManager(getActivity(),columns); mRecyclerView.setLayoutManager(mLayoutManager); 

Estoy publicando esto en caso de que alguien obtiene el ancho de columna extraño como en mi caso.

No puedo comentar la respuesta de @ s-marks debido a mi baja reputación. He aplicado su solución de solución, pero tengo un extraño ancho de columna, por lo que modificó la función checkedColumnWidth de la siguiente manera:

 private int checkedColumnWidth(Context context, int columnWidth) { if (columnWidth <= 0) { /* Set default columnWidth value (48dp here). It is better to move this constant to static constant on top, but we need context to convert it to dp, so can't really do so. */ columnWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 48, context.getResources().getDisplayMetrics()); } else { columnWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, columnWidth, context.getResources().getDisplayMetrics()); } return columnWidth; } 

Al convertir el ancho de columna dado en DP, se solucionó el problema.

Para acomodar el cambio de orientación en la respuesta de s-marks , agregué una comprobación del cambio de anchura (anchura de getWidth (), no ancho de columna).

 private boolean mWidthChanged = true; private int mWidth; @Override public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) { int width = getWidth(); int height = getHeight(); if (width != mWidth) { mWidthChanged = true; mWidth = width; } if (mColumnWidthChanged && mColumnWidth > 0 && width > 0 && height > 0 || mWidthChanged) { int totalSpace; if (getOrientation() == VERTICAL) { totalSpace = width - getPaddingRight() - getPaddingLeft(); } else { totalSpace = height - getPaddingTop() - getPaddingBottom(); } int spanCount = Math.max(1, totalSpace / mColumnWidth); setSpanCount(spanCount); mColumnWidthChanged = false; mWidthChanged = false; } super.onLayoutChildren(recycler, state); } 

La solución upvoted está bien, pero maneja los valores entrantes como píxeles, lo que puede hacerte tropezar si estás codificando valores para probar y asumiendo dp. La forma más fácil es, probablemente, poner el ancho de columna en una dimensión y leerlo al configurar el GridAutofitLayoutManager, que convertirá automáticamente dp a valor de píxel correcto:

 new GridAutofitLayoutManager(getActivity(), (int)getActivity().getResources().getDimension(R.dimen.card_width)) 
  1. Establecer un ancho fijo mínimo de imageView (144dp x 144dp por ejemplo)
  2. Cuando se crea GridLayoutManager, es necesario saber cuántas columnas serán con un tamaño mínimo de imageView:

     WindowManager wm = (WindowManager) this.getSystemService(Context.WINDOW_SERVICE); //Получаем размер экрана Display display = wm.getDefaultDisplay(); Point point = new Point(); display.getSize(point); int screenWidth = point.x; //Ширина экрана int photoWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 144, this.getResources().getDisplayMetrics()); //Переводим в точки int columnsCount = screenWidth/photoWidth; //Число столбцов GridLayoutManager gridLayoutManager = new GridLayoutManager(this, columnsCount); recyclerView.setLayoutManager(gridLayoutManager); 
  3. Después de que usted necesita para cambiar el tamaño de imageView en el adaptador si tiene espacio en la columna. Usted puede enviar nuevoImageViewSize entonces inisilize el adaptador de la actividad allí usted calcula la cuenta de la pantalla y de la columna:

     @Override //Заполнение нашей плитки public void onBindViewHolder(PhotoHolder holder, int position) { ... ViewGroup.LayoutParams photoParams = holder.photo.getLayoutParams(); //Параметры нашей фотографии int newImageViewSize = screenWidth/columnsCount; //Новый размер фотографии photoParams.width = newImageViewSize; //Установка нового размера photoParams.height = newImageViewSize; holder.photo.setLayoutParams(photoParams); //Установка параметров ... } 

Funciona en ambas orientaciones. En vertical tengo 2 columnas y en horizontal – 4 columnas. El resultado: /images//WHvyD.jpg

Establezca spanCount en un número grande (que es el número máximo de columna) y establezca un SpanSizeLookup personalizado en GridLayoutManager.

 mLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() { @Override public int getSpanSize(int i) { return SPAN_COUNT / (int) (mRecyclerView.getMeasuredWidth()/ CELL_SIZE_IN_PX); } }); 

Es un poco feo, pero funciona.

Creo que un gerente como AutoSpanGridLayoutManager sería la mejor solución, pero no encontré nada de eso.

EDIT: hay un error, en algún dispositivo añadir espacio en blanco a la derecha

Aquí están las partes relevantes de una envoltura que he estado utilizando para detectar automáticamente el recuento de span. Se inicializa llamando a setGridLayoutManager con una referencia R.layout.my_grid_item , y se calcula cuántos de ellos pueden caber en cada fila.

 public class AutoSpanRecyclerView extends RecyclerView { private int m_gridMinSpans; private int m_gridItemLayoutId; private LayoutRequester m_layoutRequester = new LayoutRequester(); public void setGridLayoutManager( int orientation, int itemLayoutId, int minSpans ) { GridLayoutManager layoutManager = new GridLayoutManager( getContext(), 2, orientation, false ); m_gridItemLayoutId = itemLayoutId; m_gridMinSpans = minSpans; setLayoutManager( layoutManager ); } @Override protected void onLayout( boolean changed, int left, int top, int right, int bottom ) { super.onLayout( changed, left, top, right, bottom ); if( changed ) { LayoutManager layoutManager = getLayoutManager(); if( layoutManager instanceof GridLayoutManager ) { final GridLayoutManager gridLayoutManager = (GridLayoutManager) layoutManager; LayoutInflater inflater = LayoutInflater.from( getContext() ); View item = inflater.inflate( m_gridItemLayoutId, this, false ); int measureSpec = View.MeasureSpec.makeMeasureSpec( 0, View.MeasureSpec.UNSPECIFIED ); item.measure( measureSpec, measureSpec ); int itemWidth = item.getMeasuredWidth(); int recyclerViewWidth = getMeasuredWidth(); int spanCount = Math.max( m_gridMinSpans, recyclerViewWidth / itemWidth ); gridLayoutManager.setSpanCount( spanCount ); // if you call requestLayout() right here, you'll get ArrayIndexOutOfBoundsException when scrolling post( m_layoutRequester ); } } } private class LayoutRequester implements Runnable { @Override public void run() { requestLayout(); } } } 
  • Cómo obtener el elemento del adaptador RecyclerView en Android
  • Detectar clic en RecyclerView fuera de los elementos
  • Cómo cambiar el diseño del elemento recyclerview desde fuera?
  • Resaltar el elemento seleccionado dentro de un RecyclerView
  • RecyclerView onClick notifyItemRemoved no desencadena onBindView
  • RecyclerView add EmptyView
  • Android - ¿Cómo cambiar la altura de Recyclerview dinámicamente?
  • Recyclerview dentro de la vista de desplazamiento anidada cargando todos los datos en lugar de llamar una por una imagen cuando se desplaza
  • Cómo recorrer los elementos en Android RecyclerView?
  • RecyclerView & SnapHelper -> obtener una vista rápida / segmentada
  • TableLayout con RecyclerView
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.