在工作中,曾多次碰到ScrollView嵌套ListView的问题,网上的解决方法有很多种,但是杂而不全。我试过很多种方法,它们各有利弊。
在这里我将会从使用ScrollView嵌套ListView结构的原因、这个结构碰到的问题、几种解决方案和优缺点比较,这4个方面来为大家阐述、分析、总结。
实际上不光是ListView,其他继承自AbsListView的类也适用,包括ExpandableListView、GridView等等,为了方便说明,以下均用ListView来代表。
一、 为什么要使用ScrollView嵌套ListView的奇怪的结构        
ScrollView和ListView都是滚动结构,按理说,这两个控件在UI上的功能是一样的,但是看看下面这个设计:

    这是天猫商城的确认订单的页面,ScrollView中嵌套了ExpandableListView,ExpandableListView上面有固定的一些控件,下面也有固定的一些控件,整体又要能够滚动。    列表数据要嵌在固定数据中间,并且作为整体一起滚动,有了这样的设计需求,于是就有了ScrollView嵌套ListView的奇怪结构。
二、 ScrollView、ListView嵌套结构碰到的问题   
不多说,直接看失败例子:   
  1. <ScrollView
  2. android:id="@+id/act_solution_1_sv"
  3. android:layout_width="fill_parent"
  4. android:layout_height="fill_parent">
  5. <LinearLayout
  6. android:layout_width="fill_parent"
  7. android:layout_height="wrap_content"
  8. android:orientation="vertical">
  9. <TextView
  10. android:layout_width="fill_parent"
  11. android:layout_height="wrap_content"
  12. android:text="\nListView上方数据\n" />
  13. <ListView
  14. android:id="@+id/act_solution_1_lv"
  15. android:layout_width="fill_parent"
  16. android:layout_height="wrap_content">
  17. </ListView>
  18. <TextView
  19. android:layout_width="fill_parent"
  20. android:layout_height="wrap_content"
  21. android:text="\nListView下方数据\n" />
  22. </LinearLayout>
  23. </ScrollView>

复制代码

   
ScrollView中只能放一个控件,一般都放LinearLayout,orientation属性值为vertical。在
LinearLayout中放需要呈现的内容。ListView也在其中,ListView的高度设为适应自身内容(wrap_content)。粗略一
看,应该没有什么问题。但是看下面的实际效果图:  

    图中黑框的部分就是ListView,里面放了20条数据,但是却只显示了1条。
    控件的属性设置上没有问题,但是为什么没有按照我的想法走呢?
    看看下面这个图:

是否有点明白了呢?原因就是scroll事件的消费处理以及ListView控件的高度设定问题。
    虽然我看源码也看了不少,但是要说出来却不知到该怎么下手,我是大概知道原因,但是不知道怎么整理完全。求高手赐教…

三、问题解决方案

1、手动设置ListView高度
    经过测试发现,在xml中直接指定ListView的高度,是可以解决这个问题的,但是ListView中的数据是可变的,实际高度还需要实际测量。于是手动代码设置ListView高度的方法就诞生了。

  1. /**
  2. * 动态设置ListView的高度
  3. * @param listView
  4. */
  5. public static void setListViewHeightBasedOnChildren(ListView listView) {
  6. if(listView == null) return;
  7. ListAdapter listAdapter = listView.getAdapter();
  8. if (listAdapter == null) {
  9. // pre-condition
  10. return;
  11. }
  12. int totalHeight = 0;
  13. for (int i = 0; i < listAdapter.getCount(); i++) {
  14. View listItem = listAdapter.getView(i, null, listView);
  15. listItem.measure(0, 0);
  16. totalHeight += listItem.getMeasuredHeight();
  17. }
  18. ViewGroup.LayoutParams params = listView.getLayoutParams();
  19. params.height = totalHeight + (listView.getDividerHeight() * (listAdapter.getCount() - 1));
  20. listView.setLayoutParams(params);
  21. }

复制代码

上面这个方法就是设定ListView的高度了,在为ListView设置了Adapter之后使用,就可以解决问题了。
    但是这个方法有个两个细节需要注意:
    
   一是Adapter中getView方法返回的View的必须由LinearLayout组成,因为只有LinearLayout才有
measure()方法,如果使用其他的布局如RelativeLayout,在调用listItem.measure(0,
0);时就会抛异常,因为除LinearLayout外的其他布局的这个方法就是直接抛异常的,没理由…。我最初使用的就是这个方法,但是因为子控件的顶
层布局是RelativeLayout,所以一直报错,不得不放弃这个方法。
        二是需要手动把ScrollView滚动至最顶端,因为使用这个方法的话,默认在ScrollView顶端的项是ListView,具体原因不了解,求大神解答…可以在Activity中设置:

  1. sv = (ScrollView) findViewById(R.id.act_solution_1_sv);

