How can I put a ListView into a ScrollView without it collapsing?



I've searched around for solutions to this problem, and the only answer I can find seems to be "don't put a ListView into a ScrollView". I have yet to see any real explanation for why though. The only reason I can seem to find is that Google doesn't think you should want to do that. Well I do, so I did.

So the question is, how can you place a ListView into a ScrollView without it collapsing to its minimum height?

share | improve this question
 
4  
It sounds like you found out why. –   Justin  Aug 16 '10 at 18:02
2  
Their argument against it seems to be "because you shouldn't have one scrollable thing inside another". And why not, exactly? I'm not an Apple fan, far from it, but they seem to think this is a reasonable thing someone might want to do. –   DougW  Aug 16 '10 at 18:08
7  
Because when a device uses a touch screen there is no good way to differentiate between two nested scrollable containers. It works on traditional desktops where you use the scrollbar to scroll a container. –  Romain Guy  Aug 16 '10 at 18:20
29  
Sure there is. If they touch inside the inner one, scroll that. If the inner one is too large, that's bad UI design. That doesn't mean it's a bad idea in general. –   DougW  Aug 16 '10 at 19:05
4  
Just stumbled on this for my Tablet app. While it doesn't seem to be too worse on Smartphone, this is horrible on a tablet where you easily could decide whether the user wants to scroll the outer or inner ScrollView. @DougW: Horrible design, I agree. –   jellyfish  Jul 6 '11 at 16:47
show 6 more comments

17 Answers

up vote 82 down vote accepted

Using a ListView to make it not scroll is extremely expensive and goes against the whole purpose of ListView. You should NOT do this. Just use a LinearLayout instead.

share | improve this answer
 
33  
The source would be me since I've been in charge of ListView for the past 2 or 3 years :) ListView does a lot of extra work to optimize the use of adapters. Trying to work around it will still cause ListView to do a lot of work a LinearLayout wouldn't have to do. I won't go into the details because there's not enough room here to explain ListView's implementation. This also won't solve the way ListView behaves with respect to touch events. There's also no guarantee that if your hack works today it will still work in a future release. Using a LinearLayout would not be much work really. –   Romain Guy  Aug 16 '10 at 19:52
7  
Well that's a reasonable response. If I could share some feedback, I have much more significant iPhone experience, and I can tell you that Apple's documentation is far better written with regard to performance characteristics and use cases (or anti-patterns) like this. Overall, the Android documentation is far more distributed and less focused. I understand there are some reasons behind that, but that's a long discussion, so if you feel compelled to chat about it let me know. –   DougW  Aug 16 '10 at 20:23
6  
You could easily write a static method that creates a LinearLayout from an Adapter. –   Romain Guy  Dec 19 '11 at 20:05
20  
This is NOT an answer for How can I put a ListView into a ScrollView without it collapsing?... You can tell that you shouldn't do it, but also give an answer on how to do it, that's a good answer. You know that a ListView has some other cool features apart from scrolling, features that LinearLayout don't have. –   Jorge Fuentes González  Jun 23 '13 at 19:50 
3  
What if you have a region containing a ListView + other Views, and you want everything to scroll as one? That seems like a reasonable use-case for placing a ListView within a ScrollView. Replacing the ListView with a LinearLayout is not a solution because then you cannot use Adapters. –   Barry Fruitman  Aug 8 '13 at 4:17
show 14 more comments

Here's my solution. I'm fairly new to the Android platform, and I'm sure this is a bit hackish, especially in the part about calling .measure directly, and setting the LayoutParams .height property directly, but it works.

All you have to do is call Utility.setListViewHeightBasedOnChildren(yourListView) and it will be resized to exactly accommodate the height of its items.

    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);
        }
     }
share | improve this answer
 
34  
You've just recreated a very expensive LinearLayout :) –   Romain Guy  Aug 16 '10 at 18:21
42  
Except a LinearLayout doesn't have all the inherent niceties of a ListView -- it doesn't have dividers, header/footer views, a list selector which matches the phone's UI theme colors, etc. Honestly, there's no reason why you can't scroll the inner container until it's reached the end and then have it stop intercepting touch events so that the outer container scrolls. Every desktop browser does this when you're using your scroll wheel. Android itself can handle nested scrolling if you're navigating via the trackball, so why not via touch? –   Neil Traft  Aug 26 '10 at 23:41
2  
For solution by DoughW there is a bug, however i fixed it, see my post. –   Nex  Dec 8 '10 at 8:33 
24  
listItem.measure(0,0) will throw a NPE if listItem is a ViewGroup instance. I added the following beforelistItem.measureif (listItem instanceof ViewGroup) listItem.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)); –   Good Guy Greg  Feb 21 '12 at 5:37 
3  
Fix for ListViews with padding other than 0: int totalHeight = listView.getPaddingTop() + listView.getPaddingBottom(); –   Paul  Apr 8 '13 at 11:56 
show 23 more comments

