我在解决这个问题时搜索了很多方案,但唯一的答案似乎是 "不要把ListView放进ScrollView中"。然而,我还没有看到任何真正的解释 为什么 不应该这样做。唯一能找到的原因是Google认为你不应该这样做。但是我想这样做,所以我尝试了。
那么问题来了,如何将ListView置于ScrollView中而不使其折叠到最小高度?
我在解决这个问题时搜索了很多方案,但唯一的答案似乎是 "不要把ListView放进ScrollView中"。然而,我还没有看到任何真正的解释 为什么 不应该这样做。唯一能找到的原因是Google认为你不应该这样做。但是我想这样做,所以我尝试了。
那么问题来了,如何将ListView置于ScrollView中而不使其折叠到最小高度?
这是我的解决方案。我对Android平台还比较新,我相信这有点像hackish,特别是关于直接调用.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);
}
}
LinearLayout
没有 ListView
所具有的所有内在优点,如分隔线、页眉/页脚视图、与手机 UI 主题颜色匹配的列表选择器等等。说实话,没有理由不能滚动内部容器直到到达末尾,然后停止拦截触摸事件,以便外部容器滚动。每个桌面浏览器在使用滚轮时都会这样做。如果你通过轨迹球导航,Android 本身可以处理嵌套滚动,那么为什么不通过触摸呢? - Neil TraftlistItem
是ViewGroup
的实例,那么执行listItem.measure(0,0)
会抛出空指针异常。在listItem.measure
语句之前,我添加了以下代码:if (listItem instanceof ViewGroup) listItem.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
- Siklab.phint totalHeight = listView.getPaddingTop() + listView.getPaddingBottom();
- PaulListView
中的项具有可变高度时,使用此方法存在一些缺陷。调用 getMeasuredHeight()
仅返回目标最小高度,而不是实际高度。我尝试使用 getHeight()
,但这会返回 0。请问有谁知道如何解决这个问题? - Matt HugginsListView 来使其不滚动非常昂贵,而且与 ListView
的整个目的相违背。你不应该这样做。只需使用 LinearLayout
即可。
这肯定会起作用……
你只需将布局XML文件中的<ScrollView></ScrollView>
替换为此自定义ScrollView,例如:<com.tmd.utils.VerticalScrollview></com.tmd.utils.VerticalScrollview>
package com.tmd.utils;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.ScrollView;
public class VerticalScrollview extends ScrollView{
public VerticalScrollview(Context context) {
super(context);
}
public VerticalScrollview(Context context, AttributeSet attrs) {
super(context, attrs);
}
public VerticalScrollview(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
final int action = ev.getAction();
switch (action)
{
case MotionEvent.ACTION_DOWN:
Log.i("VerticalScrollview", "onInterceptTouchEvent: DOWN super false" );
super.onTouchEvent(ev);
break;
case MotionEvent.ACTION_MOVE:
return false; // redirect MotionEvents to ourself
case MotionEvent.ACTION_CANCEL:
Log.i("VerticalScrollview", "onInterceptTouchEvent: CANCEL super false" );
super.onTouchEvent(ev);
break;
case MotionEvent.ACTION_UP:
Log.i("VerticalScrollview", "onInterceptTouchEvent: UP super false" );
return false;
default: Log.i("VerticalScrollview", "onInterceptTouchEvent: " + action ); break;
}
return false;
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
super.onTouchEvent(ev);
Log.i("VerticalScrollview", "onTouchEvent. action: " + ev.getAction() );
return true;
}
}
与其将ListView
放在ScrollView
中,我们可以将ListView
作为ScrollView
使用。需要放在ListView
中的内容可以放在ListView
中。顶部和底部的其他布局可以通过将布局添加到ListView
的头部和尾部来放置。因此,整个ListView
将给您滚动的体验。
有许多情况下,将ListView放在ScrollView中非常合理。
以下是基于DougW的建议编写的代码...可在片段中使用,占用更少的内存。
public static void setListViewHeightBasedOnChildren(ListView listView) {
ListAdapter listAdapter = listView.getAdapter();
if (listAdapter == null) {
return;
}
int desiredWidth = MeasureSpec.makeMeasureSpec(listView.getWidth(), MeasureSpec.AT_MOST);
int totalHeight = 0;
View view = null;
for (int i = 0; i < listAdapter.getCount(); i++) {
view = listAdapter.getView(i, view, listView);
if (i == 0) {
view.setLayoutParams(new ViewGroup.LayoutParams(desiredWidth, LayoutParams.WRAP_CONTENT));
}
view.measure(desiredWidth, MeasureSpec.UNSPECIFIED);
totalHeight += view.getMeasuredHeight();
}
ViewGroup.LayoutParams params = listView.getLayoutParams();
params.height = totalHeight + (listView.getDividerHeight() * (listAdapter.getCount() - 1));
listView.setLayoutParams(params);
listView.requestLayout();
}
在每个嵌入的列表视图上调用setListViewHeightBasedOnChildren(listview)
。
ListView实际上已经能够测量自身的高度以足够显示所有项,但当您仅指定wrap_content (MeasureSpec.UNSPECIFIED)时,它不会执行此操作。当给定一个高度为MeasureSpec.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,并设置一个非常大的值 (Integer.MAX_VALUE >> 4),会导致 ListView 测量所有子项的高度,然后根据其高度设置 ListView 的高度。
与其他解决方案相比,这种方法有几个优点:
缺点是,你可能会认为这是依赖于SDK中未记录的行为,这可能会发生变化。另一方面,你可能会认为这才是 ListView 中 wrap_content 应该工作的方式,而当前的 wrap_content 行为实际上是错误的。
如果你担心这种行为可能会在将来发生变化,那么你应该简单地复制 ListView.java 中的 onMeasure 函数和相关函数到自己的子类中,然后使 UNSPECIFIED 路径通过 onMeasure 运行 AT_MOST 路径。
顺便说一下,我认为这是在处理少量列表项时完全有效的方法。与 LinearLayout 相比可能效率低下,但当列表项较少时,使用 LinearLayout 是不必要的优化,因此是不必要的复杂性。
有一个内置的设置。在ScrollView上:
android:fillViewport="true"
在Java中,
mScrollView.setFillViewport(true);
Romain Guy在这里深入解释了它:http://www.curious-creature.org/2010/08/15/scrollviews-handy-trick/
您可以创建一个不可滚动的自定义 ListView
public class NonScrollListView extends ListView {
public NonScrollListView(Context context) {
super(context);
}
public NonScrollListView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public NonScrollListView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int heightMeasureSpec_custom = MeasureSpec.makeMeasureSpec(
Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, heightMeasureSpec_custom);
ViewGroup.LayoutParams params = getLayoutParams();
params.height = getMeasuredHeight();
}
}
在您的布局资源文件中。<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<!-- com.Example Changed with your Package name -->
<com.Example.NonScrollListView
android:id="@+id/lv_nonscroll_list"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
</com.Example.NonScrollListView>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/lv_nonscroll_list" >
<!-- Your another layout in scroll view -->
</RelativeLayout>
</RelativeLayout>
在Java文件中:
创建自定义列表视图的对象,而不是ListView,如下所示: NonScrollListView non_scroll_list = (NonScrollListView) findViewById(R.id.lv_nonscroll_list);
我们无法同时使用两个滚动条。我们需要获取ListView的总长度,并根据总高度扩展ListView。然后我们可以直接将ListView添加到ScrollView中,或者使用LinearLayout,因为ScrollView只有一个子元素。 在您的代码中复制setListViewHeightBasedOnChildren(lv)方法并扩展listview,然后您就可以在scrollview中使用listview了。 \ layout xml文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<ScrollView
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#1D1D1D"
android:orientation="vertical"
android:scrollbars="none" >
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#1D1D1D"
android:orientation="vertical" >
<TextView
android:layout_width="fill_parent"
android:layout_height="40dip"
android:background="#333"
android:gravity="center_vertical"
android:paddingLeft="8dip"
android:text="First ListView"
android:textColor="#C7C7C7"
android:textSize="20sp" />
<ListView
android:id="@+id/first_listview"
android:layout_width="260dp"
android:layout_height="wrap_content"
android:divider="#00000000"
android:listSelector="#ff0000"
android:scrollbars="none" />
<TextView
android:layout_width="fill_parent"
android:layout_height="40dip"
android:background="#333"
android:gravity="center_vertical"
android:paddingLeft="8dip"
android:text="Second ListView"
android:textColor="#C7C7C7"
android:textSize="20sp" />
<ListView
android:id="@+id/secondList"
android:layout_width="260dp"
android:layout_height="wrap_content"
android:divider="#00000000"
android:listSelector="#ffcc00"
android:scrollbars="none" />
</LinearLayout>
</ScrollView>
</LinearLayout>
Activity类中的onCreate方法:
import java.util.ArrayList;
import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ListAdapter;
import android.widget.ListView;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.listview_inside_scrollview);
ListView list_first=(ListView) findViewById(R.id.first_listview);
ListView list_second=(ListView) findViewById(R.id.secondList);
ArrayList<String> list=new ArrayList<String>();
for(int x=0;x<30;x++)
{
list.add("Item "+x);
}
ArrayAdapter<String> adapter=new ArrayAdapter<String>(getApplicationContext(),
android.R.layout.simple_list_item_1,list);
list_first.setAdapter(adapter);
setListViewHeightBasedOnChildren(list_first);
list_second.setAdapter(adapter);
setListViewHeightBasedOnChildren(list_second);
}
public static 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);
listItem.measure(0, 0);
totalHeight += listItem.getMeasuredHeight();
}
ViewGroup.LayoutParams params = listView.getLayoutParams();
params.height = totalHeight
+ (listView.getDividerHeight() * (listAdapter.getCount() - 1));
listView.setLayoutParams(params);
}
这是我唯一行之有效的方法:
在Lollipop版本及以上,您可以使用
yourtListView.setNestedScrollingEnabled(true);
启用或禁用此视图的嵌套滚动。如果需要向后兼容旧版本的操作系统,则必须使用RecyclerView。
ListView
内。ListView
可以拥有多个行样式,以及非可选行。 - CommonsWare