复制代码

2、使用单个ListView取代ScrollView中所有内容
    这个方法是我在试了几个方法都失败的情况下自己琢磨出来的。
    用一张图来解释这个方法的思想:

就是说,把整个需要放在ScrollView中的内容,统统放在ListView中,原ListView上方的数据和下方数据,都作为现ListView的一个itemView,和原ListView中的单条数据是平级的关系。
    xml布局方面十分简单:

  1. <ListView
  2. android:id="@+id/act_solution_2_lv"
  3. android:layout_width="fill_parent"
  4. android:layout_height="wrap_content">
  5. </ListView>

复制代码

一个单独的ListView就可以了。
    原ListView上方数据和下方数据,都写进两个xml布局文件中:

Java代码方面,需要自定义一个Adapter,在Adapter中的getView方法中进行position值的判断,根据position值来决定inflate哪个布局:

  1. public View getView(int position, View convertView, ViewGroup parent) {
  2. //列表第一项
  3. if(position == 0){
  4. convertView = inflater.inflate(R.layout.item_solution2_top, null);
  5. return convertView;
  6. }
  7. //列表最后一项
  8. else if(position == 21){
  9. convertView = inflater.inflate(R.layout.item_solution2_bottom, null);
  10. return convertView;
  11. }
  12. //普通列表项
  13. ViewHolder h = null;
  14. if(convertView == null || convertView.getTag() == null){
  15. convertView = inflater.inflate(R.layout.item_listview_data, null);
  16. h = new ViewHolder();
  17. h.tv = (TextView) convertView.findViewById(R.id.item_listview_data_tv);
  18. convertView.setTag(h);
  19. }else{
  20. h = (ViewHolder) convertView.getTag();
  21. }
  22. h.tv.setText("第"+ position + "条数据");
  23. return convertView;
  24. }

复制代码

在Activty中,只需要直接为ListView设置自定义的Adapter就行了。

  1. lv = (ListView) findViewById(R.id.act_solution_2_lv);
  2. adapter = new AdapterForListView2(this);
  3. lv.setAdapter(adapter);

复制代码

3、使用LinearLayout取代ListView
   
既然ListView不能适应ScrollView,那就换一个可以适应ScrollView的控件,干嘛非要吊死在ListView这一棵树上呢?而
LinearLayout是最好的选择。但如果我仍想继续使用已经定义好的Adater呢?我们只需要自定义一个类继承自LinearLayout,为其
加上对BaseAdapter的适配。

  1. import android.content.Context;
  2. import android.util.AttributeSet;
  3. import android.util.Log;
  4. import android.view.View;
  5. import android.widget.BaseAdapter;
  6. import android.widget.LinearLayout;
  7. /**
  8. * 取代ListView的LinearLayout,使之能够成功嵌套在ScrollView中
  9. * @author terry_龙
  10. */
  11. public class LinearLayoutForListView extends LinearLayout {
  12. private BaseAdapter adapter;
  13. private OnClickListener onClickListener = null;
  14. /**
  15. * 绑定布局
  16. */
  17. public void bindLinearLayout() {
  18. int count = adapter.getCount();
  19. this.removeAllViews();
  20. for (int i = 0; i < count; i++) {
  21. View v = adapter.getView(i, null, null);
  22. v.setOnClickListener(this.onClickListener);
  23. addView(v, i);
  24. }
  25. Log.v("countTAG", "" + count);
  26. }
  27. public LinearLayoutForListView(Context context) {
  28. super(context);

复制代码

上面的代码拷贝保存为LinearLayoutForListView.class,或者直接拷贝Demo中的这个类在自己的工程里。我们只需要把原来xml布局文件中的ListView替换为这个类就行了:

  1. <pm.nestificationbetweenscrollviewandabslistview.mywidgets.LinearLayoutForListView
  2. android:id="@+id/act_solution_3_mylinearlayout"
  3. android:layout_width="fill_parent"
  4. android:layout_height="wrap_content"
  5. android:orientation="vertical" >
  6. </pm.nestificationbetweenscrollviewandabslistview.mywidgets.LinearLayoutForListView>

复制代码

在Activity中也把ListView改成LinearLayoutForListView,就能成功运行了。

  1. mylinearlayout = (LinearLayoutForListView) findViewById(R.id.act_solution_3_mylinearlayout);
  2. adapter = new AdapterForListView(this);
  3. mylinearlayout.setAdapter(adapter);

复制代码

4、自定义可适应ScrollView的ListView
   
这个方法和上面的方法是异曲同工,方法3是自定义了LinearLayout以取代ListView的功能,但如果我脾气就是倔,就是要用
ListView怎么办?那就只好自定义一个类继承自ListView,通过重写其onMeasure方法,达到对ScrollView适配的效果。
    下面是继承了ListView的自定义类:

  1. import android.content.Context;
  2. import android.util.AttributeSet;
  3. import android.widget.ListView;
  4. public class ListViewForScrollView extends ListView {
  5. public ListViewForScrollView(Context context) {
  6. super(context);
  7. }
  8. public ListViewForScrollView(Context context, AttributeSet attrs) {
  9. super(context, attrs);
  10. }
  11. public ListViewForScrollView(Context context, AttributeSet attrs,
  12. int defStyle) {
  13. super(context, attrs, defStyle);
  14. }
  15. @Override
  16. /**
  17. * 重写该方法,达到使ListView适应ScrollView的效果
  18. */
  19. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  20. int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2,
  21. MeasureSpec.AT_MOST);
  22. super.onMeasure(widthMeasureSpec, expandSpec);
  23. }
  24. }