You have to just replace your <ScrollView ></ScrollView> in layout XML file with this Custom ScrollView like <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;
    }
}

This will definitely work............

share | improve this answer
 
3  
This is very good. It also makes it possible to put a Google Maps Fragment inside the ScrollView, and it still works zooming in/out. Thanks. –   Magnus  Aug 8 '13 at 14:32
1  
Thanks bro its works for me –   Nagaraja  Oct 26 '13 at 5:19
 
Awsm work bro. Works Perfectly. –   Mohanish  Nov 11 '13 at 9:28
add comment

There are plenty of situations where it makes a lot of sense to have ListView's in a ScrollView.

Here's code based on DougW's suggestion... works in a fragment, takes less memory.

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();
}

call setListViewHeightBasedOnChildren(listview) on each embedded listview.

share | improve this answer
 
 
It won't work when list-items are of variable sizes, i.e. some textview that expand over multiple lines. Any solution? –   Khobaib  Apr 26 at 18:13
 
Check my comment here - it gives the perfect solution - stackoverflow.com/a/23315696/1433187 –   Khobaib Apr 26 at 19:44
add comment

You should not put a ListView in a ScrollView because a ListView already is a ScrollView. So that would be like putting a ScrollView in a ScrollView.

What are you trying to accomplish?

share | improve this answer
 
3  
I'm trying to accomplish having a ListView inside a ScrollView. I do not want my ListView to be scrollable, but there is no simple property to set myListView.scrollEnabled = false; –   DougW  Aug 16 '10 at 18:05
 
As Romain Guy said, this is very much not the purpose of a ListView. A ListView is a view that is optimized for displaying a long list of items, with lots of complicated logic to do lazy loading of views, reuse views, etc. None of this makes sense if you are telling the ListView not to scroll. If you just want a bunch of items in a vertical column, use a LinearLayout. –   Cheryl Simon  Aug 16 '10 at 18:28
8  
The problem is that there are a number of solutions that a ListView provides, including the one you mention. Much of the functionality that Adapters simplify though is about data management and control, not UI optimization. IMO there should have been a simple ListView, and a more complex one that does all the list item reuse and stuff. –   DougW  Aug 16 '10 at 20:43
1  
Absolutely agreed. Note that it's easy to use an existing Adapter with a LinearLayout, but I want all the nice things the ListView provides, like dividers, and that I don't feel like implementing manually. –  Artem Russakovskii  Jun 16 '11 at 23:01
 
@ArtemRussakovskii Can you please explain your easy way to replace ListView to LinearLayout with an existing Adapter? –   happyhardik  Sep 12 '12 at 13:13
add comment

Insted of putting listview inside a scroll view , we can use List view as an Scroll view. Things which has to be in List view can be put inside the listview. Other layouts on top and bottom of listview can be put by adding layouts to header and footer of listview . So the entire listview will give you an experience of scrolling .

share | improve this answer
  add comment

hey I had a similar issue. I wanted to display a list view that didn't scroll and I found that manipulating the parameters worked but was inefficient and would behave differently on different devices.. as a result, this is a piece of my schedule code which actually does this very efficiently.

db = new dbhelper(this);

 cursor = db.dbCursor();
int count = cursor.getCount();
if (count > 0)
{    
LinearLayout linearLayout = (LinearLayout) findViewById(R.id.layoutId);
startManagingCursor(YOUR_CURSOR);

YOUR_ADAPTER(**or SimpleCursorAdapter **) adapter = new YOUR_ADAPTER(this,
    R.layout.itemLayout, cursor, arrayOrWhatever, R.id.textViewId,
    this.getApplication());

int i;
for (i = 0; i < count; i++){
  View listItem = adapter.getView(i,null,null);
  linearLayout.addView(listItem);
   }
}

Note: if you use this, notifyDataSetChanged(); will not work as intended as the views will not be redrawn. Do this if you need a work around

adapter.registerDataSetObserver(new DataSetObserver() {

            @Override
            public void onChanged() {
                super.onChanged();
                removeAndRedrawViews();

            }

        });
share | improve this answer
  add comment

This is a combination of the answers by DougW, Good Guy Greg, and Paul. I found it was all needed when trying to use this with a custom listview adapter and non-standard list items otherwise the listview crashed the application (also crashed with the answer by Nex):

