Перекрытие строк в GridView: как сделать так, чтобы высота строки подходила для самого высокого элемента?
Как и этот предыдущий человек, у меня есть нежелательные совпадения между элементами GridView:
Обратите внимание на текст в каждом столбце, кроме самого правого.
В чем я отличаюсь от предыдущего вопроса, так это то, что мне не нужна постоянная высота строки. Я хочу, чтобы высота строки менялась, чтобы вместить самое высокое содержание в каждом ряду, для эффективного использования экранного пространства.
Глядя на источник для GridView (не официальной копии, но kernel.org все еще не работает), мы видим в fillDown() и makeRow(), что последний просмотренный вид - это "ссылочный вид": высота строки устанавливается из высота этого вида, а не от самого высокого. Это объясняет, почему самый правый столбец в порядке. К сожалению, GridView не очень хорошо настроен, чтобы исправить это с помощью наследования. Все соответствующие поля и методы являются частными.
Итак, прежде чем я пойду по избитому и раздутому пути "клонировать и владеть", есть ли здесь уловка? Я мог бы использовать TableLayout, но это потребовало бы от меня реализации numColumns="auto_fit"
я (так как я хочу, например, только один длинный столбец на экране телефона), и это также не будет AdapterView, что, как мне кажется, должно быть.
Изменить: на самом деле, клон и собственный здесь не практично. GridView зависит от недоступных частей своих родительских и родственных классов и может привести к импорту как минимум 6000 строк кода (AbsListView, AdapterView и т. Д.)
6 ответов
Я использовал статический массив для определения максимальной высоты строки. Это не идеально, так как более ранние столбцы не будут изменены, пока ячейка не будет снова отображена. Вот код для завышенного представления контента для повторного использования.
Редактировать: я получил эту работу правильно, но я предварительно измерил все ячейки перед рендерингом. Я сделал это, создав подкласс 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);
}
}
Причина, по которой у меня есть количество столбцов в качестве ресурса, заключается в том, что у меня может быть другое число в зависимости от ориентации и т. Д.
Основываясь на информации от Криса, я использовал этот обходной путь, используя ссылочный вид, используемый собственным 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 в текущей строке, в которой находится позиция.
Я провел очень много исследований, но нашел неполный ответ или испытывал затруднения с пониманием того, что происходит с решением, но в конце концов нашел ответ, который идеально подходил для правильного объяснения.
Моя проблема состояла в том, чтобы правильно подогнать элемент gridview по высоте. это Grid-view
отлично работал, когда все ваши взгляды имеют одинаковую высоту. Но когда ваши представления имеют разную высоту, сетка не ведет себя так, как ожидалось. Представления будут перекрывать друг друга, вызывая an-aesthetically
приятная сетка.
Здесь Решение Я использовал этот класс в формате XML.
Я использовал это решение, и оно работает очень хорошо, большое спасибо.- Абхишек Миттал
Если вы преобразуете свой GridView или ListView в RecyclerView, эта проблема не произойдет. И вам не нужно будет создавать собственный класс GridView.
Это не правильное решение, о котором я упоминаю ниже, но может быть обходным путем, в зависимости от ваших требований.
Просто установите исправление высоты просмотра (в некоторых dp, т.е. 50dp) из вашего дочернего макета gridview, чтобы его можно было обернуть.
<TextView
android:layout_width="match_parent"
android:layout_height="50dp"
android:ellipsize="end"
android:textColor="@color/text_color"
android:textSize="13dp"
android:textStyle="normal" />
Придание веса вашему 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"/>