ChatGPT解决这个技术问题 Extra ChatGPT

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?

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.
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.
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.
@Romain - this is a fairly old post, so I'm wondering if the concerns here have been addressed already, or is there still no good way to use a ListView inside of a ScrollView (or an alternative that doesn't involve manually coding all the nicities of a ListView into a LinearLayout)?
@Jim: "or an alternative that doesn't involve manually coding all the nicities of a ListView into a LinearLayout" -- put everything inside the ListView. ListView can have multiple row styles, plus non-selectable rows.

R
Reaz Murshed

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

You've just recreated a very expensive LinearLayout :)
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?
listItem.measure(0,0) will throw a NPE if listItem is a ViewGroup instance. I added the following before listItem.measure: if (listItem instanceof ViewGroup) listItem.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
Fix for ListViews with padding other than 0: int totalHeight = listView.getPaddingTop() + listView.getPaddingBottom();
Unfortunately this method is a bit flawed when the ListView can contain items of variable height. The call to getMeasuredHeight() only returns the targeted minimum height as opposed to the actual height. I tried using getHeight() instead, but this returns 0. Does anyone have any insight on how to approach this?
M
Muhammad Babar

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.


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.
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.
You could easily write a static method that creates a LinearLayout from an Adapter.
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.
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.
A
Atul Bhardwaj

This will definitely work............
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 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.
Has anyone noticed any cons with this approach? Its so simple I'm afraid :o
Nice solution, should be the accepted answer +1 ! I've mixed it with the answer below (from djunod), and that work great !
This is the only fix that makes scrolling AND CLICK on children working at the same time for me. Big thanks
C
Catalina

Insted of putting ListView inside a ScrollView , we can use ListView as a ScrollView. Things which has to be in ListView 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 .


This is the most elegant and appropriate answer imho. For more details on this approach, see this answer: stackoverflow.com/a/20475821/1221537
d
djunod

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.


It won't work when list-items are of variable sizes, i.e. some textview that expand over multiple lines. Any solution?
Check my comment here - it gives the perfect solution - stackoverflow.com/a/23315696/1433187
worked for me with API 19, but not with API 23: Here it adds much white space below the listView.
J
Jason Y

ListView is actually already capable of measuring itself to be tall enough to display all items, but it doesn't do this when you simply specify wrap_content (MeasureSpec.UNSPECIFIED). It will do this when given a height with MeasureSpec.AT_MOST. With this knowledge, you can create a very simple subclass to solve this problem which works far better than any of the solutions posted above. You should still use wrap_content with this subclass.

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

Manipulating the heightMeasureSpec to be AT_MOST with a very large size (Integer.MAX_VALUE >> 4) causes the ListView to measure all of its children up to the given (very large) height and set its height accordingly.

This works better than the other solutions for a few reasons:

It measures everything correctly (padding, dividers) It measures the ListView during the measure pass Due to #2, it handles changes in width or number of items correctly without any additional code

On the downside, you could argue that doing this is relying on undocumented behavior in the SDK which could change. On the other hand, you could argue that this is how wrap_content should really work with ListView and that the current wrap_content behavior is just broken.

If you're worried that the behavior could change in the future, you should simply copy the onMeasure function and related functions out of ListView.java and into your own subclass, then make the AT_MOST path through onMeasure run for UNSPECIFIED as well.

By the way, I believe that this is a perfectly valid approach when you are working with small numbers of list items. It may be inefficient when compared to LinearLayout, but when the number of items is small, using LinearLayout is unnecessary optimization and therefore unnecessary complexity.


Also works with variable height items in the listview!
@Jason Y what does this, Integer.MAX_VALUE >> 4 means ?? what is 4 there??
@Jason Y for me it worked only after i changed Integer.MAX_VALUE >> 2 to Integer.MAX_VALUE >> 21 . Why its so? Also it work's with ScrollView not with NestedScrollView.
T
TalkLittle

There's a built-in setting for it. On the ScrollView:

android:fillViewport="true"

In Java,

mScrollView.setFillViewport(true);

Romain Guy explains it in depth here: http://www.curious-creature.org/2010/08/15/scrollviews-handy-trick/


That works for a LinearLayout as he demonstrates. It does not work for a ListView. Based on the date of that post, I imagine he wrote it to provide an example of his alternative, but this does not answer the question.
this is fast solution
D
Dedaniya HirenKumar

You Create Custom ListView Which is non Scrollable

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

In Your Layout Resources File

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

In Java File