public void setListViewHeightBasedOnChildren(ListView listView) {
        ListAdapter listAdapter = listView.getAdapter();
        if (listAdapter == null) {
            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);
    }
share | improve this answer
  add comment

Here is small modification on @djunod's answer that I need to make it work perfectly:

public static void setListViewHeightBasedOnChildren(ListView listView)
{
    ListAdapter listAdapter = listView.getAdapter();
    if(listAdapter == null) return;
    if(listAdapter.getCount() <= 1) 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);
        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();
}
share | improve this answer
 
 
:Hi, your answer works most of time, but it does not work if the listview have a header view and a foot view. Could you check it? –   hguser  Nov 20 '13 at 9:23
 
I need a solution when list-items are of variable sizes, i.e. some textview that expand over multiple lines. Any suggestion? –   Khobaib  Apr 26 at 18:19
add comment

Although the suggested setListViewHeightBasedOnChildren() methods work in most of the cases, in some cases, specially with a lot of items, I noticed that the last elements are not displayed. So I decided to mimic a simple version of the ListView behavior in order to reuse any Adapter code, here it's the ListView alternative:

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 static final String TAG = StretchedListView.class.getSimpleName();
private final DataSetObserver dataSetObserver;
private ListAdapter adapter;

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) {
    this.adapter = adapter;
    if (adapter != null) {
        this.adapter.registerDataSetObserver(dataSetObserver);
    } else {
        this.adapter.unregisterDataSetObserver(dataSetObserver);
    }

    syncDataFromAdapter();
}

public ListAdapter getAdapter() {
    return adapter;
}

private void syncDataFromAdapter() {
    Log.d(TAG, "syncDataFromAdapter() called");
    removeAllViews();
    if (adapter != null) {
        int count = adapter.getCount();
        for (int i = 0; i < count; i++) {
            View view = adapter.getView(i, null, this);
            addView(view);
        }
    }
}


  public int getCount() {
    return adapter != null ? adapter.getCount() : 0;
  }
}
share | improve this answer
  add comment

Here is my version of the code that calculates total height of the list view. This one works for me:

   public static void setListViewHeightBasedOnChildren(ListView listView) {
    ListAdapter listAdapter = listView.getAdapter();
    if (listAdapter == null || listAdapter.getCount() < 2) {
        // pre-condition
        return;
    }

    int totalHeight = 0;
    int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(BCTDApp.getDisplaySize().width, View.MeasureSpec.AT_MOST);
    int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
    ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);

    for (int i = 0; i < listAdapter.getCount(); i++) {
        View listItem = listAdapter.getView(i, null, listView);
        if (listItem instanceof ViewGroup) listItem.setLayoutParams(lp);
        listItem.measure(widthMeasureSpec, heightMeasureSpec);
        totalHeight += listItem.getMeasuredHeight();
    }

    totalHeight += listView.getPaddingTop() + listView.getPaddingBottom();
    totalHeight += (listView.getDividerHeight() * (listAdapter.getCount() - 1));
    ViewGroup.LayoutParams params = listView.getLayoutParams();
    params.height = totalHeight;
    listView.setLayoutParams(params);
    listView.requestLayout();
}
share | improve this answer
  add comment

try this, this works for me, I forgot where I found it, somewhere in stack overflow, i'm not here to explained it why it doesn't work, but this is the answer :).

    final ListView AturIsiPulsaDataIsiPulsa = (ListView) findViewById(R.id.listAturIsiPulsaDataIsiPulsa);
    AturIsiPulsaDataIsiPulsa.setOnTouchListener(new ListView.OnTouchListener() 
    {
        @Override
        public boolean onTouch(View v, MotionEvent event) 
        {
            int action = event.getAction();
            switch (action) 
            {
                case MotionEvent.ACTION_DOWN:
                // Disallow ScrollView to intercept touch events.
                v.getParent().requestDisallowInterceptTouchEvent(true);
                break;

                case MotionEvent.ACTION_UP:
                // Allow ScrollView to intercept touch events.
                v.getParent().requestDisallowInterceptTouchEvent(false);
                break;
            }

            // Handle ListView touch events.
            v.onTouchEvent(event);
            return true;
        }
    });
    AturIsiPulsaDataIsiPulsa.setClickable(true);
    AturIsiPulsaDataIsiPulsa.setAdapter(AturIsiPulsaDataIsiPulsaAdapter);

EDIT !, I finally found out where I got the code. here ! : Listview inside ScrollView is not scrolling on Android

share | improve this answer
  add comment

