GridView行重叠:如何使行高适合最高的项目?
像这个以前的人一样 ,我在GridView项目之间有不必要的重叠:
请注意文本,除了最右边的列以外的每一列。
在那里我不同于以前的问题是,我不想要一个恒定的行高度 。 我希望行高不同,以适应每行最高的内容 ,以有效利用屏幕空间。
查看GridView的源代码 (不是授权拷贝,但是kernel.org仍然是down),我们可以在fillDown()和makeRow()中看到最后一个View是“参考视图”:行的高度设置为该视图的高度,而不是最高的那个。 这解释了为什么最右边的列是好的。 不幸的是,GridView没有很好的设置,我通过inheritance来解决这个问题。 所有相关的领域和方法都是私有的。
那么,在我采取“克隆和拥有”这个老套的路线之前, 我有没有在这里错过的一个骗局? 我可以使用一个TableLayout,但是这将需要我自己实现numColumns="auto_fit"
(因为我想例如只是在电话屏幕上的一个长列),它也不会是一个AdapterView,这感觉就像它应该成为。
编辑:其实克隆和自己在这里是不实际的。 GridView依赖于父类和兄弟类的不可访问部分,并且会导致至less导入6000行代码(AbsListView,AdapterView等)
我使用了一个静态数组来驱动行的最大高度。 这是不完美的,因为在重新显示单元格之前,先前的列不会被resize。 以下是可重复使用的内容视图的代码。
编辑 :我得到这个工作正确,但我已经预先测量所有单元格之前呈现。 我通过inheritanceGridView并在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); } }
我有多less列作为资源的原因是,我可以根据方向等有不同的数字。
基于来自Chris的信息,我使用这个解决方法,在确定其他GridView项目的高度时,使用本地GridView使用的reference-View。
我创build了这个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. Eg: 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”是“position”所在位置当前行的View列表。
作为一个孩子,给GridView赋予权重也适用于LinearLayouts内部的GridViews。 通过这种方式,GridView可以使用子窗口填充视口,只要它们适合屏幕(然后滚动),就可以查看它的项目。
但总是避免使用ScrollViews内的GridViews。 否则,您将需要计算每个孩子的身高,并重新分配他们,正如Chase在上面所回答的。
<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"/>