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

26

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?

Consensual answered 16/8, 2010 at 18:0 Comment(13)
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.Consensual
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.Wilson
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.Consensual
@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?Urine
@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.Jeb
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.Brickyard
@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)?Novelia
No there isn't, sorry.Wilson
@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.Cloison
@Cloison is there a way if I'm using a GridView?Supinator
Google material design guidelines instructs you to use a nestedScrollView with a cardView that contains a listView.Jellaba
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.Polycarp
Add nestedScrollView instead of Scrollview it will work fine.Pori
W
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.

Wilson answered 16/8, 2010 at 18:19 Comment(22)
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.Consensual
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.Wilson
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.Consensual
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?Fenella
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?Manipulate
Is there an equivalent adapter-based unscrollable list? LinearLayout doesn't utilize an adapter. I'm trying to re-use an existing list adapter.Polychasium
You could easily write a static method that creates a LinearLayout from an Adapter.Wilson
@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 ?Tilbury
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.Dmz
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.Beebe
@Beebe ListView allows this. You can use headers/footers if you want the button/textfield/whatever to scroll with the list.Wilson
@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?Beebe
@RomainGuy is my implementation fine? I have also stopped using Map and I send my object within the constructor of my ViewDmz
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.Pyrimidine
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.Dorelle
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.Unwind
@RomainGuy:Hi, how do you mean of Just use a LinearLayout instead? Do you mean that create a LinearLayout for each data item pragmatically?Levenson
There is always an alternative to creativity. It just requires courage for UX designers.Burgas
stop arguing about it, it wont solve the question, someone already find the solution, this is the ANSWER by Moisés Olmedo : #6211395Unwind
here's the real nice gotcha of DIYing this with linearlayout - if you make a linear layout and include several identical layouts using <include /> 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.Romanfleuve
Google material design guidelines show examples of nestedScrollViews housing a cardView that contains a listView.Jellaba
@RomainGuy what about the answer given by Jason Y below ??Northwesterly
C
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);
    }
}
Consensual answered 16/8, 2010 at 18:4 Comment(28)
You've just recreated a very expensive LinearLayout :)Wilson
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?Semaphore
For solution by DoughW there is a bug, however i fixed it, see my post.Automobile
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?Octastyle
It might be slow, but when you have a layout you have to stick to, this does the trick!Irksome
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));Skewbald
I create horizontal list view..and it help to get size of view just by pass no of row. :)Woodsia
Fix for ListViews with padding other than 0: int totalHeight = listView.getPaddingTop() + listView.getPaddingBottom();Toccaratoccata
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?Ranunculaceous
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().Ranunculaceous
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);Wolfish
Is this a bad idea or not? I'm not sure if I can use it.Flexure
@Flexure - 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.Consensual
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!Gristede
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 confusedWhacky
am using the above code and it is working fine but footer is missing from the view...How can i fix this.Casavant
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?Lustrous
i dont know why it isn't working for me.A slight scroll is still there.Giaour
@MattHuggins Yous solution for variable height of list-item isn't working in my case. Any suggestion?Flatfoot
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".Precancel
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?Rodriques
The line listItem.measure(...) will throw a NullPointerException if we use addHeaderView in your ListView.Lemmy
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.Pilothouse
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()...Wanderjahr
The last listview inside the scrollview is cutting last element iside it everytime it is updated..Polemic
@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!Teratism
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 solutionAiglet
but it will let the listview.setSelection(int) don't work. :(Deice
W
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.

Wilson answered 16/8, 2010 at 18:19 Comment(22)
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.Consensual
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.Wilson
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.Consensual
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?Fenella
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?Manipulate
Is there an equivalent adapter-based unscrollable list? LinearLayout doesn't utilize an adapter. I'm trying to re-use an existing list adapter.Polychasium
You could easily write a static method that creates a LinearLayout from an Adapter.Wilson
@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 ?Tilbury
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.Dmz
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.Beebe
@Beebe ListView allows this. You can use headers/footers if you want the button/textfield/whatever to scroll with the list.Wilson
@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?Beebe
@RomainGuy is my implementation fine? I have also stopped using Map and I send my object within the constructor of my ViewDmz
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.Pyrimidine
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.Dorelle
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.Unwind
@RomainGuy:Hi, how do you mean of Just use a LinearLayout instead? Do you mean that create a LinearLayout for each data item pragmatically?Levenson
There is always an alternative to creativity. It just requires courage for UX designers.Burgas
stop arguing about it, it wont solve the question, someone already find the solution, this is the ANSWER by Moisés Olmedo : #6211395Unwind
here's the real nice gotcha of DIYing this with linearlayout - if you make a linear layout and include several identical layouts using <include /> 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.Romanfleuve
Google material design guidelines show examples of nestedScrollViews housing a cardView that contains a listView.Jellaba
@RomainGuy what about the answer given by Jason Y below ??Northwesterly
N
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;
    }
}
Noncommittal answered 10/12, 2012 at 6:40 Comment(4)
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.Spohr
Has anyone noticed any cons with this approach? Its so simple I'm afraid :oAmble
Nice solution, should be the accepted answer +1 ! I've mixed it with the answer below (from djunod), and that work great !Marchpast
This is the only fix that makes scrolling AND CLICK on children working at the same time for me. Big thanksTeeters
M
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 .

