El uso de las nuevas GridLayoutManager: https://developer.android.com/reference/android/support/v7/widget/GridLayoutManager.html

Toma una explícita span contar, por lo que el problema ahora es: ¿cómo sabe usted cuántos «abarca» ajuste por fila? Esta es una cuadrícula, después de todo. Debe haber como muchos se extiende como la RecyclerView puede caber, basado en la medición de la anchura.

Usando el viejo GridView, usted acaba de establecer el «columnWidth» propiedad y sería detectar automáticamente cuántas columnas ajuste. Esto es básicamente lo que quiero replicar para el RecyclerView:

  • agregar OnLayoutChangeListener en el RecyclerView
  • en este devolución de llamada, inflar un único «elemento de cuadrícula’ y medir
  • spanCount = recyclerViewWidth /singleItemWidth;

Esto parece bastante común de comportamiento, de modo que hay una forma más simple que yo no estoy viendo?

InformationsquelleAutor foo64 | 2014-10-31

12 Comentarios

  1. 113

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

    public class GridAutofitLayoutManager extends GridLayoutManager
    {
    private int columnWidth;
    private boolean isColumnWidthChanged = true;
    private int lastWidth;
    private int lastHeight;
    public GridAutofitLayoutManager(@NonNull final Context context, final int columnWidth) {
    /* Initially set spanCount to 1, will be changed automatically later. */
    super(context, 1);
    setColumnWidth(checkedColumnWidth(context, columnWidth));
    }
    public GridAutofitLayoutManager(
    @NonNull final Context context,
    final int columnWidth,
    final int orientation,
    final boolean reverseLayout) {
    /* Initially set spanCount to 1, will be changed automatically later. */
    super(context, 1, orientation, reverseLayout);
    setColumnWidth(checkedColumnWidth(context, columnWidth));
    }
    private int checkedColumnWidth(@NonNull final Context context, final 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(final int newColumnWidth) {
    if (newColumnWidth > 0 && newColumnWidth != columnWidth) {
    columnWidth = newColumnWidth;
    isColumnWidthChanged = true;
    }
    }
    @Override
    public void onLayoutChildren(@NonNull final RecyclerView.Recycler recycler, @NonNull final RecyclerView.State state) {
    final int width = getWidth();
    final int height = getHeight();
    if (columnWidth > 0 && width > 0 && height > 0 && (isColumnWidthChanged || lastWidth != width || lastHeight != height)) {
    final int totalSpace;
    if (getOrientation() == VERTICAL) {
    totalSpace = width - getPaddingRight() - getPaddingLeft();
    } else {
    totalSpace = height - getPaddingTop() - getPaddingBottom();
    }
    final int spanCount = Math.max(1, totalSpace / columnWidth);
    setSpanCount(spanCount);
    isColumnWidthChanged = false;
    }
    lastWidth = width;
    lastHeight = height;
    super.onLayoutChildren(recycler, state);
    }
    }

    Yo en realidad no recuerdo por qué me eligió ajustado recuento en onLayoutChildren, escribí esta clase hace algún tiempo. Pero el punto es que tenemos que hacerlo después de ver se midan. de modo que podemos obtener es la altura y la anchura.

    EDIT 1: Corregir un error en el código causado a una configuración incorrecta span contar. Gracias usuario @Elyees Abouda para la presentación de informes y sugiriendo solución.

    EDIT 2: Algunos pequeños refactorización y revisión de casos de borde con manual de cambios de orientación de la manipulación. Gracias usuario @tatarize para la presentación de informes y sugiriendo solución.

    • Esto debe ser aceptado responder, es LayoutManager‘s de trabajo para sentar a los niños y no RecyclerView‘s
    • Lo que si ColumnWidth no es fijo.
    • ¿te refieres a la situación, cuando el ancho de la columna debe ser diferente dependiendo de la orientación, por ejemplo?
    • Me refiero a columnWidth sería diferente dependiendo de los datos que se pasan en el adaptador. Dicen que si cuatro letras de las palabras se pasa a continuación, se debe acomodar a cuatro elementos en una fila si 10 letras de las palabras se pasa, a continuación, debería ser capaz de adaptarse a sólo 2 elementos en una fila.
    • oh. Lo que yo veo. Bueno, yo creo que no se debe agregar esta funcionalidad a LayoutManager, porque básicamente LayoutManager no debe tratar con datos reales. Sólo sabe «debo diseño de algo según mis parámetros» y no sabe nada acerca de este «algo». Así que creo que en este caso debe escribir esta lógica fuera de LayoutManager, en la Actividad.onCreate() por ejemplo. Cómo hacerlo? Puede ser complicado dependiendo de los datos que pasan a su adaptador y real requirenments. Te sugiero que para crear otra cuestión aquí para eso, para cualquier persona podría encontrar una respuesta más tarde.
    • Tengo una excepción dentro de setSpanCount: java.lang.IllegalStateException: No se puede llamar a este método, mientras que RecyclerView es el cálculo de una disposición o de desplazamiento
    • hacer manualmente llamando setSpanCount(int) en el gestor de diseño? También desde donde se llama setLayoutManager(LayoutManager) en su RecyclerView?
    • Los problemas en este código es la siguiente: si columnWidth = 300, y totalSpace = 736, entonces spanCount =2, lo que resulta en el establecimiento de los artículos no en la misma proporción. 2 * 300 = 600, el resto 136 píxeles no se cuentan, que los resultados de la no es igual de relleno
    • No estoy seguro de que el problema real ocurre porque de este código, pero no puedo decir a ciencia cierta, hasta que yo vea cómo configurar tu RecyclerView y elementos. Por favor, crear pregunta y post algo de código para que yo pueda ayudarle de alguna manera.
    • Para mí, al girar el dispositivo, es cambiar un poco el tamaño de la cuadrícula, la reducción de 20dp. Tienes alguna info sobre esto? Gracias.
    • misma cosa, este código funciona perfectamente bien im mis proyectos, así que no puedo decir lo que exactamente causando problemas sin mirar el código.
    • He actualizado el apoyo de la biblioteca a 23.3.0 y esta detenido ocurriendo. Adivinar que era un error de 23.0.0. Gracias por el administrador, bonito fragmento de código.
    • Gracias, me alegro de oír tu problema resuelto.
    • A veces getWidth() o getHeight() es 0 antes de que la vista se crea, que obtendrá un mal spanCount (1, ya totalSpace será <=0). Lo que he añadido es ignorar setSpanCount en este caso. (onLayoutChildren será llamado de nuevo más tarde)
    • Este código es la fijación de la anchura de cada elemento y una vez que se span contar lo que se fija para siempre. Yo estaba buscando algo como WRAP_CONTENT para cada elemento y diferentes span recuento de fila diferente.
    • Estoy recibiendo OOM error en el super.onLayoutChildren(). Cualquier ayuda se aprecia
    • Hay un borde condición de que no está cubierto. Si establece el configChanges tal que se puede manejar la rotación en lugar de dejar que reconstruir la totalidad de la actividad, que tiene el raro caso de que la anchura de la recyclerview cambios, mientras que nada más lo hace. Con la modificación de la anchura y la altura de la spancount es sucio, pero mColumnWidth no ha cambiado, por lo que onLayoutChildren() anula y no volver a calcular el ahora sucio valores. Guardar la anterior, la altura y la anchura, y la activación en caso de cambios en un valor distinto de cero manera.

  2. 27

    Que he logrado esto mediante un árbol de vista de observador para obtener el ancho de la recylcerview una vez prestados y, a continuación, obtener las dimensiones fijas de mi tarjeta de vista de los recursos y, a continuación, ajuste el intervalo de recuento después de hacer mis cálculos. Es realmente sólo es aplicable si los elementos que se muestran son de un ancho fijo. Esto me ayudó a rellenar automáticamente la cuadrícula, independientemente del tamaño de la pantalla o la 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 usado esto y tengo ArrayIndexOutOfBoundsException (en android.apoyo.v7.widget.GridLayoutManager.layoutChunk(GridLayoutManager.java:361)) cuando se desplaza por la RecyclerView.
    • Sólo añadir mLayoutManager.requestLayout() después de setSpanCount() y trabajo
    • Nota: removeGlobalOnLayoutListener() está en desuso en la API de nivel 16. uso removeOnGlobalLayoutListener() lugar. Documentación.
    • errata removeOnGLobalLayoutListener debe ser removeOnGlobalLayoutListener
  3. 14

    Bien, esto es lo que he usado, bastante básico, pero hace el trabajo por mí. Este código básicamente se obtiene el ancho de la pantalla en salsas y luego se divide por 300 (o lo que sea de ancho que está utilizando para el adaptador del diseño). Por lo que pequeños teléfonos con 300-500 inmersión anchura de sólo mostrar una columna, tabletas de 2-3 columnas, etc. Simple, alboroto manera gratuita y sin ningún inconveniente, 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);
    • ¿Por qué utilizar el ancho de la pantalla en lugar de la RecyclerView del ancho? Y codificar 300 es una mala práctica (que necesita para mantenerse en sincronía con su xml de diseño)
    • en el xml sólo puede establecer match_parent en el elemento. Pero sí, sigue siendo feo 😉
  4. 13

    Me extendió la RecyclerView y anuló la onMeasure método.

    Me puse un elemento ancho(miembro de la variable) tan pronto como puedo,con un valor predeterminado de 1. Esto también actualiza la configuración ha cambiado. Este de ahora tendrá tantas filas como puede caber en el retrato,el paisaje,el teléfono/tablet, 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);
    }
    }
    }
  5. 7

    Estoy publicando esto solo en caso de que alguien se extraña el ancho de la columna como en mi caso.

    Yo no soy capaz de comentar sobre @s-marcas‘s respuesta debido a mi baja reputación. He aplicado su solución solución pero tengo algo raro ancho de columna, así que he modificado checkedColumnWidth función 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;
    }

    Mediante la conversión de la dado que el ancho de columna en DP se ha solucionado el problema.

  6. 2

    Para dar cabida a cambio de orientación en s-marcas‘s respuesta, he añadido un cheque en el cambio de ancho (ancho de getWidth(), no el ancho de la 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);
    }
  7. 2

    La upvoted solución está muy bien, pero maneja los valores entrantes, como los pixeles, que pueden viaje si usted está codificar los valores de las pruebas, y suponiendo que los dp. Forma más sencilla es probablemente para poner el ancho de columna en una dimensión y leerlo a la hora de configurar el GridAutofitLayoutManager, que se convertirá automáticamente en dp para corregir el valor de píxel:

    new GridAutofitLayoutManager(getActivity(), (int)getActivity().getResources().getDimension(R.dimen.card_width))
    • sí, que me tiro por un lazo. es verdad que se acaba de aceptar el recurso en sí. Me refiero a que es como siempre lo estaremos haciendo.
  8. 1
    1. Conjunto mínimo de ancho fijo de imageView (144dp x 144dp por ejemplo)
    2. Al crear GridLayoutManager, usted necesita saber cuánto de columnas con 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 tienes espacio en la columna. Usted puede enviar newImageViewSize luego inisilize adaptador de actividad hay que calcular la pantalla y el número de columnas:

      @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: https://i.stack.imgur.com/WHvyD.jpg

  9. 0

    Esto es s.mak’ de la clase con una pequeña solución para cuando el recyclerview sí cambia de tamaño. Como cuando a lidiar con los cambios de orientación del sí mismo (en el manifiesto android:configChanges="orientation|screenSize|keyboardHidden"), o alguna otra razón la recyclerview puede cambiar de tamaño sin la mColumnWidth cambiando. También he cambiado el valor int que se necesita para ser el recurso del tamaño y permitió un constructor de ningún recurso, a continuación, setColumnWidth a hacerlo por ti mismo.

    public class GridAutofitLayoutManager extends GridLayoutManager {
    private Context context;
    private float mColumnWidth;
    private float currentColumnWidth = -1;
    private int currentWidth = -1;
    private int currentHeight = -1;
    public GridAutofitLayoutManager(Context context) {
    /* Initially set spanCount to 1, will be changed automatically later. */
    super(context, 1);
    this.context = context;
    setColumnWidthByResource(-1);
    }
    public GridAutofitLayoutManager(Context context, int resource) {
    this(context);
    this.context = context;
    setColumnWidthByResource(resource);
    }
    public GridAutofitLayoutManager(Context context, int resource, int orientation, boolean reverseLayout) {
    /* Initially set spanCount to 1, will be changed automatically later. */
    super(context, 1, orientation, reverseLayout);
    this.context = context;
    setColumnWidthByResource(resource);
    }
    public void setColumnWidthByResource(int resource) {
    if (resource >= 0) {
    mColumnWidth = context.getResources().getDimension(resource);
    } else {
    /* 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. */
    mColumnWidth = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 48,
    context.getResources().getDisplayMetrics());
    }
    }
    public void setColumnWidth(float newColumnWidth) {
    mColumnWidth = newColumnWidth;
    }
    @Override
    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
    recalculateSpanCount();
    super.onLayoutChildren(recycler, state);
    }
    public void recalculateSpanCount() {
    int width = getWidth();
    if (width <= 0) return;
    int height = getHeight();
    if (height <= 0) return;
    if (mColumnWidth <= 0) return;
    if ((width != currentWidth) || (height != currentHeight) || (mColumnWidth != currentColumnWidth)) {
    int totalSpace;
    if (getOrientation() == VERTICAL) {
    totalSpace = width - getPaddingRight() - getPaddingLeft();
    } else {
    totalSpace = height - getPaddingTop() - getPaddingBottom();
    }
    int spanCount = (int) Math.max(1, Math.floor(totalSpace / mColumnWidth));
    setSpanCount(spanCount);
    currentColumnWidth = mColumnWidth;
    currentWidth = width;
    currentHeight = height;
    }
    }
    }
  10. -1

    Conjunto spanCount a un gran número (que es el número máximo de columna) y establecer una costumbre SpanSizeLookup a la 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 he encontrado nada de eso.

    EDIT : Hay un error, en algún dispositivo que agregar espacio en blanco a la derecha

    • El espacio de la derecha no es un error. si el intervalo de recuento es de 5 y getSpanSize devuelve 3 habrá un espacio porque usted no está llenando el espacio.
  11. -7

    Aquí las partes pertinentes de un contenedor he estado usando para detectar automáticamente el intervalo de recuento. Inicializarla llamando setGridLayoutManager con un R. layout.my_grid_item de referencia, y se da cuenta de cómo muchos de los que caben 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();
    }
    }
    }
    • Por qué downvote aceptadas respuesta. debe explicar por qué downvote

Dejar respuesta

Please enter your comment!
Please enter your name here