44 votes

Chevauchement des lignes GridView: comment adapter la hauteur des lignes au plus grand élément?

Comme cette personne précédente, j'ai indésirables de chevauchement entre les éléments GridView:

GridView items overlapping

Remarquez le texte, dans chaque colonne, à l'exception de la plus à droite.

Là où je diffère de celle de la question précédente, c'est que je ne veux pas d'une constante de la hauteur de ligne. Je veux la hauteur de ligne à varier à accueillir le plus grand contenu de chaque ligne, pour une utilisation efficace de l'espace à l'écran.

En regardant le source pour GridView (pas la copie faisant autorité, mais kernel.org est toujours à la baisse), nous pouvons voir dans fillDown() et makeRow() le dernier point de Vue vu est la "vue de référence": la ligne de hauteur est définie à partir de la hauteur de ce point de Vue, pas de la plus haute un. C'est ce qui explique pourquoi la colonne la plus à droite est ok. Malheureusement, GridView n'est pas bon pour moi de résoudre ce problème en héritage. Tous les champs pertinents et les méthodes sont privés.

Donc, avant de me prendre le bien usés bloaty chemin de "clone et propre", est-il un truc qui me manque ici? Je pourrais utiliser un TableLayout, mais cela m'obligerait à mettre en oeuvre numColumns="auto_fit" moi-même (puisque je veux par exemple, juste une longue colonne sur un écran de téléphone), et elle aussi ne serait pas un AdapterView, ce qui se sent comme elle devrait l'être.

Edit: en fait, clone et propre n'est pas pratique ici. GridView dépend des parties inaccessibles de ses parents et de la fratrie des classes, et aurait pour conséquence de l'importation d'au moins 6000 lignes de code (AbsListView, AdapterView, etc.)

26voto

Chase Points 7520

J'ai utilisé un tableau statique à disque max hauteurs de la ligne. Ce n'est pas parfait, car le plus tôt colonnes ne sera pas redimensionnée jusqu'à ce que la cellule est réaffiché. Voici le code pour le gonflés de contenu réutilisable vue.

Edit: j'ai eu ce travail correctement, mais j'avais pré-mesurer toutes les cellules avant le rendu. Je l'ai fait en sous-classement GridView et l'ajout d'une mesure de crochet dans le onLayout méthode.

/**
 * 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]);
    }
}

Ici est la fonction de mesure dans mon BaseAdapter sous-classe. Notez que j'ai une méthode updateItemDisplay qui définit toutes les mesures de texte et d'images sur le point de vue de la cellule.

    /**
     * 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);
        }
    }

Et enfin, voici le GridView sous-classe mis en place pour mesurer la vue de cellules au cours de la mise en page:

/**
 * 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 raison pour laquelle j'ai le nombre de colonnes que la ressource afin que je puisse avoir un nombre différent basé sur l'orientation, etc.

1voto

Streets Of Boston Points 3978

Basée sur les informations de Chris, j'ai utilisé cette solution de contournement, faisant usage de la référence de la Vue utilisée par les indigènes GridView lors de la détermination de la hauteur des autres éléments GridView.

J'ai créé ce GridViewItemContainer classe personnalisée:

/**
 * 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;

    }
}

Dans votre carte du getView méthode, soit l'enveloppe de votre convertView comme un enfant dans un nouveau GridViewItemContainer ou de faire de cette le haut de l'élément XML de votre élément de mise en page:

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

Où numColumns est le nombre de colonnes dans le contrôle GridView et "viewsInRow" est une liste de point de Vue sur la ligne actuelle, d'où la "position" est situé.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X