2011-09-25 9 views
42

Como this previous person, que tienen superposición no deseada entre los elementos de GridView:superposición de filas de GridView: cómo hacer que la altura de fila se ajuste al elemento más alto?

GridView items overlapping

Aviso del texto, en cada columna excepto el extremo derecho.

Donde difiero de la pregunta anterior es que I no quiero una altura de fila constante. Quiero que la altura de la fila varíe a para acomodar el contenido más alto en cada fila, para un uso eficiente del espacio en la pantalla.

Al mirar el source for GridView (no la copia autorizada, pero kernel.org aún está inactivo), podemos ver en fillDown() y makeRow() que la última Vista vista es la "vista de referencia": la altura de la fila es establecer desde la altura de esa vista, no desde la más alta. Esto explica por qué la columna de la derecha está bien. Desafortunadamente, GridView no está bien configurado para que lo solucione por herencia. Todos los campos y métodos relevantes son privados.

Por lo tanto, antes de tomar el desgastado camino lleno de baches de "clonar y poseer", ¿Hay algún truco que me falta aquí? Podría usar TableLayout, pero eso requeriría que implementara (ya que quiero, por ejemplo, una sola columna larga en la pantalla de un teléfono), y tampoco sería una AdapterView, que así debería ser.

Editar: de hecho, clonar y poseer no es práctico aquí. GridView depende de partes inaccesibles de sus clases padre y hermano, y daría como resultado la importación de al menos 6000 líneas de código (AbsListView, AdapterView, etc.)

+1

Para la fijación de altura, este enlace puede ser una solución: http://stackoverflow.com/questions/11411472/android-gridviews-row-height-max-of-item/16018067#16018067 – kinghomer

+0

Es mejor para usar la vista de reciclaje Manejará este tipo de cosas correctamente. –

Respuesta

27

Utilicé una matriz estática para controlar las alturas máximas de la fila. Esto no es perfecto ya que las columnas anteriores no se redimensionarán hasta que la celda se vuelva a mostrar. Aquí está el código para la vista de contenido reutilizable inflado.

Editar: Obtuve este trabajo correctamente pero había medido previamente todas las celdas antes de la representación. Hice esto subclasificando GridView y agregando un gancho de medición en el método onLayout.

/** 
* Custom view group that shares a common max height 
* @author Chase Colburn 
*/ 
public class GridViewItemLayout extends LinearLayout { 

    // Array of max cell heights for each row 
    private static int[] mMaxRowHeight; 

    // The number of columns in the grid view 
    private static int mNumColumns; 

    // The position of the view cell 
    private int mPosition; 

    // Public constructor 
    public GridViewItemLayout(Context context) { 
     super(context); 
    } 

    // Public constructor 
    public GridViewItemLayout(Context context, AttributeSet attrs) { 
     super(context, attrs); 
    } 

    /** 
    * Set the position of the view cell 
    * @param position 
    */ 
    public void setPosition(int position) { 
     mPosition = position; 
    } 

    /** 
    * Set the number of columns and item count in order to accurately store the 
    * max height for each row. This must be called whenever there is a change to the layout 
    * or content data. 
    * 
    * @param numColumns 
    * @param itemCount 
    */ 
    public static void initItemLayout(int numColumns, int itemCount) { 
     mNumColumns = numColumns; 
     mMaxRowHeight = new int[itemCount]; 
    } 

    @Override 
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 
     super.onMeasure(widthMeasureSpec, heightMeasureSpec); 
     // Do not calculate max height if column count is only one 
     if(mNumColumns <= 1 || mMaxRowHeight == null) { 
      return; 
     } 

     // Get the current view cell index for the grid row 
     int rowIndex = mPosition/mNumColumns; 
     // Get the measured height for this layout 
     int measuredHeight = getMeasuredHeight(); 
     // If the current height is larger than previous measurements, update the array 
     if(measuredHeight > mMaxRowHeight[rowIndex]) { 
      mMaxRowHeight[rowIndex] = measuredHeight; 
     } 
     // Update the dimensions of the layout to reflect the max height 
     setMeasuredDimension(getMeasuredWidth(), mMaxRowHeight[rowIndex]); 
    } 
} 

Aquí está la función de medición en mi subclase BaseAdapter. Tenga en cuenta que tengo un método updateItemDisplay que establece todos los textos e imágenes apropiados en la celda de visualización.

