GridView行重叠:如何使行高适合最高的项目?

发布于 2024-12-06 04:20:51 字数 995 浏览 2 评论 0原文

就像前一个人一样,我的 GridView 项目之间存在不需要的重叠:

GridView 项目重叠

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

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

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

因此,在我采取“克隆并拥有”这一陈旧的臃肿路径之前,我在这里缺少一个技巧吗?我可以使用 TableLayout,但这需要我实现 numColumns="auto_fit" 我自己(因为我只想在手机屏幕上显示一长列),而且它也不会是 AdapterView,这感觉应该是。

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

Like this previous person, I have unwanted overlap between GridView items:

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 source for GridView (not the authoritative copy, but kernel.org is still down), we can see in fillDown() and makeRow() that the last View seen is the "reference view": the row's height is set from the height of that View, not from the tallest one. This explains why the rightmost column is ok. Unfortunately, GridView is not well set-up for me to fix this by inheritance. All the relevant fields and methods are private.

So, before I take the well-worn bloaty path of "clone and own", is there a trick I'm missing here? I could use a TableLayout, but that would require me to implement numColumns="auto_fit" myself (since I want e.g. just one long column on a phone screen), and it also wouldn't be an AdapterView, which this feels like it ought to be.

Edit: in fact, clone and own is not practical here. GridView depends on inaccessible parts of its parent and sibling classes, and would result in importing at least 6000 lines of code (AbsListView, AdapterView, etc.)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

诗酒趁年少 2024-12-13 04:20:51

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

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

我将列数作为资源的原因是这样我可以根据方向等拥有不同的数字。

I used a static array to drive max heights for the row. This is not perfect since the earlier columns will not be resized until the cell is redisplayed. Here is the code for the inflated reusable content view.

Edit: I got this work correctly but I had pre-measure all cells before rendering. I did this by subclassing GridView and adding a measuring hook in the onLayout method.

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

Here is the measuring function in my BaseAdapter subclass. Note that I have a method updateItemDisplay that sets all appropriate text and images on the view cell.

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

And finally, here is the GridView subclass set up to measure view cells during layout:

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

The reason I have the number of columns as a resource is so that I can have a different number based on orientation, etc.

萌︼了一个春 2024-12-13 04:20:51

根据 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'是'position'所在当前行的View列表。

Based on the info from Chris, I used this workaround making use of the reference-View used by the native GridView when determining the height of other GridView items.

I created this GridViewItemContainer custom class:

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

    }
}

In your adapter's getView method, either wrap your convertView as a child in a new GridViewItemContainer or make this one the top XML element of your item layout:

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

Where numColumns is the number of columns in the GridView and 'viewsInRow' is an list of View on the current row of where 'position' is located.

等风来 2024-12-13 04:20:51

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

我的问题是如何正确调整 gridview 项目的高度。当所有视图的高度相同时,此网格视图效果很好。但是,当您的视图具有不同的高度时,网格不会按预期运行。视图将相互重叠,形成一个美观的网格。

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

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

I did so many research but found incomplete answer or had tough with understanding what going on with solution but finally found an answer that fit perfectly with proper explanation.

My problem was to fit gridview item into height properly. This Grid-view worked great when all of your views are the same height. But when your views have different heights, the grid doesn't behave as expected. Views will overlap each other, causing an an-aesthetically pleasing grid.

Here Solution I used this class in XML layout.

I used this solution, and this is working very well, thanks a lot.--Abhishek Mittal

记忆で 2024-12-13 04:20:51

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

If you convert your GridView or ListView to a RecyclerView, this issue will not happen. And you won't need to make a custom GridView class.

安人多梦 2024-12-13 04:20:51

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

只需从 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" />

This is not the correct solution which I am mentioned below, but can be workaround depends on your requirement.

Just set the height of view fix(in some dp i.e.- 50dp) from your child layout of gridview, so that it can be Wrapped.

        <TextView
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:ellipsize="end"
            android:textColor="@color/text_color"
            android:textSize="13dp"
            android:textStyle="normal" />
峩卟喜欢 2024-12-13 04:20:51

为 GridView 赋予权重也适用于作为子项的 LinearLayouts 内的 GridView。这样,GridView 用它的子项填充视口,这样只要它们适合屏幕(然后滚动),您就可以查看它的项目。

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

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

Giving weight to your GridView also works on GridViews inside LinearLayouts as a child. This way GridView fills the viewport with its children so you are able to view it's items as long as they fit the screen (then you scroll).

But always avoid using GridViews inside ScrollViews. Otherwise you will need to calculate each child's height and reassign them as Chase answered above.

<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"/>
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文