我到处寻找这个问题的解决方案,我能找到的唯一答案似乎是“不要把一个ListView放入一个ScrollView”。不过,我还没有看到任何真正的解释。我能找到的唯一原因似乎是谷歌认为你不应该这么做。我知道,所以我做了。
问题是,如何将ListView放到ScrollView中而不缩到最小高度?
我到处寻找这个问题的解决方案,我能找到的唯一答案似乎是“不要把一个ListView放入一个ScrollView”。不过,我还没有看到任何真正的解释。我能找到的唯一原因似乎是谷歌认为你不应该这么做。我知道,所以我做了。
问题是,如何将ListView放到ScrollView中而不缩到最小高度?
当前回答
虽然建议的setListViewHeightBasedOnChildren()方法在大多数情况下都可以工作,但在某些情况下,特别是对于许多项,我注意到最后一个元素不显示。所以我决定模仿一个简单版本的ListView行为,以便重用任何适配器代码,这里是ListView的替代方案:
import android.content.Context;
import android.database.DataSetObserver;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.ListAdapter;
public class StretchedListView extends LinearLayout {
private final DataSetObserver dataSetObserver;
private ListAdapter adapter;
private OnItemClickListener onItemClickListener;
public StretchedListView(Context context, AttributeSet attrs) {
super(context, attrs);
setOrientation(LinearLayout.VERTICAL);
this.dataSetObserver = new DataSetObserver() {
@Override
public void onChanged() {
syncDataFromAdapter();
super.onChanged();
}
@Override
public void onInvalidated() {
syncDataFromAdapter();
super.onInvalidated();
}
};
}
public void setAdapter(ListAdapter adapter) {
ensureDataSetObserverIsUnregistered();
this.adapter = adapter;
if (this.adapter != null) {
this.adapter.registerDataSetObserver(dataSetObserver);
}
syncDataFromAdapter();
}
protected void ensureDataSetObserverIsUnregistered() {
if (this.adapter != null) {
this.adapter.unregisterDataSetObserver(dataSetObserver);
}
}
public Object getItemAtPosition(int position) {
return adapter != null ? adapter.getItem(position) : null;
}
public void setSelection(int i) {
getChildAt(i).setSelected(true);
}
public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
this.onItemClickListener = onItemClickListener;
}
public ListAdapter getAdapter() {
return adapter;
}
public int getCount() {
return adapter != null ? adapter.getCount() : 0;
}
private void syncDataFromAdapter() {
removeAllViews();
if (adapter != null) {
int count = adapter.getCount();
for (int i = 0; i < count; i++) {
View view = adapter.getView(i, null, this);
boolean enabled = adapter.isEnabled(i);
if (enabled) {
final int position = i;
final long id = adapter.getItemId(position);
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (onItemClickListener != null) {
onItemClickListener.onItemClick(null, v, position, id);
}
}
});
}
addView(view);
}
}
}
}
其他回答
这是我的解决方案。我对Android平台相当陌生,我确信这有点笨拙,特别是在直接调用.measure和设置LayoutParams的部分。直接使用Height属性,但它是有效的。
你所要做的就是调用Utility.setListViewHeightBasedOnChildren(yourListView),它将被调整大小,以完全适应其项目的高度。
public class Utility {
public static void setListViewHeightBasedOnChildren(ListView listView) {
ListAdapter listAdapter = listView.getAdapter();
if (listAdapter == null) {
// pre-condition
return;
}
int totalHeight = listView.getPaddingTop() + listView.getPaddingBottom();
for (int i = 0; i < listAdapter.getCount(); i++) {
View listItem = listAdapter.getView(i, null, listView);
if (listItem instanceof ViewGroup) {
listItem.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
}
listItem.measure(0, 0);
totalHeight += listItem.getMeasuredHeight();
}
ViewGroup.LayoutParams params = listView.getLayoutParams();
params.height = totalHeight + (listView.getDividerHeight() * (listAdapter.getCount() - 1));
listView.setLayoutParams(params);
}
}
感谢Vinay的代码,这是我的代码,当你不能在滚动视图中有一个列表视图时,你需要这样的东西
LayoutInflater li = LayoutInflater.from(this);
RelativeLayout parent = (RelativeLayout) this.findViewById(R.id.relativeLayoutCliente);
int recent = 0;
for(Contatto contatto : contatti)
{
View inflated_layout = li.inflate(R.layout.header_listview_contatti, layout, false);
inflated_layout.setId(contatto.getId());
((TextView)inflated_layout.findViewById(R.id.textViewDescrizione)).setText(contatto.getDescrizione());
((TextView)inflated_layout.findViewById(R.id.textViewIndirizzo)).setText(contatto.getIndirizzo());
((TextView)inflated_layout.findViewById(R.id.textViewTelefono)).setText(contatto.getTelefono());
((TextView)inflated_layout.findViewById(R.id.textViewMobile)).setText(contatto.getMobile());
((TextView)inflated_layout.findViewById(R.id.textViewFax)).setText(contatto.getFax());
((TextView)inflated_layout.findViewById(R.id.textViewEmail)).setText(contatto.getEmail());
RelativeLayout.LayoutParams relativeParams = new RelativeLayout.LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT);
if (recent == 0)
{
relativeParams.addRule(RelativeLayout.BELOW, R.id.headerListViewContatti);
}
else
{
relativeParams.addRule(RelativeLayout.BELOW, recent);
}
recent = inflated_layout.getId();
inflated_layout.setLayoutParams(relativeParams);
//inflated_layout.setLayoutParams( new RelativeLayout.LayoutParams(source));
parent.addView(inflated_layout);
}
relativeLayout保持在ScrollView中,所以它都是可滚动的:)
ListView实际上已经能够测量自身的高度以显示所有项,但是当您简单地指定wrap_content (measurespect . unspecified)时,它不能这样做。当给出一个带有measurespect . at_most的高度时,它将这样做。有了这些知识,你可以创建一个非常简单的子类来解决这个问题,它比上面发布的任何解决方案都要好得多。您仍然应该对这个子类使用wrap_content。
public class ListViewForEmbeddingInScrollView extends ListView {
public ListViewForEmbeddingInScrollView(Context context) {
super(context);
}
public ListViewForEmbeddingInScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ListViewForEmbeddingInScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 4, MeasureSpec.AT_MOST));
}
}
操纵heightMeasureSpec为AT_MOST,具有非常大的大小(整数。MAX_VALUE >> 4)导致ListView测量它的所有子节点直到给定的(非常大的)高度,并相应地设置它的高度。
这比其他解决方案更好,原因如下:
它可以正确测量所有内容(填充,分隔线) 它在测量过程中测量ListView 由于#2,它正确地处理宽度或项目数量的变化,而不需要任何额外的代码
缺点是,您可能会认为这样做依赖于SDK中未记录的行为,而这些行为可能会更改。另一方面,你可能认为这就是wrap_content与ListView一起工作的方式,而当前的wrap_content行为只是被破坏了。
如果你担心行为可能会在未来发生变化,你应该简单地复制onMeasure函数和相关函数从ListView.java和到你自己的子类,然后使AT_MOST路径通过onMeasure运行为未指定的以及。
顺便说一下,我相信当您处理少量列表项时,这是一种非常有效的方法。与线性布局相比,它可能效率较低,但当项目数量较少时,使用线性布局是不必要的优化,因此也会造成不必要的复杂性。
它有一个内置的设置。在ScrollView中:
android:fillViewport="true"
在Java中,
mScrollView.setFillViewport(true);
罗曼·盖伊在这里有深入的解释:http://www.curious-creature.org/2010/08/15/scrollviews-handy-trick/
如果LinearLayout有一个setAdapter方法,整个问题就会消失,因为当你告诉别人使用它时,替代方案将是微不足道的。
如果你想要一个滚动的ListView在另一个滚动视图中,这不会有帮助,但至少会给你一个想法。
您需要创建一个自定义适配器来组合要滚动的所有内容,并将ListView的适配器设置为该内容。
我手头没有样本代码,但如果你想要。
<ListView/>
(other content)
<ListView/>
然后需要创建一个表示所有这些内容的适配器。ListView/Adapters也足够智能,可以处理不同的类型,但是您需要自己编写适配器。
android的UI API并不像其他平台那么成熟,所以它不像其他平台那么精致。此外,当你在android上做一些事情时,你需要以android (unix)的心态来做任何事情,你可能不得不将更小的部分的功能组装起来,并编写一堆自己的代码来让它工作。