复制代码

三个构造方法完全不用动,只要重写onMeasure方法,需要改动的地方比起方法3少了不是一点半点…
    在xml布局中和Activty中使用的ListView改成这个自定义ListView就行了。代码就省了吧…
    这个方法和方法1有一个同样的毛病,就是默认显示的首项是ListView,需要手动把ScrollView滚动至最顶端。

  1. sv = (ScrollView) findViewById(R.id.act_solution_4_sv);
  2. sv.smoothScrollTo(0, 0);

复制代码

5、设置ScrollView的属性,使ListView能够成功嵌套(无法达到预定效果)
    这个方法是我在写Demo的时候找到的,第一反应是有这个方法我还写这个Demo干嘛,只要在布局文件中添加一个属性就搞定了。不过结果确实是ListView的大小把ScrollView的剩余部分填满了,但却不能滚动,真是个致命的问题…
    不废话了,布局文件中:

  1. <ScrollView
  2. android:id="@+id/act_solution_5_sv"
  3. android:layout_width="fill_parent"
  4. android:layout_height="fill_parent"
  5. android:layout_below="@+id/act_solution_5_vg_top"
  6. android:fillViewport="true">

复制代码

设置fillViewport的属性为true即可。简单吧?
    但是不能滚动这个致命的问题我却不知道该怎么解决了,继续求大神解答…

四、几种种方法的优缺点比较

上面一共给出了4中亲测可用的方法,各自有使用条件,复杂程度也各不相同。
    下面我来从几个方面来分析几种方法的优势和劣势。
   
方法1的优点是不用对使用的控件做任何修改,只需要使用一个现成的方法就好了,而最大的限制是ListView的item只能由LinearLayout
这一个布局组成,对于一些复杂的布局就不适用了。如果你的工程急需解决这个问题,而且满足方法的使用条件,即ListView的item布局简单,完全有
LinearLayout组成,你就只需要把setListViewHeightBasedOnChildren方法拿过去就行了。
   
方法2的优点是布局文件设计简单、Activity中的代码也很少,而缺点却是自定义Adapter变得十分复杂,而且执行效率会变低,因为
findViewById是十分费时的操作,而使用ViewHolder结构可以解决费时的问题(有兴趣的童鞋可以去搜一艘ViewHolder结构),
然而使用了方法2的话,会破坏这种结构。如果你的工程设计上偏简单,ListView子项相对少、ListView上下方数据少、子项间交互少的话,可以
尝试一下。
   
方法3的优点是完全解决了ScrollView嵌套ListView的问题,同时代码较少,你甚至可以直接使用LinearLayout,而在
Activity中手动为LinearLayout添加子项控件,不过需要注意的是,在添加前需要调用其removeAllViews的方法,否则可能会
出现预想不到的事情,那时你会想念天国的ListView的。缺点不是很明显,但还是有两个:一是使用的不是系统控件,不能在xml布局的
Graphical
Layout视图中直接看到效果;二是不能向ListView那样可以使用ViewHolder结构,在加载大量子项时会费很多时间在
findViewById中。如果你的列表数据比较少的话,不妨试试这个方法,除了不能使用ViewHolder结构,使用方法几乎和ListView一
样。
   
方法4…比方法3更简单,代码更少,同时保留了ListView原有的所有方法,包括notifyDataSetChanged方法,相比其他方法是最趋
近于完美的方法,只是需要在Activity中设定ScrollView滚动至顶端。如果你还在犹豫不决的话就选这个方法吧,我想我以后是只会用这个方法
了…

