安卓Listview测量高度

4
我正在尝试找到一种根据其子项高度设置列表视图高度的方法。我按照此处给出的解决方案进行了操作: 如何在不使其折叠的情况下将ListView放入ScrollView中?
public void setListViewHeightBasedOnChildren(ListView listView) {
    ListAdapter listAdapter = listView.getAdapter();
    if (listAdapter == null) {
        // pre-condition
        return;
    }

    int totalHeight = 0;

    for (int i = 0; i < listAdapter.getCount(); i++) {
        View listItem = listAdapter.getView(i, null, listView);

        if(listItem != null){
            listItem.setLayoutParams(new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT));                
            listItem.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
            //listItem.measure(0, 0);
            totalHeight += listItem.getMeasuredHeight();
        }
    }

    ViewGroup.LayoutParams params = listView.getLayoutParams();
    params.height = totalHeight + (listView.getDividerHeight() * (listAdapter.getCount() - 1));
    listView.setLayoutParams(params);
    listView.requestLayout();
}

现在我像这样在listview上调用上述函数:
public void displayReviews(ArrayList<Reviews> resultReviews){
    // Hide the loading progress
    hideReviewsLoading();

    if(resultReviews != null && resultReviews.size() > 0){
        mCurrentReviewList.onFetchFinished(resultReviews);
        setListViewHeightBasedOnChildren(mCurrentReviewList.getListView());
    }
    else{
        // Display a generic text to indicate no reviews are in yet
        displayEmptyText();
    }
}

这里的mCurrentreviewList是一个列表碎片,它基本上具有一个适配器,用于在布局中设置元素。

我的问题是,它测量每个列表项的高度不准确。因此,最终当所有列表项(评论)都被填充时,包含它的整个ListView从下面切断了一部分,只显示了7.5个评论,而不是总共10个。

我不确定自己做错了什么。非常感谢您提供任何帮助和指导!


你确定在这里使用 ListView 是你想要的吗? - sdabet
@fiddler 是的,我认为ListView是我要实现的最佳布局解决方案。这是我画的一个初步布局,以解释我想要实现的内容。https://www.dropbox.com/s/6mhf2xfqwkz2zsi/2014-02-06%2022.56.35.jpg我正在使用TabHost,每个选项卡都会启动一个带有上述FragmentList的片段。因此,在那个小窗口中,我不需要实际滚动的列表视图。 500px应用程序中可以找到实现的示例。(截图:https://www.dropbox.com/s/h4q5poiqfvdpmwa/2014-02-07%2006.57.33.png),Google Plus应用程序中也有。 - falc0nit3
好的,看起来我已经能够找出为什么测量结果不准确了。对于具有可变/动态高度的ListItem,这将行不通。测量必须知道至少一个变量,在这种情况下它将是常量宽度。因此,首先通过使用以下方法计算ListView所需的宽度(您知道它将是常量):int desiredWidth = MeasureSpec.makeMeasureSpec(listView.getWidth(), MeasureSpec.AT_MOST);然后将其传递给listitem measure函数。 - falc0nit3
4个回答

32

好的,看起来我已经能够找出为什么测量结果不准确了。我试图通过传递

listview的高度来获取一个测量值。
listItem.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));

对于高度变化的ListItem,这种方法并不适用。测量必须至少知道一个变量,而在这种情况下,它将是固定的宽度。因此,首先通过使用以下方法计算您知道将保持恒定的ListView的所需宽度:

int desiredWidth = MeasureSpec.makeMeasureSpec(listView.getWidth(), MeasureSpec.AT_MOST);

然后将其传递给listitem measure。完整代码如下:
public void setListViewHeightBasedOnChildren(ListView listView) {
    ListAdapter listAdapter = listView.getAdapter();
    if (listAdapter == null) {
        // pre-condition
        return;
    }

    int totalHeight = listView.getPaddingTop() + listView.getPaddingBottom();
    int desiredWidth = MeasureSpec.makeMeasureSpec(listView.getWidth(), MeasureSpec.AT_MOST);
    for (int i = 0; i < listAdapter.getCount(); i++) {
        View listItem = listAdapter.getView(i, null, listView);

        if(listItem != null){
            // This next line is needed before you call measure or else you won't get measured height at all. The listitem needs to be drawn first to know the height.
            listItem.setLayoutParams(new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT));
            listItem.measure(desiredWidth, MeasureSpec.UNSPECIFIED);
            totalHeight += listItem.getMeasuredHeight();

        }
    }

    ViewGroup.LayoutParams params = listView.getLayoutParams();
    params.height = totalHeight + (listView.getDividerHeight() * (listAdapter.getCount() - 1));
    listView.setLayoutParams(params);
    listView.requestLayout();
}

