359

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?

Celeo
  • 5,583
  • 8
  • 39
  • 41
DougW
  • 28,776
  • 18
  • 79
  • 107
  • 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
  • 11
    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
  • 57
    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
  • @Romain, I'm curious as to listviews with horiztonal scrolling were accomplished in the google news and weather app. Was this done through a viewflipper, gallery, or horiztonal scroll view filled with listview objects? – Nadewad Apr 07 '11 at 20:34
  • @Romain The funny thing is the two nested containers work fine with respect to touch events. The only problem is that the Listview doesn't expand to fill it's parent. – HRJ Apr 20 '11 at 14:36
  • 5
    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 06 '11 at 16:47
  • 4
    @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 Apr 03 '13 at 20:59
  • 2
    No there isn't, sorry. – Romain Guy Apr 04 '13 at 18:06
  • 3
    @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. – CommonsWare Apr 05 '13 at 02:00
  • @CommonsWare is there a way if I'm using a GridView? – Nemanja Kovacevic Aug 21 '13 at 11:08
  • Google material design guidelines instructs you to use a nestedScrollView with a cardView that contains a listView. – Luke Allison Jun 01 '16 at 06:20
  • The thing is, why a ListView (or RecyclerView) must absolutely be scrollable? RecyclerView has one main function: recycling. ScrollView has one function: scrolling. Why couldn't they work together? The RecyclerView could handle its recyclables cells according to the visible area allocated by the scrollview. Yes this is more difficult to code but it makes sense. – Benoit Aug 09 '16 at 02:01
  • Add nestedScrollView instead of Scrollview it will work fine. – Taimoor Oct 05 '16 at 08:10

26 Answers26

263

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);
    }
}
Reaz Murshed
  • 23,691
  • 13
  • 78
  • 98