四种方案解决ScrollView嵌套ListView问题的更多相关文章

  1. 四种方案解决ScrollView嵌套ListView问题(转)

    以下文章转自@安卓泡面 在工作中,曾多次碰到ScrollView嵌套ListView的问题,网上的解决方法有很多种,但是杂而不全.我试过很多种方法,它们各有利弊. 在这里我将会从使用ScrollVie ...

  2. 转:四种方案解决ScrollView嵌套ListView问题

    转载自:http://blog.sina.com.cn/s/blog_46798aa80101lxbk.html 原始的连接已经不知道是哪里了,项目中遇到了同样的问题,花了一下午都没有想到是嵌套引起的 ...

  3. 四种方案解决ScrollView嵌套ListView问题[转]

    http://bbs.anzhuo.cn/thread-982250-1-1.html 以下文章转自@安卓泡面 在工作中,曾多次碰到ScrollView嵌套ListView的问题,网上的解决方法有很多 ...

  4. 转-四种方案解决ScrollView嵌套ListView问题

    本人网上用的ID是泡面或安卓泡面,学习一年半之前开始从事Android应用开发,这是我写的第一篇Android技术文章,转载请注明出处和作者,有写的不好的地方还请帮忙指出,谢谢. 在工作中,曾多次碰到 ...

  5. 四种方案解决ScrollView嵌套ListView问题 [复制链接]

    以下文章转自@安卓泡面 在工作中,曾多次碰到ScrollView嵌套ListView的问题,网上的解决方法有很多种,但是杂而不全.我试过很多种方法,它们各有利弊. 在这里我将会从使用ScrollVie ...

  6. Android——MeasureSpec学习 - 解决ScrollView嵌套ListView和GridView冲突的方法

      原文地址:http://blog.csdn.net/yuhailong626/article/details/20639217   在自定义View和ViewGroup的时候,我们经常会遇到int ...

  7. 一键解决ScrollView嵌套ListView仅仅显示一行的问题

    /** * 解决ScrollView嵌套ListView仅仅显示一行的问题 * * @param listView */ private void setListViewHeightBasedOnCh ...

  8. android 解决ScrollView嵌套ListView的问题,不能全屏,全屏不能显示下面控件

    在开发中遇到ScrollView嵌套ListView的问题,最开始发出不能全屏,效果是这样的: 但我想要的效果是这样的: 下面看一下布局文件: <?xml version="1.0&q ...

  9. 解决ScrollView嵌套ListView,ListView填充容器后,界面自动滚动回顶部的问题

    1.scrollView.scrollTo(0,0),有时可以,有时不行: 2.listView.post(new Runnable() {                               ...

随机推荐

  1. 利用动软代码生成器 自动生成LINQ需要用的数据实体类 (转)

    首先先建立一个模板 名称随意 我起的“生成数据实体.cmt” 代码如下: <#@ template language="c#" HostSpecific="True ...

  2. CAN

    CAN Introduction Features Network Topology(CANbus網路架構) MESSAGE TRANSFER(CAN通訊的資料格式) 1.DATA FRAME(資料通 ...

  3. lib-flexible 结合 WKWebView 的样式错乱解决方法

    技术栈 lib-flexible 是淘宝的可伸缩方案 WKWebView 是ios8以上支持的网页控件 问题场景 最新公司一个项目使用 lib-flexible 来做移动端的伸缩解决方案,页面在saf ...

  4. 使用Oracle的存储过程批量插入数据

    原文地址:http://www.cnblogs.com/liaoyu/p/oracle-procedure-batch-insert.html 作者:L君还在说之乎者也 最近在工作中,需要使用生成一些 ...

  5. leetcode面试准备: Maximal Rectangle

    leetcode面试准备: Maximal Rectangle 1 题目 Given a 2D binary matrix filled with 0's and 1's, find the larg ...

  6. POJ1699Best Sequence(DFS)

    链接 这题其实是由bug的 一个串包含其它两个串的数据没有 所以就这么水了它吧 只处理两个串的关系就行了 回来补点..看了huge的博客 发现其实不是有Bug  题意没读清楚 必须首尾相连 像AGCT ...

  7. oracle的一种字符串处理机制。

    orcale会把空字符串当成Null进行存储,sqlserver直接存储空字符串

  8. jquery 创建 SVG DOM 的处理方法

    使用的是 createElement 方法 这个是无法生成SVG DOM的 可以使用下方的方法生成 var svgns = "http://www.w3.org/2000/svg" ...

  9. HDU4370 0 or 1 最短路

    分析: 1001  (已更新) 显然,题目给的是一个0/1规划模型.解题的关键在于如何看出这个模型的本质.3个条件明显在刻画未知数之间的关系,从图论的角度思考问题,容易得到下面3个结论:1.X12+X ...

  10. 【原】Spark中Stage的提交源码解读

    版权声明:本文为原创文章,未经允许不得转载. 复习内容: Spark中Job如何划分为Stage http://www.cnblogs.com/yourarebest/p/5342424.html 1 ...