/** 
    * Run a pass through each item and force a measure to determine the max height for each row 
    */ 
    public void measureItems(int columnWidth) { 
     // Obtain system inflater 
     LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 
     // Inflate temp layout object for measuring 
     GridViewItemLayout itemView = (GridViewItemLayout)inflater.inflate(R.layout.list_confirm_item, null); 

     // Create measuring specs 
     int widthMeasureSpec = MeasureSpec.makeMeasureSpec(columnWidth, MeasureSpec.EXACTLY); 
     int heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); 

     // Loop through each data object 
     for(int index = 0; index < mItems.size(); index++) { 
      String[] item = mItems.get(index); 

      // Set position and data 
      itemView.setPosition(index); 
      itemView.updateItemDisplay(item, mLanguage); 

      // Force measuring 
      itemView.requestLayout(); 
      itemView.measure(widthMeasureSpec, heightMeasureSpec); 
     } 
    } 

Y, por último, aquí está la subclase GridView configurar para medir celdas de la vista durante el diseño:

/** 
* Custom subclass of grid view to measure all view cells 
* in order to determine the max height of the row 
* 
* @author Chase Colburn 
*/ 
public class AutoMeasureGridView extends GridView { 

    public AutoMeasureGridView(Context context) { 
     super(context); 
    } 

    public AutoMeasureGridView(Context context, AttributeSet attrs) { 
     super(context, attrs); 
    } 

    public AutoMeasureGridView(Context context, AttributeSet attrs, int defStyle) { 
     super(context, attrs, defStyle); 
    } 

    @Override 
    protected void onLayout(boolean changed, int l, int t, int r, int b) { 
     if(changed) { 
      CustomAdapter adapter = (CustomAdapter)getAdapter(); 

      int numColumns = getContext().getResources().getInteger(R.integer.list_num_columns); 
      GridViewItemLayout.initItemLayout(numColumns, adapter.getCount()); 

      if(numColumns > 1) { 
       int columnWidth = getMeasuredWidth()/numColumns; 
       adapter.measureItems(columnWidth); 
      } 
     } 
     super.onLayout(changed, l, t, r, b); 
    } 
} 

La razón por la que tengo el número de columnas como un recurso es para que pueda tener una diferente número basado en orientación, etc.

+0

¿Puede incluir aquí su código de subclase GridView o las partes relevantes del mismo? –

+0

¡Aquí tienes! Espero que ayude. – Chase

+0

¡Gracias! Cambiaré a esto en algún momento, debería verse bastante mejor que el TableLayout. :-) –

1

Basado en la información de Chris, utilicé esta solución utilizando la vista de referencia utilizada por el GridView nativo para determinar la altura de otros elementos de GridView.

creé esta clase personalizada GridViewItemContainer:

/** 
* This class makes sure that all items in a GridView row are of the same height. 
* (Could extend FrameLayout, LinearLayout etc as well, RelativeLayout was just my choice here) 
* @author Anton Spaans 
* 
*/ 
public class GridViewItemContainer extends RelativeLayout { 
private View[] viewsInRow; 

public GridViewItemContainer(Context context) { 
    super(context); 
} 

public GridViewItemContainer(Context context, AttributeSet attrs, int defStyle) { 
    super(context, attrs, defStyle); 
} 

public GridViewItemContainer(Context context, AttributeSet attrs) { 
    super(context, attrs); 
} 

public void setViewsInRow(View[] viewsInRow) { 
    if (viewsInRow != null) { 
     if (this.viewsInRow == null) { 
      this.viewsInRow = Arrays.copyOf(viewsInRow, viewsInRow.length); 
     } 
     else { 
      System.arraycopy(viewsInRow, 0, this.viewsInRow, 0, viewsInRow.length); 
     } 
    } 
    else if (this.viewsInRow != null){ 
     Arrays.fill(this.viewsInRow, null); 
    } 
} 

@Override 
protected LayoutParams generateDefaultLayoutParams() { 
    return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); 
} 

@Override 
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 
    super.onMeasure(widthMeasureSpec, heightMeasureSpec); 

    if (viewsInRow == null) { 
     return; 
    } 

    int measuredHeight = getMeasuredHeight(); 
    int maxHeight  = measuredHeight; 
    for (View siblingInRow : viewsInRow) { 
     if (siblingInRow != null) { 
      maxHeight = Math.max(maxHeight, siblingInRow.getMeasuredHeight()); 
     } 
    } 

    if (maxHeight == measuredHeight) { 
     return; 
    } 

    int heightMode = MeasureSpec.getMode(heightMeasureSpec); 
    int heightSize = MeasureSpec.getSize(heightMeasureSpec); 
    switch(heightMode) { 
    case MeasureSpec.AT_MOST: 
     heightMeasureSpec = MeasureSpec.makeMeasureSpec(Math.min(maxHeight, heightSize), MeasureSpec.EXACTLY); 
     super.onMeasure(widthMeasureSpec, heightMeasureSpec); 
     break; 

    case MeasureSpec.EXACTLY: 
     // No debate here. Final measuring already took place. That's it. 
     break; 

    case MeasureSpec.UNSPECIFIED: 
     heightMeasureSpec = MeasureSpec.makeMeasureSpec(maxHeight, MeasureSpec.EXACTLY); 
     super.onMeasure(widthMeasureSpec, heightMeasureSpec); 
     break; 

    } 
} 