DougW
  • 28,776
  • 18
  • 79
  • 107
  • 66
    You've just recreated a very expensive LinearLayout :) – Romain Guy Aug 16 '10 at 18:21
  • 82
    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
  • 1
    If I have list element with eg. `TextView` and in some cases it will contain two lines of text (or more) instead of one then height of whole `ListView` isn't measured correctly. Have solution for that? – croogie Oct 10 '11 at 18:35
  • 2
    For solution by DoughW there is a bug, however i fixed it, see [my post](http://nex-otaku-en.blogspot.com/2010/12/android-put-listview-in-scrollview.html). – Nex Dec 08 '10 at 08:33
  • It might be slow, but when you have a layout you have to stick to, this does the trick! – Rasmus Øvlesen Jan 17 '12 at 15:46
  • 29
    `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));` – Siklab.ph Feb 21 '12 at 05:37
  • I create horizontal list view..and it help to get size of view just by pass no of row. :) – CoDe Mar 04 '13 at 08:06
  • 4
    Fix for ListViews with padding other than 0: `int totalHeight = listView.getPaddingTop() + listView.getPaddingBottom();` – Paul Apr 08 '13 at 11:56
  • 8
    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? – Matt Huggins Jun 09 '13 at 18:16
  • 2
    To answer my own question from above, I ended up fixing this by changing the call to `measure(0, 0)`. Instead, this method should be passed the known width and the unspecified height via `MeasureSpec.makeMeasureSpec()`. For the width, specify measure mode `MeasureSpec.AT_MOST`, and for the height, specify `MeasureSpec.UNSPECIFIED`. Pass the results of these two calls to `listItem.measure()`. – Matt Huggins Jun 09 '13 at 18:58
  • if anyone has any slight issue after using it, where your layout view starts from the end of the ListView before you even start scrolling. just call widget.setFocusableInTouchMode(true); widget.requestFocus(); (where widget could be a widget you choose in your layout) after calling Utility.setListViewHeightBasedOnChildren(yourListView); – irobotxx Jun 14 '13 at 14:08
  • Is this a bad idea or not? I'm not sure if I can use it. – KarenAnne Jul 17 '13 at 05:57
  • 1
    @KarenAnne - You could certainly use this to shoot yourself in the foot, but it's not a bad idea. Performance should not be an issue unless you use it to load a huge listview with a lot of images or something. It worked fine for us 3 years ago when we started using it, and devices have become much more powerful in the meantime. – DougW Jul 17 '13 at 23:26
  • Worked for me, thanks, but there is one issue! when the layout is loaded, I cannot see the top part of the layout. I gave to scroll up to see the top part. Please help! – TharakaNirmana Jul 23 '13 at 09:21
  • i donot know why it takes to much height bcz my list view element height is 70 and when print this line listItem.getMeasuredHeight() it show 316 so im confused – duggu Aug 29 '13 at 13:57
  • am using the above code and it is working fine but footer is missing from the view...How can i fix this. – Madhu Jan 30 '14 at 08:49
  • If there is no items in ListView than last line looks like: "params.height = totalHeight + (listView.getDividerHeight() * (0 - 1));" or "params.height = totalHeight - listView.getDividerHeight();" But why we should deduce dividerHeight if we have no items? Is it error? – alcsan Apr 17 '14 at 12:18
  • i dont know why it isn't working for me.A slight scroll is still there. – Rishabh Srivastava Apr 25 '14 at 13:13
  • @MattHuggins Yous solution for variable height of list-item isn't working in my case. Any suggestion? – Khobaib Apr 26 '14 at 10:58
  • Works very well so far. The only thing with this code is that the bottom list item's bottom horizontal divider doesn't show up. To fix this, on this line: params.height = totalHeight + (listView.getDividerHeight() * (listAdapter.getCount() - 1)); just remove the "- 1". – JDJ May 22 '14 at 20:01
  • I fixed the height of the listview directly from xml. And yes, my listview scrolls for some devices (Nexus 4, HTC One). But for some devices it doesn't scroll. What's wrong here? – Reaz Murshed Sep 09 '14 at 07:39
  • 1
    The line `listItem.measure(...)` will throw a NullPointerException if we use `addHeaderView` in your ListView. – Guicara Sep 25 '14 at 12:08
  • I had a similar problem, but in my case all list items are of same height, so instead of for loop, i just multiplied listitem's height to number of listitems to calculate totalHeight. – Seshu Vinay Nov 04 '14 at 06:17
  • For everyone that gets NPE in listItem.measure(...) make sure your list row xml layout is LinearLayout instead of any other because other layouts (such as RelativeLayout) do not override the onMeasure()... – Marko Niciforovic Nov 24 '14 at 15:22
  • The last listview inside the scrollview is cutting last element iside it everytime it is updated.. – pratz9999 Apr 06 '15 at 08:15
  • @RomainGuy if you guys hadn't written such a horrific SDK, there wouldn't be such a requirement for so many hacks. There's still open bugs from 2010! – StackOverflowed Jun 08 '15 at 23:11
  • View.MeasureSpec.EXACTLY worked for me before that it was View.MeasureSpec.AT_MOST and it was adding extra space in the bottom for example if i have 2 item in list it will add 2 extra item spaces at the end of listview. Anways great solution – umerk44 Nov 29 '15 at 22:02
  • but it will let the `listview.setSelection(int)` don't work. :( – DysaniazzZ Feb 16 '17 at 05:41
199

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.

Muhammad Babar
  • 8,084
  • 5
  • 39
  • 56