I converted @DougW's Utility into C# (used in Xamarin). The following works fine for fixed-height items in the list, and is going to be mostly fine, or at least a good start, if only some of the items are a bit bigger than the standard item.

// You will need to put this Utility class into a code file including various
// libraries, I found that I needed at least System, Linq, Android.Views and 
// Android.Widget.
using System;
using System.Linq;
using Android.Views;
using Android.Widget;

namespace UtilityNamespace  // whatever you like, obviously!
{
    public class Utility
    {
        public static void setListViewHeightBasedOnChildren (ListView listView)
        {
            if (listView.Adapter == null) {
                // pre-condition
                return;
            }

            int totalHeight = listView.PaddingTop + listView.PaddingBottom;
            for (int i = 0; i < listView.Count; i++) {
                View listItem = listView.Adapter.GetView (i, null, listView);
                if (listItem.GetType () == typeof(ViewGroup)) {
                    listItem.LayoutParameters = new LinearLayout.LayoutParams (ViewGroup.LayoutParams.MatchParent, ViewGroup.LayoutParams.WrapContent);
                }
                listItem.Measure (0, 0);
                totalHeight += listItem.MeasuredHeight;
            }

            listView.LayoutParameters.Height = totalHeight + (listView.DividerHeight * (listView.Count - 1));
        }
    }
}

Thanks @DougW, this got me out of a tight spot when I had to work with OtherPeople'sCode. :-)

share | improve this answer
 
 
One question : When did you call the setListViewHeightBasedOnChildren() method? Because it doesn't work all the time for me. –   Alexandre D.  Jul 9 at 15:53
 
@AlexandreD you call the Utility.setListViewHeightBasedOnChildren(yourListView) when you have created the list of items. i.e. make sure that you have created your list first! I had found that I was making a list, and I was even displaying my items in Console.WriteLine... but somehow the items were in the wrong scope. Once I had figured that out, and ensured that I had the right scope for my list, this worked like a charm. –   Phil Ryan Jul 10 at 4:49
 
The fact is my lists are created in my ViewModel. How can I wait that my lists are created in my view before any call to the Utility.setListViewHeightBasedOnChildren(yourListView)? –   Alexandre D.  Jul 10 at 7:16 
add comment

We could not use two scrolling simulteniuosly.We will have get total length of ListView and expand listview with the total height .Then we can add ListView in ScrollView directly or using LinearLayout because ScrollView have directly one child . copy setListViewHeightBasedOnChildren(lv) method in your code and expand listview then you can use listview inside scrollview. \layout xml file

<?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>

onCreate method in Activity class:

 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);
      }
share | improve this answer
  add comment

thanks to Vinay's code here is my code for when you can't have a listview inside a scrollview yet you need something like that

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);
                }

the relativeLayout stays inside a ScrollView so it all becomes scrollable :)

share | improve this answer
  add comment

This whole problem would just go away if LinearLayout had a setAdapter method, because then when you told someone to use it instead the alternative would be trivial.

If you actually want a scrolling ListView inside another scrolling view this won't help, but otherwise this will at least give you an idea.

You need to create a custom adapter to combine all the content you want to scroll over and set the ListView's adapter to that.

I don't have sample code handy, but if you want something like.

<ListView/>

(other content)

<ListView/>

Then you need to create an adapter that represents all of that content. The ListView/Adapters are smart enough to handle different types as well, but you need to write the adapter yourself.

The android UI API just isn't as mature as pretty much everything else out there, so it doesn't have the same niceties as other platforms. Also, when doing something on android you need to be in an android (unix) mindset where you expect that to do anything you're probably going to have to assemble functionality of smaller parts and write a bunch of your own code to get it to work.

share | improve this answer
  add comment

There are two issue when using a ListView inside a ScrollView.

1- ListView must fully expand to its children height. this ListView resolve this:

public class ListViewExpanded extends ListView
{
    public ListViewExpanded(Context context, AttributeSet attrs)
    {
        super(context, attrs);
        setDividerHeight(0);
    }

    @Override
    public void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
    {
        super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST));
    }
}

Divider height must be 0, use padding in rows instead.

2- The ListView consumes touch events so ScrollView can't be scrolled as usual. This ScrollView resolve this issue:

public class ScrollViewInterceptor extends ScrollView
{
    float startY;

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

    @Override
    public boolean onInterceptTouchEvent(MotionEvent e)
    {
        onTouchEvent(e);
        if (e.getAction() == MotionEvent.ACTION_DOWN) startY = e.getY();
        return (e.getAction() == MotionEvent.ACTION_MOVE) && (Math.abs(startY - e.getY()) > 50);
    }
}

