计算列表视图的大小或如何让其完全展开

37

我目前在尝试将ListView放置在ScrollView中。根据我所了解的,这种做法不被推荐,但我想让ListView完全展开,并显示所有行,以便无需滚动。然而,我一直在努力找到方法告诉ListView在需要定义高度时如何完全展开以显示所有行。我该如何在绘制之前计算完全展开的ListView的高度呢?

这个问题主要源于不能在另一个可滚动视图中放置一个可滚动视图。只要我能够使ListView扩展展示所有行,即使它无法滚动,我也没关系。然而,如果没有为其指定高度,我无法实现这一点,而这似乎需要计算。

我的整个布局对于“物理”屏幕来说太大了,需要滚动才能显示余下的列表和底部的按钮。我想表达的是,“虚拟”屏幕即使没有ListView也太大而无法适合一个屏幕。


“完全展开”是什么意思? - CommonsWare
通过完全展开,我指的是显示它的所有行。问题已经适当编辑以解释这一点。 - Rudy Mutter
如果您发布您尝试完成的内容的屏幕截图或草图可能会有所帮助。我很难理解为什么“尽可能扩展,然后滚动如果有太多项目无法适合”的默认行为不足。 - Cheryl Simon
12个回答

57

感谢Rudy,他的建议非常有帮助。以下是如何实现:

1)创建一个继承ListView的新类:

package com.example.android.views;

import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.widget.ListView;

public class ExpandedListView extends ListView {

    private android.view.ViewGroup.LayoutParams params;
    private int old_count = 0;

    public ExpandedListView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        if (getCount() != old_count) {
            old_count = getCount();
            params = getLayoutParams();
            params.height = getCount() * (old_count > 0 ? getChildAt(0).getHeight() : 0);
            setLayoutParams(params);
        }

        super.onDraw(canvas);
    }

}

2) ... 最后将新视图添加到您的xml布局文件中:

<com.example.android.views.ExpandedListView
    android:id="@+id/list"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:scrollbars="none"
    android:padding="0px"
    />

14
我还没有尝试过,但从我所看到的情况来看,它只有在所有元素的高度与getChildAt(0)相同(或者如果这些视图的平均大小恰巧等于getChildAt(0).getHeight())时才有效。 - J-Rou
有人知道我该如何在Xamarin.Android中使用上述内容吗? - Dave Haigh

14

计算高度的正确方式是:

int height = 0;
for (int i = 0; i < listView.getChildCount(); i++) {
    height += listView.getChildAt(i).getMeasuredHeight();
    height += listView.getDividerHeight();
}

11

注意!正确计算高度的方法不是

params.height = getCount() * (old_count > 0 ? getChildAt(0).getHeight() : 0);
我们必须添加每个(减一)分隔符的高度。
if(oldCount > 0 && getCount() > 0)
    params.height = getCount()
                  * (getChildAt(0).getHeight() + getDividerHeight())
                  - getDividerHeight();
else
    params.height = 0;

1
请注意,该解决方案仍然假定所有项目高度相同,并且没有页脚或页眉。 - a.ch.
listView.getChildAt(0) 为空。 - AndroidGuy

4
@Override
public void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
    if (this.isExpanded) {
        final int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
        super.onMeasure(widthMeasureSpec, expandSpec);
        final ViewGroup.LayoutParams params = this.getLayoutParams();
        params.height = this.getMeasuredHeight();
    } else {
       super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
}

3
如果您想要一个不会滚动的ListView,那么您可以使用LinearLayout吗?

1
我希望保持原生 Android 列表的外观,而不必自己编写。 - Rudy Mutter
你是指高亮状态吗?如果是的话,我相信你可以使用android.R.drawable.list_selector_background来获得相同的行为。 - jqpubliq
是的,除了默认的标题和页脚功能、列表渐变分隔符以及适配器构造的使用外,我可能会尝试通过复制所有内置的Android样式来制作ListView的克隆版本,但如果可能的话,我想避免这种情况。 - Rudy Mutter
我也曾经遇到这个问题。你建议直接使用LinearLayout的想法让我恍然大悟。现在完美解决了问题,生活变得更简单了。谢谢! - Jere.Jones

2

不应该将ListView放置在ScrollView中。ListView本身已经可以滚动。

如果ListView是布局中唯一的元素,则默认情况下应该完全展开。

如果ListView不是布局中唯一的元素,并且您想让ListView扩展以占用所有可用空间,请将ListView的layout_weight设置为1,而其他所有layout_weight=0。

<LinearLayout android:layout_width="fill_parent" 
             android:layout_height="fill_parent" orientation="vertical">
  <TextView id="@+id/title" 
             android:layout_width="wrap_content" 
            android:layout_height="wrap_content"/>
  <ListView id="@+id/listView" 
            android:layout_width="fill_parent" 
            android:layout_height="0dp" 
            android:layout_weight="1"/>
</LinearLayout>

编辑: ListView 真的是设计成可滚动的......你屏幕截图中的布局似乎不太像“安卓方式”。

然而,如果你真的想绕过这个问题,可以尝试膨胀其中一行,获取其最小高度,并将其乘以 ListView 适配器中的项目数。

LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View view = inflater.inflate(R.layout.label_value_row, null, false);
int height = adapter.getCount() * view.getMinHeight();