Romain Guy
  • 97,993
  • 18
  • 219
  • 200
  • 1
    Not arguing with your point, but can you cite a source that talks about how expensive it is and why? Re-engineering a list of items in a LinearLayout seems much more expensive with regard to dev time. I'd like to know why it's so expensive, and make that trade off decision myself. – DougW Aug 16 '10 at 19:07
  • 70
    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
  • So there is really no way to do this? It is very common and trivial on an iPhone and I am looking for an android analog to use. Is there some workaround? – w.donahue Jan 26 '11 at 06:30
  • 2
    I'm not sure this is also true for a ListView inside a HorizontalScrollView or vice versa. Sounds like a perfectly legal use case for me? – andig Feb 23 '11 at 08:57
  • 3
    Is there an equivalent adapter-based unscrollable list? LinearLayout doesn't utilize an adapter. I'm trying to re-use an existing list adapter. – James Wald Dec 19 '11 at 20:04
  • 6
    You could easily write a static method that creates a LinearLayout from an Adapter. – Romain Guy Dec 19 '11 at 20:05
  • 1
    @Romain Guy : does the tip about using a non-scrollable listView hold for gridView as well ? what if i wish to have a gridview with constant number of items , which can be updated upon clicking somewhere on the screen ? – android developer Jul 10 '12 at 07:39
  • 1
    The best way I could find of adding adding items as if they were in a `listview` within a `scrollview` was by `LinearLayout.addView(View view, LayoutParams params);`. I also create a `Map` with key value pairs that help me track which item was added at what index and how i can actually gain access to the data. – prometheuspk Jul 15 '12 at 13:08
  • 5
    This has been frustrating for me a couple of times. It goes like this: 1, you build a list with listview thinking thats all you need. 2: afterwards design is added with a button/textfield/whatever that goes above or under the list. Listview doesn't allow this. 3: you have to throw away the listview implementation and start over. Conclusion: never use listview unless you're absolutely positive thats all you'll ever need in the design. – Karl Aug 15 '12 at 13:58
  • 3
    @Karl ListView allows this. You can use headers/footers if you want the button/textfield/whatever to scroll with the list. – Romain Guy Aug 29 '12 at 22:24
  • @RomainGuy that's true. Ive only used it for padding before. In some of my implementations I could have used header and footer instead of starting over, thanks for the reminder! But can you add multiple headers? – Karl Aug 31 '12 at 10:01
  • @RomainGuy is my implementation fine? I have also stopped using `Map` and I send my object within the constructor of my `View` – prometheuspk Sep 05 '12 at 07:34
  • 125
    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
  • 45
    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 08 '13 at 04:17
  • 1
    okay, try to answer my question : i have 400 dpi layout (potrait) with listview inside, everything works fine, but when i CHANGE THE VIEW TO LANDSCAPE (i rotate my handphone), the total height of the screen is less than 400 dpi, this why we need to hack listview inside scrollview. – Bhimbim Oct 29 '13 at 11:55
  • 2
    @RomainGuy:Hi, how do you mean of `Just use a LinearLayout instead`? Do you mean that create a `LinearLayout` for each data item pragmatically? – hguser Nov 20 '13 at 00:19
  • There is always an alternative to creativity. It just requires courage for UX designers. – Gökhan Barış Aker Feb 12 '14 at 09:16
  • stop arguing about it, it wont solve the question, someone already find the solution, this is the ANSWER by Moisés Olmedo : http://stackoverflow.com/questions/6210895/listview-inside-scrollview-is-not-scrolling-on-android?rq=1 – Bhimbim Mar 03 '14 at 03:55
  • 2
    here's the real nice gotcha of DIYing this with linearlayout - if you make a linear layout and include several identical layouts using `` tags then you won't have unique id in your subviews and will lose any view state if your activity is recreated. This is presumably something listview+adapter take care of normally. – Sam Jun 11 '15 at 11:10
  • Google material design guidelines show examples of nestedScrollViews housing a cardView that contains a listView. – Luke Allison Jun 01 '16 at 06:22
  • @RomainGuy what about the answer given by Jason Y below ?? – KJEjava48 Jul 12 '19 at 08:43
89

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;
    }
}
Atul Bhardwaj
  • 6,647
  • 5
  • 45
  • 63
25

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 .

Catalina
  • 1,954
  • 1
  • 17
  • 25
Arun
  • 2,800
  • 23
  • 13
  • 3
    This is the most elegant and appropriate answer imho. For more details on this approach, see this answer: http://stackoverflow.com/a/20475821/1221537 – adamdport Mar 10 '15 at 13:50
21

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.