This is the best way I found to do the trick!


I've searched around for solutions to this problem, and the only answer I can find seems to be "don't put a ListView into a ScrollView". I have yet to see any real explanation for why though. The only reason I can seem to find is that Google doesn't think you should want to do that. Well I do, so I did.

So the question is, how can you place a ListView into a ScrollView without it collapsing to its minimum height?

share | improve this question
 
4  
It sounds like you found out why. –   Justin  Aug 16 '10 at 18:02
2  
Their argument against it seems to be "because you shouldn't have one scrollable thing inside another". And why not, exactly? I'm not an Apple fan, far from it, but they seem to think this is a reasonable thing someone might want to do. –   DougW  Aug 16 '10 at 18:08
7  
Because when a device uses a touch screen there is no good way to differentiate between two nested scrollable containers. It works on traditional desktops where you use the scrollbar to scroll a container. –  Romain Guy  Aug 16 '10 at 18:20
29  
Sure there is. If they touch inside the inner one, scroll that. If the inner one is too large, that's bad UI design. That doesn't mean it's a bad idea in general. –   DougW  Aug 16 '10 at 19:05
4  
Just stumbled on this for my Tablet app. While it doesn't seem to be too worse on Smartphone, this is horrible on a tablet where you easily could decide whether the user wants to scroll the outer or inner ScrollView. @DougW: Horrible design, I agree. –   jellyfish  Jul 6 '11 at 16:47
show 6 more comments

17 Answers

up vote 82 down vote accepted

Using a ListView to make it not scroll is extremely expensive and goes against the whole purpose of ListView. You should NOT do this. Just use a LinearLayout instead.

share | improve this answer
 
33  
The source would be me since I've been in charge of ListView for the past 2 or 3 years :) ListView does a lot of extra work to optimize the use of adapters. Trying to work around it will still cause ListView to do a lot of work a LinearLayout wouldn't have to do. I won't go into the details because there's not enough room here to explain ListView's implementation. This also won't solve the way ListView behaves with respect to touch events. There's also no guarantee that if your hack works today it will still work in a future release. Using a LinearLayout would not be much work really. –   Romain Guy  Aug 16 '10 at 19:52
7  
Well that's a reasonable response. If I could share some feedback, I have much more significant iPhone experience, and I can tell you that Apple's documentation is far better written with regard to performance characteristics and use cases (or anti-patterns) like this. Overall, the Android documentation is far more distributed and less focused. I understand there are some reasons behind that, but that's a long discussion, so if you feel compelled to chat about it let me know. –   DougW  Aug 16 '10 at 20:23
6  
You could easily write a static method that creates a LinearLayout from an Adapter. –   Romain Guy  Dec 19 '11 at 20:05
20  
This is NOT an answer for How can I put a ListView into a ScrollView without it collapsing?... You can tell that you shouldn't do it, but also give an answer on how to do it, that's a good answer. You know that a ListView has some other cool features apart from scrolling, features that LinearLayout don't have. –   Jorge Fuentes González  Jun 23 '13 at 19:50 
3  
What if you have a region containing a ListView + other Views, and you want everything to scroll as one? That seems like a reasonable use-case for placing a ListView within a ScrollView. Replacing the ListView with a LinearLayout is not a solution because then you cannot use Adapters. –   Barry Fruitman  Aug 8 '13 at 4:17
show 14 more comments

Here's my solution. I'm fairly new to the Android platform, and I'm sure this is a bit hackish, especially in the part about calling .measure directly, and setting the LayoutParams .height property directly, but it works.

All you have to do is call Utility.setListViewHeightBasedOnChildren(yourListView) and it will be resized to exactly accommodate the height of its items.

    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);
        }
     }
share | improve this answer
 
34  
You've just recreated a very expensive LinearLayout :) –   Romain Guy  Aug 16 '10 at 18:21
42  
Except a LinearLayout doesn't have all the inherent niceties of a ListView -- it doesn't have dividers, header/footer views, a list selector which matches the phone's UI theme colors, etc. Honestly, there's no reason why you can't scroll the inner container until it's reached the end and then have it stop intercepting touch events so that the outer container scrolls. Every desktop browser does this when you're using your scroll wheel. Android itself can handle nested scrolling if you're navigating via the trackball, so why not via touch? –   Neil Traft  Aug 26 '10 at 23:41
2  
For solution by DoughW there is a bug, however i fixed it, see my post. –   Nex  Dec 8 '10 at 8:33 
24  
listItem.measure(0,0) will throw a NPE if listItem is a ViewGroup instance. I added the following beforelistItem.measureif (listItem instanceof ViewGroup) listItem.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)); –   Good Guy Greg  Feb 21 '12 at 5:37 
3  
Fix for ListViews with padding other than 0: int totalHeight = listView.getPaddingTop() + listView.getPaddingBottom(); –   Paul  Apr 8 '13 at 11:56 
show 23 more comments