请注意,这是一种hackish的实现,不是推荐的方法。在实施之前,请参阅如何将ListView放入ScrollView而不使其折叠?并了解您正在进行的操作。


1
我不得不更改为int desiredWidth = View.MeasureSpec.makeMeasureSpec(listView.getWidth(), View.MeasureSpec.EXACTLY); - Okku
3
哇,这真是让我高兴的一天!我之前使用0,0进行测量,结果数据不准确,但是当我改为使用实际宽度和AT_MOST后,它完美地工作了!! - Justin
@falc0nit3 - 我遇到了一个问题。当我的应用程序首次加载时,它可以正确测量并设置必要的高度。但是,如果我的活动被重新创建,我的ListView的高度会增加三倍,但其中包含相同数量的项目。我注意到,如果我使用计时器延迟创建我的ListView(500毫秒),问题就会消失,并且在重新创建活动时再次获得正确的高度。我想知道为什么?顺便说一句,这是一个选项卡中的片段活动。 - Edmond Tamas
是的,我的问题通过这一重要代码得到解决 - listItem.setLayoutParams(new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT)); 各位不要错过这行代码。 - Ranjithkumar
问题已经用“确切的方法”解决了...但是如果我在一个ListView下面使用另一个ListView,那么两个ListView之间会显示一些空白!问题出在哪里?提前致谢! - Zen Bhatt
显示剩余2条评论

1
ListView应该可以滚动 - 所以理论上,您可以在其中拥有无限数量的项目。不需要根据其中包含的项目数设置ListView的长度。
如果您确实需要设置长度,则这将违反使用ListView的目的。请对于您所需的内容使用其他视图,如Vertical ScrollView等。

感谢HariKris的回复,我意识到ListView的主要功能是作为一个可以滚动的项目列表收集器。我不确定如何使用类似于我现在使用LinearLayouts的ListAdapter来实现一个带有分隔符的项目列表。请查看我的上面评论以查看我正在尝试实现的布局。有没有一种方法可以在不使用ListView的情况下实现相同的效果? - falc0nit3
我认为ListView是你需要的。只需确保在布局中将屏幕的剩余空间分配给ListView。然后您可以添加尽可能多的评论项。ListView会自然滚动,无需为其分配固定大小。在xml文件中确保使用剩余屏幕布局的空间来放置ListView。 - VJ Vélan Solutions
如果我现在基本上不调用“setListViewHeightBasedOnChildren”函数来重置列表视图的高度,它将表现为您所描述的那样。我已经将剩余的空间分配给了列表视图的高度。但它只有我的手机屏幕大小的一半(我使用的是4.7英寸的手机)。在较小的手机上,列表视图的窗口将非常小,用户需要不断滚动才能阅读单个项目。(可能每次只有4-6行。)这绝对不会看起来很好。500px和Google Plus已经很好地实现了它,当然还有其他应用程序。不确定如何实现。 - falc0nit3

0

falc0nit3的答案(被接受的)对我有用,但是如果你使用列表分隔符,我建议在for循环之后添加以下代码。

totalHeight += listView.getDividerHeight() * i;

因此,最终的代码片段将如下所示:

public static void setListViewHeightBasedOnChildren(ListView listView) {
    ListAdapter listAdapter = listView.getAdapter();
    if (listAdapter == null)
        return;

    int desiredWidth = View.MeasureSpec.makeMeasureSpec(listView.getWidth(), View.MeasureSpec.UNSPECIFIED);
    int totalHeight = 0;
    View view = null;
    int i;
    for (i = 0; i < listAdapter.getCount(); i++) {
        view = listAdapter.getView(i, view, listView);
        if (i == 0)
            view.setLayoutParams(new ViewGroup.LayoutParams(desiredWidth, ViewGroup.LayoutParams.WRAP_CONTENT));

        view.measure(desiredWidth, View.MeasureSpec.UNSPECIFIED);
        totalHeight += view.getMeasuredHeight();

    }

    //add divider height to total height as many items as there are in listview
    totalHeight += listView.getDividerHeight()*i;
    ViewGroup.LayoutParams params = listView.getLayoutParams();
    params.height = totalHeight + (listView.getDividerHeight() * (listAdapter.getCount() - 1));
    listView.setLayoutParams(params);
}

0

我没有足够的声望来进行评论,但是关于 falc0nit3 的回答,我已经实现了这一点,但最终在具有图像的大单元格中遇到了内存溢出错误。

这是由于循环的第一行 View listItem = listAdapter.getView(i, null, listView);

将 null 作为 convertView 参数传递将导致适配器膨胀列表中的每一行。 这不是理想的方法,因此最好传入一个已经膨胀的行,以便 getView 只需替换该行中的内容(例如,自己膨胀列表的第一行并继续使用它)。 请参见以下链接中的“视图回收” http://lucasr.org/2012/04/05/performance-tips-for-androids-listview/


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接