Monahan answered 31/12, 2012 at 12:50 Comment(1)
This is the most elegant and appropriate answer imho. For more details on this approach, see this answer: https://mcmap.net/q/37220/-android-list-view-inside-a-scroll-viewExistential
S
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.

Snotty answered 20/7, 2013 at 18:46 Comment(3)
It won't work when list-items are of variable sizes, i.e. some textview that expand over multiple lines. Any solution?Flatfoot
Check my comment here - it gives the perfect solution - https://mcmap.net/q/37081/-how-can-i-put-a-listview-into-a-scrollview-without-it-collapsingFlatfoot
worked for me with API 19, but not with API 23: Here it adds much white space below the listView.Salazar
T
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.

Tarratarradiddle answered 17/4, 2015 at 20:1 Comment(3)
Also works with variable height items in the listview!Falsecard
@Jason Y what does this, Integer.MAX_VALUE >> 4 means ?? what is 4 there??Northwesterly
@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.Northwesterly
B
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/

Burgenland answered 4/8, 2014 at 5:50 Comment(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.Consensual
this is fast solutionBreathed
P
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);

Perryperryman answered 24/5, 2016 at 10:28 Comment(0)
G
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);
      }
Gourami answered 12/7, 2014 at 6:37 Comment(1)
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.Whippoorwill
S
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.

Seema answered 7/11, 2016 at 7:30 Comment(1)
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.Skylar
K
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?

Kisangani answered 16/8, 2010 at 18:2 Comment(6)
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;Consensual
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.Kisangani
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.Consensual
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.Yonyona
@ArtemRussakovskii Can you please explain your easy way to replace ListView to LinearLayout with an existing Adapter?Buckshot
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.Anticlimax
G
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);
    }
Galliwasp answered 4/8, 2013 at 19:29 Comment(4)
Where to call this method?Kleper
After creating the listview and setting the adapter.Galliwasp
This gets too slow on a long lists.Quaquaversal
@Quaquaversal 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.Galliwasp
A
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. :-)

Acicula answered 9/7, 2014 at 4:31 Comment(3)
One question : When did you call the setListViewHeightBasedOnChildren() method? Because it doesn't work all the time for me.Verleneverlie
@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.Acicula
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)?Verleneverlie
B
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>
Burbage answered 10/10, 2017 at 5:25 Comment(2)
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 ;)Consensual
This works with recyclerView but not listView. ThanxMatthieu
N
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();

            }

        });
Nertie answered 11/5, 2012 at 15:59 Comment(0)
I
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!

Improve answered 31/3, 2014 at 0:37 Comment(0)
P
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.

Piebald answered 24/7, 2015 at 7:52 Comment(0)
B
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 :)

Beisel answered 22/6, 2012 at 14:1 Comment(0)
T
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();
}
Trochal answered 23/9, 2013 at 18:39 Comment(2)
: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?Levenson
I need a solution when list-items are of variable sizes, i.e. some textview that expand over multiple lines. Any suggestion?Flatfoot
S
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);

        }
    }
}
}
Steamer answered 19/2, 2014 at 11:8 Comment(0)
S
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.

Spindling answered 10/2, 2016 at 20:56 Comment(0)
A
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.

Aubigny answered 13/1, 2013 at 19:41 Comment(0)
E
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.

Earldom answered 24/10, 2014 at 4:36 Comment(0)
P
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.

Periphery answered 14/3, 2016 at 14:55 Comment(0)
H
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

Harms answered 31/1, 2021 at 9:8 Comment(0)
G
-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();
}
Glynisglynn answered 13/2, 2014 at 23:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.