Перекрытие строк в GridView: как сделать так, чтобы высота строки подходила для самого высокого элемента? - PullRequest
43 голосов
/ 25 сентября 2011

Как и этот предыдущий человек , у меня есть нежелательное перекрытие между элементами GridView:

GridView items overlapping

Notice the text, in every column except the rightmost one.

Where I differ from that previous question is that I don't want a constant row height. I want the row height to vary to accommodate the tallest content in each row, for efficient use of screen space.

Looking at the источник для GridView (не официальная копия, но kernel.org все еще не работает), мы можем видеть в fillDown () и makeRow (), что последний просмотренный вид - это «ссылочный вид»: высота строки задается с высоты этого вида, а не с самого высокого. Это объясняет, почему самый правый столбец в порядке. К сожалению, GridView не очень хорошо настроен, чтобы исправить это с помощью наследования.Все соответствующие поля и методы являются закрытыми.

Итак, прежде чем я выберу заветный вздутый путь "клонировать и владеть", есть ли уловка, которую я здесь упускаю? IЯ мог бы использовать TableLayout, но это потребовало бы, чтобы я сам реализовал numColumns="auto_fit" (так как я хочу, например, только один длинный столбец на экране телефона), и это также не было бы AdapterView, как это должно быть.

Редактировать: на самом деле, клон и собственный здесь не практичны.GridView зависит от недоступных частей своих родительских и родственных классов и может привести к импорту как минимум 6000 строк кода (AbsListView, AdapterView и т. Д.)

Ответы [ 5 ]

27 голосов
/ 27 сентября 2011

Я использовал статический массив для определения максимальной высоты строки.Это не идеально, так как более ранние столбцы не будут изменены, пока ячейка не будет снова отображена.Вот код для завышенного представления содержимого для повторного использования.

Редактировать : Я правильно выполнил эту работу, но перед рендерингом я предварительно измерил все ячейки.Я сделал это, создав подкласс GridView и добавив измерительный хук в метод 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]);
    }
}

Вот функция измерения в моем подклассе BaseAdapter.Обратите внимание, что у меня есть метод updateItemDisplay, который устанавливает весь соответствующий текст и изображения в ячейке вида.

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

И, наконец, вот подкласс GridView, настроенный для измерения ячеек вида во время размещения:

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

Причина, по которой у меня есть количество столбцов в качестве ресурса, заключается в том, что у меня может быть другое число в зависимости от ориентации и т. Д.

1 голос
/ 21 ноября 2017

Я провел очень много исследований, но нашел неполный ответ или испытывал затруднения с пониманием того, что происходит с решением, но в конце концов нашел ответ, который идеально сочетается с правильным объяснением.

Моя проблема заключалась в том, чтобы правильно подогнать элемент gridview по высоте. Это Grid-view отлично работало, когда все ваши взгляды имеют одинаковую высоту. Но когда ваши представления имеют разную высоту, сетка не ведет себя так, как ожидалось. Представления будут перекрывать друг друга, вызывая an-aesthetically приятную сетку.

Здесь Решение Я использовал этот класс в XML-макете.

Я использовал это решение, и оно работает очень хорошо, большое спасибо. - Абхишек Миттал

1 голос
/ 21 декабря 2012

Основываясь на информации от Криса, я использовал этот обходной путь, используя ссылочный вид, используемый собственным GridView при определении высоты других элементов GridView.

Я создал этот пользовательский класс 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;

    }
}

В методе getView вашего адаптера либо оберните ваш convertView как дочерний элемент в новый GridViewItemContainer, либо сделайте этот элемент верхним XML-элементом макета вашего элемента:

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

Где numColumns - это число столбцов в GridView, а 'viewsInRow' - это список View в текущей строке, где находится 'position'.

0 голосов
/ 23 декабря 2018

Если вы преобразуете свой GridView или ListView в RecyclerView , эта проблема не произойдет. И вам не нужно будет создавать собственный класс GridView.

0 голосов
/ 05 ноября 2014

Придание веса вашему GridView также работает на GridViews в LinearLayouts в детстве. Таким образом, GridView заполняет область просмотра дочерними элементами, чтобы вы могли просматривать ее элементы, пока они соответствуют экрану (затем прокручиваете).

Но всегда избегайте использования GridViews внутри ScrollViews. В противном случае вам нужно будет рассчитать рост каждого ребенка и переназначить его, как Чейз ответил выше.

<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"/>
...