然而,如果ListView中显示了所有行,则其将无法滚动。我的问题是,我需要在布局中显示更多视图,而不仅仅是适合一个屏幕的视图。我不希望ListView只扩展到屏幕大小,我希望它能够扩展以显示所有行。 - Rudy Mutter
1
ListView会在屏幕上无法容纳所有内容时自动滚动。根据ListView API的定义,ListView是“以垂直滚动列表形式展示项目的视图”。滚动功能已经内置。试一试吧! - Cheryl Simon
即使没有ListView,屏幕上的其他视图也太高了,无法适应,因此我需要覆盖视图成为ScrollView。我希望ListView自动调整大小,足以显示所有行。我可以通过在其LayoutParams中设置固定高度来实现这一点,但是列表是动态的,因此我正在尝试找出计算此高度的方法。请参见我提供的问题编辑。 - Rudy Mutter
所以这是正确的,但是我的 ListView 中的数据是从数据库动态获取的,并且每行的实际高度将根据文本换行方式而变化。在将 ListView 的高度设置为此估计值后,是否有任何方法可以弄清实际高度比估计值少还是多?顺便说一下,非常感谢您迄今为止的帮助,非常感谢。 - Rudy Mutter
你可以比较可见子项的数量和适配器中子项的数量,但这似乎会变得非常混乱。老实说,像jqpubliq建议的LinearLayout可能更好一些。ListView真的是为了滚动而设计的...它懒惰地加载它的内容,所以只有可见组件的大小在开始滚动之前才会确定。复制ListView的单击行为并不那么难。 - Cheryl Simon

1
我一直在研究如何做到这一点,尽管问题已经得到了回答,但我想提供一个我认为更好的解决方案:
查看ListView的源代码,您可以在onMeasure(...)代码中看到以下内容:
if (heightMode == MeasureSpec.AT_MOST) {
     // TODO: after first layout we should maybe start at the first visible position, not 0
     heightSize = measureHeightOfChildren(widthMeasureSpec, 0, NO_POSITION, heightSize, -1);
}

所以只需调用listView的measure方法,并将高度参数传递为MeasureSpec.AT_MOST,而不是通常的MeasureSpec.UNSPECIFIED。

我使用以下代码来测量ListView并将其放置在弹出窗口中:

    int widthMeasureSpec = MeasureSpec.makeMeasureSpec(displayMetrics.widthPixels, MeasureSpec.EXACTLY);
    int heightMeasureSpec = MeasureSpec.makeMeasureSpec(displayMetrics.heightPixels, MeasureSpec.AT_MOST);
    mCatalogView.measure(widthMeasureSpec, heightMeasureSpec);

    mPopup.setWidth(mCatalogView.getMeasuredWidth());
    mPopup.setHeight(mCatalogView.getMeasuredHeight());

1

use this will be worked:

public class ListViewEx extends ListView {

public ListViewEx(Context context) {
    super(context);
}

/**
 * @param context
 * @param attrs
 */
public ListViewEx(Context context, AttributeSet attrs) {
    super(context, attrs);
}

/**
 * 设置不滚动
 */
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2,MeasureSpec.AT_MOST);
    super.onMeasure(widthMeasureSpec, expandSpec);
}

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    return ev.getAction()==MotionEvent.ACTION_MOVE||super.dispatchTouchEvent(ev);
}

}


1

我使用了djunod的答案中的函数(静态类中的函数)

计算ListView大小

并将ondraw方法更改如下。在成功删除操作后它可以正常工作。

这是我的最终类

扩展ListView类:

private android.view.ViewGroup.LayoutParams params;
private int old_count = 0;

public ExpandedListView(Context context, AttributeSet attrs) {
    super(context, attrs);
}

@Override
protected void onDraw(Canvas canvas) {

    if (getCount() != old_count) {
        params = getLayoutParams();
        old_count = getCount();
        int totalHeight = 0;
        for (int i = 0; i < getCount(); i++) {
            this.measure(0, 0);
            totalHeight += getMeasuredHeight();
        }

        params = getLayoutParams();
        params.height = totalHeight + (getDividerHeight() * (getCount() - 1));
        setLayoutParams(params);
    }

    super.onDraw(canvas);
}

}

现在我的ListView在插入行后不会滚动而是自动扩展。在删除行后,此代码会计算出正确的ListView大小。我不知道它是如何工作的,但它确实有效。


我不知道它是如何工作的,但它确实在工作。LOL - fire in the hole

0

如果您有高度不同的项目,您需要使用以下方法:

public void setListViewHeightBasedOnChildren(ListView listView) {

    ListAdapter listAdapter = listView.getAdapter();
    if (listAdapter != null) {

        int numberOfItems = listAdapter.getCount();

        // Get total height of all items.
        int totalItemsHeight = 0;
        for (int itemPos = 0; itemPos < numberOfItems; itemPos++) {
            View item = listAdapter.getView(itemPos, null, listView);
            float px = 300 * (listView.getResources().getDisplayMetrics().density);
            item.measure(
                    View.MeasureSpec.makeMeasureSpec((int)px, View.MeasureSpec.AT_MOST),
                    View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
            int height = item.getMeasuredHeight();
            totalItemsHeight += height;
        }

        // Get total height of all item dividers.
        int totalDividersHeight = listView.getDividerHeight() *
                (numberOfItems - 1);

        // Set list height.
        ViewGroup.LayoutParams params = listView.getLayoutParams();
        params.height = totalItemsHeight + totalDividersHeight;
        listView.setLayoutParams(params);
        listView.requestLayout();
    }
}

它可以与任何ListView和任何项一起良好地运作


如果您的列表包含大约1000个项目,则此代码无法正常工作并会冻结用户界面。我甚至尝试删除for循环和getView方法,只针对第一个项目进行操作,但是bindView方法仍然会被调用1000次.. :( - AndroidGuy
当UI一次尝试渲染1000个元素时,你期望会发生什么呢? - Wackaloon

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