You have to just replace your <ScrollView ></ScrollView> in layout XML file with this Custom ScrollView like <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;
    }
}

This will definitely work............

share | improve this answer
 
3  
This is very good. It also makes it possible to put a Google Maps Fragment inside the ScrollView, and it still works zooming in/out. Thanks. –   Magnus  Aug 8 '13 at 14:32
1  
Thanks bro its works for me –   Nagaraja  Oct 26 '13 at 5:19
    
Awsm work bro. Works Perfectly. –   Mohanish  Nov 11 '13 at 9:28
add comment

There are plenty of situations where it makes a lot of sense to have ListView's in a ScrollView.

Here's code based on DougW's suggestion... works in a fragment, takes less memory.

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();
}

call setListViewHeightBasedOnChildren(listview) on each embedded listview.

share | improve this answer
 
    
It won't work when list-items are of variable sizes, i.e. some textview that expand over multiple lines. Any solution? –   Khobaib  Apr 26 at 18:13
    
Check my comment here - it gives the perfect solution - stackoverflow.com/a/23315696/1433187 –   Khobaib Apr 26 at 19:44
add comment

You should not put a ListView in a ScrollView because a ListView already is a ScrollView. So that would be like putting a ScrollView in a ScrollView.

What are you trying to accomplish?

share | improve this answer
 
3  
I'm trying to accomplish having a ListView inside a ScrollView. I do not want my ListView to be scrollable, but there is no simple property to set myListView.scrollEnabled = false; –   DougW  Aug 16 '10 at 18:05
    
As Romain Guy said, this is very much not the purpose of a ListView. A ListView is a view that is optimized for displaying a long list of items, with lots of complicated logic to do lazy loading of views, reuse views, etc. None of this makes sense if you are telling the ListView not to scroll. If you just want a bunch of items in a vertical column, use a LinearLayout. –   Cheryl Simon  Aug 16 '10 at 18:28
8  
The problem is that there are a number of solutions that a ListView provides, including the one you mention. Much of the functionality that Adapters simplify though is about data management and control, not UI optimization. IMO there should have been a simple ListView, and a more complex one that does all the list item reuse and stuff. –   DougW  Aug 16 '10 at 20:43
1  
Absolutely agreed. Note that it's easy to use an existing Adapter with a LinearLayout, but I want all the nice things the ListView provides, like dividers, and that I don't feel like implementing manually. –  Artem Russakovskii  Jun 16 '11 at 23:01
    
@ArtemRussakovskii Can you please explain your easy way to replace ListView to LinearLayout with an existing Adapter? –   happyhardik  Sep 12 '12 at 13:13
add comment

Insted of putting listview inside a scroll view , we can use List view as an Scroll view. Things which has to be in List view can be put inside the listview. Other layouts on top and bottom of listview can be put by adding layouts to header and footer of listview . So the entire listview will give you an experience of scrolling .

share | improve this answer
  add comment

hey I had a similar issue. I wanted to display a list view that didn't scroll and I found that manipulating the parameters worked but was inefficient and would behave differently on different devices.. as a result, this is a piece of my schedule code which actually does this very efficiently.

db = new dbhelper(this);

 cursor = db.dbCursor();
int count = cursor.getCount();
if (count > 0)
{    
LinearLayout linearLayout = (LinearLayout) findViewById(R.id.layoutId);
startManagingCursor(YOUR_CURSOR);

YOUR_ADAPTER(**or SimpleCursorAdapter **) adapter = new YOUR_ADAPTER(this,
    R.layout.itemLayout, cursor, arrayOrWhatever, R.id.textViewId,
    this.getApplication());

int i;
for (i = 0; i < count; i++){
  View listItem = adapter.getView(i,null,null);
  linearLayout.addView(listItem);
   }
}

Note: if you use this, notifyDataSetChanged(); will not work as intended as the views will not be redrawn. Do this if you need a work around

adapter.registerDataSetObserver(new DataSetObserver() {

            @Override
            public void onChanged() {
                super.onChanged();
                removeAndRedrawViews();

            }

        });
share | improve this answer
  add comment

This is a combination of the answers by DougW, Good Guy Greg, and Paul. I found it was all needed when trying to use this with a custom listview adapter and non-standard list items otherwise the listview crashed the application (also crashed with the answer by Nex):

