web-dev-qa-db-fra.com

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

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

GridView items overlapping

Remarquez le texte dans toutes les colonnes sauf la plus à droite.

La différence entre cette question et la question précédente est que je je ne veux pas d’une hauteur de ligne constante. Je souhaite que la hauteur de la ligne varie de accueillir le contenu le plus haut dans chaque ligne, pour une utilisation efficace de l'écran.

En regardant la source pour GridView (pas la copie faisant autorité, mais kernel.org est toujours en panne), nous pouvons voir dans fillDown () et makeRow () que la dernière vue vue est la "vue de référence": la hauteur est définie à partir de la hauteur de cette vue, et non de la plus grande. _ {Cela explique pourquoi la colonne la plus à droite est ok. Malheureusement, GridView n'est pas bien configuré pour résoudre ce problème par héritage. Tous les champs et méthodes pertinents sont privés.

Donc, avant de prendre le chemin usé de "clone and own", y a-t-il un truc qui me manque ici? Je pourrais utiliser un TableLayout, mais cela exigerait que je mette en œuvre numColumns="auto_fit" moi-même ( car je veux par exemple une seule longue colonne sur un écran de téléphone), et ce ne serait pas non plus un AdapterView, ce qui semble être le cas.

Edit: en fait, cloner et posséder n'est pas pratique ici. GridView dépend de parties inaccessibles de ses classes parentes et apparentées et entraînerait l'importation d'au moins 6000 lignes de code (AbsListView, AdapterView, etc.).

43
Chris Boyle

J'ai utilisé un tableau statique pour atteindre des hauteurs maximales pour la ligne. Ce n'est pas parfait, car les colonnes précédentes ne seront pas redimensionnées tant que la cellule ne sera pas réaffichée. Voici le code pour la vue du contenu réutilisable gonflé.

Edit : J'ai eu ce travail correctement mais j'avais pré-mesuré toutes les cellules avant le rendu. Je l'ai fait en sous-classant GridView et en ajoutant un crochet de mesure dans la méthode 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]);
    }
}

Voici la fonction de mesure dans ma sous-classe BaseAdapter. Notez que j’ai une méthode updateItemDisplay qui définit tout le texte et toutes les images appropriés dans la cellule de vue.

    /**
     * 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 la sous-classe GridView configurée pour mesurer les cellules de vue lors 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 en tant que ressource est que je peux avoir un nombre différent en fonction de l'orientation, etc. 

27
Chase

J'ai fait tellement de recherches, mais j'ai trouvé une réponse incomplète ou je ne savais pas quoi faire avec la solution, mais j'ai finalement trouvé une réponse qui cadrait parfaitement avec une explication appropriée.

Mon problème était d’adapter correctement l’élément gridview à la hauteur. Ce Grid-view a bien fonctionné lorsque toutes vos vues ont la même hauteur. Mais lorsque vos vues ont des hauteurs différentes, la grille ne se comporte pas comme prévu. Les vues se chevaucheront, créant une grille plaisante an-aesthetically.

Ici Solution J'ai utilisé cette classe dans un layout XML.

J'ai utilisé cette solution, et cela fonctionne très bien, merci beaucoup .-- Abhishek Mittal

1
opalfire

Sur la base des informations de Chris, j'ai utilisé cette solution de contournement en utilisant la référence-View utilisée par le GridView natif pour déterminer la hauteur d'autres éléments GridView.

J'ai créé cette classe personnalisée 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;

    }
}

Dans la méthode getView de votre adaptateur, enveloppez votre convertView en tant qu'enfant dans un nouveau GridViewItemContainer ou faites de celui-ci le premier élément XML de la présentation de votre élément:

        // 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 GridView et "viewsInRow" est une liste de View sur la ligne actuelle de l'emplacement de "position".

1
Streets Of Boston

Si vous convertissez votre GridView ou ListView en RecyclerView , ce problème ne se produira pas. Et vous n'avez pas besoin de créer une classe GridView personnalisée.

0
Mr-IDE