En método getView de su adaptador, o bien envolver su convertView como un niño en una nueva GridViewItemContainer o hacer éste el elemento XML superior de su elemento disposición:

 // convertView has been just been inflated or came from getView parameter. 
     if (!(convertView instanceof GridViewItemContainer)) { 
      ViewGroup container = new GridViewItemContainer(inflater.getContext()); 

      // If you have tags, move them to the new top element. E.g.: 
      container.setTag(convertView.getTag()); 
      convertView.setTag(null); 

      container.addView(convertView); 
      convertView = container; 
     } 
     ... 
     ... 
     viewsInRow[position % numColumns] = convertView; 
     GridViewItemContainer referenceView = (GridViewItemContainer)convertView; 
     if ((position % numColumns == (numColumns-1)) || (position == getCount()-1)) { 
      referenceView.setViewsInRow(viewsInRow); 
     } 
     else { 
      referenceView.setViewsInRow(null); 
     } 

Dónde númeroColumnas es el número de columnas en el GridView y 'viewsInRow' es una lista de Vista en la fila actual de donde se encuentra 'posición'.

+0

Funciona bien, hasta dos * consecuentes * filas exponen el mismo problema: luego se superponen nuevamente en el primero que era inicialmente demasiado alto. La fijación de la altura de lo que está sujeto a cambios de altura, en el otro lado, siempre funciona. Ver http://stackoverflow.com/a/2379461/1865860 :) – dentex

-1

Dando peso a su GridView también funciona en GridViews dentro de LinearLayouts como un niño. De esta forma, GridView llena la ventana gráfica con sus elementos secundarios para que pueda ver sus elementos siempre que se ajusten a la pantalla (luego se desplaza).

Pero siempre evite usar GridViews dentro de ScrollViews. De lo contrario, tendrá que calcular la altura de cada niño y reasignarlos como Chase contestó anteriormente.

<GridView 
    android:id="@+id/gvFriends" 
    android:layout_width="match_parent" 
    android:layout_height="wrap_content" 
    android:layout_weight="1" 
    android:verticalSpacing="5dp" 
    android:horizontalSpacing="5dp" 
    android:clipChildren="false" 
    android:listSelector="@android:color/transparent" 
    android:scrollbarAlwaysDrawHorizontalTrack="false" 
    android:scrollbarAlwaysDrawVerticalTrack="false" 
    android:stretchMode="columnWidth" 
    android:scrollbars="none" 
    android:numColumns="4"/> 
+0

Esto no parece funcionar. – wvdz

+0

¿Puedes compartir tu código? –

+0

Tengo una vista en cuadrícula con elementos de altura variable. Hasta ahora, la única solución que encontré es establecer una altura fija en los artículos. Estoy bastante seguro de que tu solución es incorrecta. En primer lugar, layout_weight no es heredado por elementos secundarios (sería realmente extraño si lo fuera), e incluso si lo fuera, aún no resuelve el problema. Como se describe en la pregunta, el problema es que GridView toma el último elemento de la fila como la vista de referencia, en lugar de la más alta. Si sería genial si su solución funciona, pero proporcione un ejemplo más completo en este caso para demostrar que funciona. – wvdz

0

me hicieron muchas investigaciones pero no encontró respuesta incompleta o tuvo conocimiento duro con lo que sucede con la solución, pero finalmente encontró una respuesta que encaja perfectamente con la explicación adecuada.

Mi problema consistía en ajustar el elemento gridview en altura correctamente. Este Grid-view funcionó muy bien cuando todas tus vistas tienen la misma altura. Pero cuando sus puntos de vista tienen diferentes alturas, la cuadrícula no se comporta como se esperaba. Las vistas se superpondrán entre sí, causando una cuadrícula agradable an-aesthetically.

Aquí Solution Utilicé esta clase en el diseño XML.

Cuestiones relacionadas