44

这个以前的人一样,我在 GridView 项目之间有不必要的重叠:

GridView 项目重叠

注意除了最右边的每一列中的文本。

我与上一个问题的不同之处在于我不想要一个恒定的行高。我希望行高变化以适应每行中最高的内容,以有效利用屏幕空间。

查看 GridView的源代码(不是权威副本,但 kernel.org 仍然处于关闭状态),我们可以在 fillDown() 和 makeRow() 中看到最后看到的 View 是“参考视图”:行的高度设置为该视图的高度,而不是从最高的视图。这解释了为什么最右边的列是好的。不幸的是,GridView 没有很好地设置,我无法通过继承来解决这个问题。所有相关的字段和方法都是私有的。

那么,在我走上“克隆并拥有”这条陈旧的臃肿道路之前,我是否在这里遗漏了一个技巧?我可以使用 TableLayout,但这需要我numColumns="auto_fit"自己实现(因为我想要例如电话屏幕上的一个长列),而且它也不会是一个 AdapterView,这感觉应该是。

编辑:事实上,克隆和拥有在这里并不实用。GridView 依赖于其父类和兄弟类的不可访问部分,并会导致导入至少 6000 行代码(AbsListView、AdapterView 等)

4

6 回答 6

26

我使用静态数组来驱动行的最大高度。这并不完美,因为在重新显示单元格之前不会调整早期列的大小。这是膨胀的可重用内容视图的代码。

编辑:我正确地完成了这项工作,但我在渲染之前预先测量了所有单元格。我通过继承 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);
    }
}

我将列数作为资源的原因是我可以根据方向等获得不同的数字。

于 2011-09-27T11:17:39.760 回答
1

根据来自 Chris 的信息,我在确定其他 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”是“位置”所在的当前行上的视图列表。

于 2012-12-21T17:08:29.000 回答
1

我做了很多研究,但发现答案不完整,或者很难理解解决方案的情况,但最终找到了一个与正确解释完全吻合的答案。

我的问题是将gridview项目正确地放入高度。Grid-view 当您的所有视图都具有相同的高度时,这非常有用。但是,当您的视图具有不同的高度时,网格的行为就不会像预期的那样。视图将相互重叠,形成an-aesthetically令人愉悦的网格。

这里解决方案我在 XML 布局中使用了这个类。

我使用了这个解决方案,效果很好,非常感谢。--Abhishek Mittal

于 2017-11-21T13:31:13.393 回答
0

如果您将 GridView 或 ListView 转换为RecyclerView,则不会发生此问题。而且您不需要创建自定义 GridView 类。

于 2018-12-22T21:57:43.253 回答
0

这不是我在下面提到的正确解决方案,但可以根据您的要求解决。

只需从 gridview 的子布局设置视图修复的高度(在某些 dp 中,即 50dp),以便它可以被包裹。

        <TextView
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:ellipsize="end"
            android:textColor="@color/text_color"
            android:textSize="13dp"
            android:textStyle="normal" />
于 2021-03-30T07:06:07.910 回答
-2

赋予 GridView 权重也适用于 LinearLayouts 中的 GridViews 作为孩子。这种方式 GridView 用其子项填充视口,因此只要它们适合屏幕(然后滚动),您就可以查看它的项目。

但始终避免在 ScrollViews 中使用 GridViews。否则,您将需要计算每个孩子的身高并按照上面蔡斯的回答重新分配他们。

<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"/>
于 2014-11-05T13:03:11.697 回答