Create a object of your customListview instead of ListView like : NonScrollListView non_scroll_list = (NonScrollListView) findViewById(R.id.lv_nonscroll_list);


A
Ashish Saini

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

Good fix! It works as needed. Thanks. In my case I have a whole bunch of views before the list and the perfect solution is to have just one vertical scroll on the view.
J
JackA

This is the only thing that worked for me:

on Lollipop onwards you can use

yourtListView.setNestedScrollingEnabled(true);

This enable or disable nested scrolling for this view if you need backwards compatibility with older version of the OS you'll have to use the RecyclerView.


This didn't have any effect on my list views on API 22 in a DialogFragment in an AppCompat activity. They still collapse. @Phil Ryan 's answer worked with a little bit of modification.
C
Cheryl Simon

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?


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;
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.
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.
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.
@ArtemRussakovskii Can you please explain your easy way to replace ListView to LinearLayout with an existing Adapter?
A
Abandoned Cart

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

Where to call this method?
After creating the listview and setting the adapter.
This gets too slow on a long lists.
@cakan It was also written 2 years earlier as a workaround to allow using certain UI components together that aren't really meant to be combined. You are adding an unnecessary amount of overhead in modern Android, so it is to be expected that the list would slow down when it gets large.
P
Phil Ryan

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. :-)


One question : When did you call the setListViewHeightBasedOnChildren() method? Because it doesn't work all the time for me.
@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.
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)?
S
Shashank Kapsime

Before it was not possible. But with the release of new Appcompat libraries and Design libraries, this can be achieved.

You just have to use NestedScrollView https://developer.android.com/reference/android/support/v4/widget/NestedScrollView.html

I am not aware it will work with Listview or not but works with RecyclerView.

Code Snippet:

<android.support.v4.widget.NestedScrollView 
android:layout_width="match_parent"
android:layout_height="match_parent">

<android.support.v7.widget.RecyclerView
    android:layout_width="match_parent"
    android:layout_height="wrap_content" />

</android.support.v4.widget.NestedScrollView>

Cool, I haven't tried it so I can't confirm that an internal ListView does not collapse, but looks like that might be the intent. Deeply saddened not to see Romain Guy in the blame log ;)
This works with recyclerView but not listView. Thanx
P
PSchuette

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

            }

        });

A
Ali

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!


b
brokedid

A solution I use is, to add all Content of the ScrollView (what should be above and under the listView) as headerView and footerView in the ListView.

So it works like, also the convertview is resued how it should be.


C
Community

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 :)


C
Community

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

: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?
I need a solution when list-items are of variable sizes, i.e. some textview that expand over multiple lines. Any suggestion?
C
Community

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


This covers how to prevent losing scrollview interaction, but the question was about maintaining the listview height.
A
Alécio Carvalho

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

        }
    }
}
}

T
TacoEater

All these answers are wrong!!! If you are trying to put a listview in a scroll view you should re-think your design. You are trying to put a ScrollView in a ScrollView. Interfering with the list will hurt list performance. It was designed to be like this by Android.

If you really want the list to be in the same scroll as the other elements, all you have to do is add the other items into the top of the list using a simple switch statement in your adapter:

class MyAdapter extends ArrayAdapter{

    public MyAdapter(Context context, int resource, List objects) {
        super(context, resource, objects);
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
         ViewItem viewType = getItem(position);

        switch(viewType.type){
            case TEXTVIEW:
                convertView = layouteInflater.inflate(R.layout.textView1, parent, false);

                break;
            case LISTITEM:
                convertView = layouteInflater.inflate(R.layout.listItem, parent, false);

                break;            }


        return convertView;
    }


}

The list adapter can handle everything since it only renders what is visible.


m
majinnaibu

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.


D
Durgadass S

When we place ListView inside ScrollView two problems arise. One is ScrollView measures its children in UNSPECIFIED mode, so ListView sets its own height to accommodate only one item(I don't know why), another is ScrollView intercepts the touch event so ListView does not scrolls.

But we can place ListView inside ScrollView with some workaround. This post, by me, explains the workaround. By this workaround we can also retain ListView's recycling feature as well.


S
Saksham

Instead of putting the listview inside Scrollview, put the rest of the content between listview and the opening of the Scrollview as a separate view and set that view as the header of the listview. So you will finally end up only list view taking charge of Scroll.


G
Garg's

This library is the easiest and quickest solution to the problem.


Z
Zohab Ali

You should never use listview inside scrollview. Instead you should use NestedScrollView as parent and RecyclerView inside that.... as it handles a lot of scrolling issues


m
myforums

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