djunod
  • 4,876
  • 2
  • 34
  • 28
  • 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 '14 at 18:13
  • Check my comment here - it gives the perfect solution - http://stackoverflow.com/a/23315696/1433187 – Khobaib Apr 26 '14 at 19:44
  • worked for me with API 19, but not with API 23: Here it adds much white space below the listView. – Lucker10 Jul 26 '16 at 18:24
17

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:

  1. It measures everything correctly (padding, dividers)
  2. It measures the ListView during the measure pass
  3. 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.

Jason Y
  • 527
  • 4
  • 4
  • Also works with variable height items in the listview! – Denny Sep 17 '18 at 19:18
  • @Jason Y what does this, Integer.MAX_VALUE >> 4 means ?? what is 4 there?? – KJEjava48 Jul 11 '19 at 09:38
  • @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. – KJEjava48 Jul 12 '19 at 06:09
13

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/

TalkLittle
  • 8,866
  • 6
  • 54
  • 51
  • 2
    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. – DougW Aug 04 '14 at 19:11
  • this is fast solution –  May 26 '17 at 15:18
11

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

Dedaniya HirenKumar
  • 2,980
  • 2
  • 22
  • 25
8

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);
      }
Ashish Saini
  • 2,328
  • 25
  • 21
  • 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. – Juampa Jul 23 '14 at 02:06
6

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.

JackA
  • 183
  • 2
  • 6
  • 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](https://stackoverflow.com/a/24645448/1106332) worked with a little bit of modification. – Hakan Çelik Mk1 May 02 '18 at 04:09
4

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);
    }
Abandoned Cart
  • 4,512
  • 1
  • 34
  • 41
4

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?

Cheryl Simon
  • 46,552
  • 15
  • 93
  • 82
  • 5
    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
  • 9
    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
  • 2
    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
  • 1
    @ArtemRussakovskii Can you please explain your easy way to replace ListView to LinearLayout with an existing Adapter? – happyhardik Sep 12 '12 at 13:13
  • I have an easy example of why one would want a ListView within a ScrollView. As a matter of fact, I have an activity with 2 ListView and a bunch of other elements. Of course, when I populate one of the two lists beyond a certain limit, it fills the screen and requires some scrolling to get to the bottom of the screen. User Interface patterns should not have messy restrictions, even if they can sometimes be optimized. – Bamaco Apr 04 '15 at 15:54
3

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

Phil Ryan
  • 855
  • 1
  • 10
  • 16
  • One question : When did you call the `setListViewHeightBasedOnChildren()` method? Because it doesn't work all the time for me. – Alexandre D. Jul 09 '14 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 '14 at 04: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 '14 at 07:16
3

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>
Shashank Kapsime
  • 799
  • 5
  • 10
  • 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 ;) – DougW Oct 12 '17 at 07:00
  • 1
    This works with recyclerView but not listView. Thanx – IgniteCoders Nov 15 '17 at 00:21
2

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

            }

        });
PSchuette
  • 4,463
  • 3
  • 19
  • 21
2

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!

Ali
  • 21,572
  • 15
  • 83
  • 95
2

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.

brokedid
  • 879
  • 2
  • 10
  • 35
1

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

Community
  • 1
  • 1
max4ever
  • 11,909
  • 13
  • 77
  • 115
1

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();
}
Community
  • 1
  • 1
Eng.Fouad
  • 115,165
  • 71
  • 313
  • 417
  • :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 09: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 '14 at 18:19
1

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

        }
    }
}
}
Alécio Carvalho
  • 13,481
  • 5
  • 68
  • 74
1

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.

TacoEater
  • 2,115
  • 20
  • 22
0

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.

majinnaibu
  • 2,832
  • 1
  • 18
  • 22
0

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.

Durgadass S
  • 1,098
  • 6
  • 13
0

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.

Saksham
  • 304
  • 4
  • 7
0

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

Zohab Ali
  • 8,426
  • 4
  • 55
  • 63
-1

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();
}
myforums
  • 191
  • 1
  • 9