public void setListViewHeightBasedOnChildren(ListView listView) {
        ListAdapter listAdapter = listView.getAdapter();
        if (listAdapter == null) {
            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);
    }
share | improve this answer
  add comment

Here is small modification on @djunod's answer that I need to make it work perfectly:

public static void setListViewHeightBasedOnChildren(ListView listView)
{
    ListAdapter listAdapter = listView.getAdapter();
    if(listAdapter == null) return;
    if(listAdapter.getCount() <= 1) 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);
        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();
}
share | improve this answer
 
    
:Hi, your answer works most of time, but it does not work if the listview have a header view and a foot view. Could you check it? –   hguser  Nov 20 '13 at 9:23
    
I need a solution when list-items are of variable sizes, i.e. some textview that expand over multiple lines. Any suggestion? –   Khobaib  Apr 26 at 18:19
add comment

Although the suggested setListViewHeightBasedOnChildren() methods work in most of the cases, in some cases, specially with a lot of items, I noticed that the last elements are not displayed. So I decided to mimic a simple version of the ListView behavior in order to reuse any Adapter code, here it's the ListView alternative:

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 static final String TAG = StretchedListView.class.getSimpleName();
private final DataSetObserver dataSetObserver;
private ListAdapter adapter;

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) {
    this.adapter = adapter;
    if (adapter != null) {
        this.adapter.registerDataSetObserver(dataSetObserver);
    } else {
        this.adapter.unregisterDataSetObserver(dataSetObserver);
    }

    syncDataFromAdapter();
}

public ListAdapter getAdapter() {
    return adapter;
}

private void syncDataFromAdapter() {
    Log.d(TAG, "syncDataFromAdapter() called");
    removeAllViews();
    if (adapter != null) {
        int count = adapter.getCount();
        for (int i = 0; i < count; i++) {
            View view = adapter.getView(i, null, this);
            addView(view);
        }
    }
}


  public int getCount() {
    return adapter != null ? adapter.getCount() : 0;
  }
}
share | improve this answer
  add comment

Here is my version of the code that calculates total height of the list view. This one works for me:

   public static void setListViewHeightBasedOnChildren(ListView listView) {
    ListAdapter listAdapter = listView.getAdapter();
    if (listAdapter == null || listAdapter.getCount() < 2) {
        // pre-condition
        return;
    }

    int totalHeight = 0;
    int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(BCTDApp.getDisplaySize().width, View.MeasureSpec.AT_MOST);
    int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
    ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);

    for (int i = 0; i < listAdapter.getCount(); i++) {
        View listItem = listAdapter.getView(i, null, listView);
        if (listItem instanceof ViewGroup) listItem.setLayoutParams(lp);
        listItem.measure(widthMeasureSpec, heightMeasureSpec);
        totalHeight += listItem.getMeasuredHeight();
    }

    totalHeight += listView.getPaddingTop() + listView.getPaddingBottom();
    totalHeight += (listView.getDividerHeight() * (listAdapter.getCount() - 1));
    ViewGroup.LayoutParams params = listView.getLayoutParams();
    params.height = totalHeight;
    listView.setLayoutParams(params);
    listView.requestLayout();
}
share | improve this answer
  add comment

try this, this works for me, I forgot where I found it, somewhere in stack overflow, i'm not here to explained it why it doesn't work, but this is the answer :).

    final ListView AturIsiPulsaDataIsiPulsa = (ListView) findViewById(R.id.listAturIsiPulsaDataIsiPulsa);
    AturIsiPulsaDataIsiPulsa.setOnTouchListener(new ListView.OnTouchListener() 
    {
        @Override
        public boolean onTouch(View v, MotionEvent event) 
        {
            int action = event.getAction();
            switch (action) 
            {
                case MotionEvent.ACTION_DOWN:
                // Disallow ScrollView to intercept touch events.
                v.getParent().requestDisallowInterceptTouchEvent(true);
                break;

                case MotionEvent.ACTION_UP:
                // Allow ScrollView to intercept touch events.
                v.getParent().requestDisallowInterceptTouchEvent(false);
                break;
            }

            // Handle ListView touch events.
            v.onTouchEvent(event);
            return true;
        }
    });
    AturIsiPulsaDataIsiPulsa.setClickable(true);
    AturIsiPulsaDataIsiPulsa.setAdapter(AturIsiPulsaDataIsiPulsaAdapter);

EDIT !, I finally found out where I got the code. here ! : Listview inside ScrollView is not scrolling on Android

share | improve this answer
  add comment

I converted @DougW's Utility into C# (used in Xamarin). The following works fine for fixed-height items in the list, and is going to be mostly fine, or at least a good start, if only some of the items are a bit bigger than the standard item.

// You will need to put this Utility class into a code file including various
// libraries, I found that I needed at least System, Linq, Android.Views and 
// Android.Widget.
using System;
using System.Linq;
using Android.Views;
using Android.Widget;

namespace UtilityNamespace  // whatever you like, obviously!
{
    public class Utility
    {
        public static void setListViewHeightBasedOnChildren (ListView listView)
        {
            if (listView.Adapter == null) {
                // pre-condition
                return;
            }

            int totalHeight = listView.PaddingTop + listView.PaddingBottom;
            for (int i = 0; i < listView.Count; i++) {
                View listItem = listView.Adapter.GetView (i, null, listView);
                if (listItem.GetType () == typeof(ViewGroup)) {
                    listItem.LayoutParameters = new LinearLayout.LayoutParams (ViewGroup.LayoutParams.MatchParent, ViewGroup.LayoutParams.WrapContent);
                }
                listItem.Measure (0, 0);
                totalHeight += listItem.MeasuredHeight;
            }

            listView.LayoutParameters.Height = totalHeight + (listView.DividerHeight * (listView.Count - 1));
        }
    }
}

Thanks @DougW, this got me out of a tight spot when I had to work with OtherPeople'sCode. :-)

share | improve this answer
 
    
One question : When did you call the setListViewHeightBasedOnChildren() method? Because it doesn't work all the time for me. –   Alexandre D.  Jul 9 at 15:53
    
@AlexandreD you call the Utility.setListViewHeightBasedOnChildren(yourListView) when you have created the list of items. i.e. make sure that you have created your list first! I had found that I was making a list, and I was even displaying my items in Console.WriteLine... but somehow the items were in the wrong scope. Once I had figured that out, and ensured that I had the right scope for my list, this worked like a charm. –   Phil Ryan Jul 10 at 4:49
    
The fact is my lists are created in my ViewModel. How can I wait that my lists are created in my view before any call to the Utility.setListViewHeightBasedOnChildren(yourListView)? –   Alexandre D.  Jul 10 at 7:16 
add comment

We could not use two scrolling simulteniuosly.We will have get total length of ListView and expand listview with the total height .Then we can add ListView in ScrollView directly or using LinearLayout because ScrollView have directly one child . copy setListViewHeightBasedOnChildren(lv) method in your code and expand listview then you can use listview inside scrollview. \layout xml file

<?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>

onCreate method in Activity class:

 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);
      }
share | improve this answer
  add comment

thanks to Vinay's code here is my code for when you can't have a listview inside a scrollview yet you need something like that

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);
                }

the relativeLayout stays inside a ScrollView so it all becomes scrollable :)

share | improve this answer
  add comment

This whole problem would just go away if LinearLayout had a setAdapter method, because then when you told someone to use it instead the alternative would be trivial.

If you actually want a scrolling ListView inside another scrolling view this won't help, but otherwise this will at least give you an idea.

You need to create a custom adapter to combine all the content you want to scroll over and set the ListView's adapter to that.

I don't have sample code handy, but if you want something like.

<ListView/>

(other content)

<ListView/>

Then you need to create an adapter that represents all of that content. The ListView/Adapters are smart enough to handle different types as well, but you need to write the adapter yourself.

The android UI API just isn't as mature as pretty much everything else out there, so it doesn't have the same niceties as other platforms. Also, when doing something on android you need to be in an android (unix) mindset where you expect that to do anything you're probably going to have to assemble functionality of smaller parts and write a bunch of your own code to get it to work.

share | improve this answer
  add comment

There are two issue when using a ListView inside a ScrollView.

1- ListView must fully expand to its children height. this ListView resolve this:

public class ListViewExpanded extends ListView
{
    public ListViewExpanded(Context context, AttributeSet attrs)
    {
        super(context, attrs);
        setDividerHeight(0);
    }

    @Override
    public void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
    {
        super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST));
    }
}

Divider height must be 0, use padding in rows instead.

2- The ListView consumes touch events so ScrollView can't be scrolled as usual. This ScrollView resolve this issue:

public class ScrollViewInterceptor extends ScrollView
{
    float startY;

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

    @Override
    public boolean onInterceptTouchEvent(MotionEvent e)
    {
        onTouchEvent(e);
        if (e.getAction() == MotionEvent.ACTION_DOWN) startY = e.getY();
        return (e.getAction() == MotionEvent.ACTION_MOVE) && (Math.abs(startY - e.getY()) > 50);
    }
}

This is the best way I found to do the trick!


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值