Android core developers say you "must not put ListView inside ScrollView", because of many reasons, one of them - ListView has it's own scroll.
When you try just do it, ListView collapses, and you wish it not to be collapsed at all.
To accomplish that, I use a "hack", measuring and setting the height directly:
public class Utility { public static void setListViewHeightBasedOnChildren(ListView listView) { ListAdapter listAdapter = listView.getAdapter(); if (listAdapter == null) { // pre-condition return; } int totalHeight = 0; int desiredWidth = MeasureSpec.makeMeasureSpec(listView.getWidth(), MeasureSpec.AT_MOST); for (int i = 0; i < listAdapter.getCount(); i++) { View listItem = listAdapter.getView(i, null, listView); listItem.measure(desiredWidth, MeasureSpec.UNSPECIFIED); totalHeight += listItem.getMeasuredHeight(); } ViewGroup.LayoutParams params = listView.getLayoutParams(); params.height = totalHeight + (listView.getDividerHeight() * (listAdapter.getCount() - 1)); listView.setLayoutParams(params); listView.requestLayout(); } }
Call this function right after you change ListView items, like that:
Utility.setListViewHeightBasedOnChildren(myListView);
Updated 9 Feb. 2011: fixed one nasty bug :) It all works now!
P.S. Original code is written by DougW at stackoverflow.com, I've modified it a bit.
Awesome, man!
ReplyDeleteI don't mind you modifying and reusing my public work, but a little credit would be nice.
ReplyDeletehttp://stackoverflow.com/questions/3495890/how-can-i-put-a-listview-into-a-scrollview-without-it-collapsing/3495908#3495908
It's Still working in 2015 too. Awesome doughw
DeleteGood for me
ReplyDeleteHi, nex
ReplyDeleteThanks for your code, that works like a charm!
But I've a problem when I use it in my program: when there's just 1 list, that works fine; when there're 3 lists in the scrollable, I can scroll it while the second and third lists don't show normally..There is a enorme space between the second and third list, and after the second list.
I debugged the code and the problem comes from :
listItem.measure(desiredWidth, MeasureSpec.UNSPECIFIED);
totalHeight += listItem.getMeasuredHeight();
The return value of the height is a very very large number.
I can't figure out how to resolve the problem, do you have any ideas?
Thanks !
DougW, sure!
ReplyDeleteAnd thanks for original code, man!
xue, I don't have any clue for your case, maybe there is a problem in layout?
ReplyDeleteI tried this on HTC desire, list is getting displayed but its(listview's) height is increased by 3 times. Could you please post your layout xml file here?
ReplyDeleteCall this function right after you change ListView items, like that:
ReplyDelete-> but I had a problem. each item's height was measured as too high. I managed to solve. like this.
listview.postDelayed(new Runnable() {
public void run() {
Utility.setListViewHeightBasedOnChildren(listview);
}
}, 400);
Anyway, thanks for the code!!! ^^
!!!! u guys save me a days. Thank so muchhhh.
DeleteSpending entire day to solve this problem.
Thank u so muchhhh.
DeleteThanks to all of you guys!
ReplyDeletehad the same problem as cosmos, but you did find the solution. Works perfekt for me!
This solution is still inaccurate - in a list of 30 items, it cuts the last one off for me.
ReplyDeleteArtem, are you sure there is nothing wrong with your layout?
ReplyDeleteIf not, maybe you will find a fix?
Hey man...your a genius...totally solved my problem!
ReplyDeleteArtem, I had the same issue. Found the problem in my adapter, the adapter did not assign text at the correct position of the list. So it either cut the last one off or added white space at the end. After I solved this the height was as desired. I suppose the reason is that my list items have variable height. When the user scrolls a longer text komms at the wrong position making an item longer as it should actually be and therefore longer as calculated.
ReplyDeleteCheers
This doesn't work for me (Android 3.2). It draws the first 2 rows, and nothing else. Still, debugging, the height calculation is correct, as it goes through all of the adapter elements (so it's not an adapter issue).
ReplyDeleteAnyone else has got this problem? Can anyone solve it?
I've been bashing my head trying to get a list inside a scroller to work (I'm just a few days into learning android..) and this code worked perfectly for me, thank you so much!
ReplyDeleteGreat tip! Anyone tried to adapt it for GridView? it seems GridView are missing important methods for that: getnumcolumns is only available from API level 11 and no way to get vertical spacing :(
ReplyDeleteYay :) thank you so much! Spent so long struggling with this recently and now it's all working fine! :)
ReplyDeleteI always get nullpointerexceptions in View.measure. Is there a specific point where or time when I should make this call?
ReplyDeleteI have also a nullpointerexception, do you find solution ? anyone else ?
DeleteI do get NPE as well while executing the measure method.
DeleteMe too:
Deletejava.lang.NullPointerException at android.widget.RelativeLayout.onMeasure(RelativeLayout.java:493)
On Jelly Bean 4.2.
Thank you so much.. it worked great.. but if I want to restrict the height of listview displayed? I tried doing some modification in the code above but no luck.. Please help me in this. Thanks in advance..
ReplyDeleteRahulS, try to change "MeasureSpec.UNSPECIFIED" parameter to your desired height.
ReplyDeleteThis code works fine, i have a problem and it is that when i excute it the layout start showing content from the listview and below but my view used to start from the top
ReplyDeletehey - did u ever find a solution to this problem?
DeleteI have the same issue. On some devices it jumps past the first content down to the listview.
DeleteWay to solve this is to pick any widget in your layout. probably the first ones and do:
Deletewidget.setFocusableInTouchMode(true);
widget.requestFocus();
(where widget could be anythinf you choose in your layout)
use listItem.measure(0, 0); rather than listItem.measure(desiredWidth, MeasureSpec.UNSPECIFIED); for remove unwanted space among controls.
ReplyDeleteAre you still getting scroll events? I seem to get them, but they always say I'm at the bottom of the list when I haven't even scrolled at all
ReplyDeleteThis may be helpful to some people. I have the same issue, but am using Expandable lists. So, I modified the above to handle that:
ReplyDeletepackage com.postmaster;
import android.view.View;
import android.view.View.MeasureSpec;
import android.view.ViewGroup;
import android.widget.ExpandableListAdapter;
import android.widget.ExpandableListView;
public class Utility {
public static void setListViewHeightBasedOnChildren(ExpandableListView listView, int groupid) {
ExpandableListAdapter listAdapter = listView.getExpandableListAdapter();
if (listAdapter == null) {
// pre-condition
return;
}
int totalHeight = 0;
int desiredWidth = MeasureSpec.makeMeasureSpec(listView.getWidth(), MeasureSpec.AT_MOST);
for (int i = 0; i < listAdapter.getGroupCount(); i++) {//Get the number of groups
//Add space for all of the groups
View groupItem = listAdapter.getGroupView(i, false, null, null);
groupItem.measure(desiredWidth, MeasureSpec.UNSPECIFIED);
totalHeight += groupItem.getMeasuredHeight();
/* Need to add space for all of the children. If the group was just clicked,
* it won't already be expanded if we're expanding it. Vice-versa, if we're
* closing it, it will be expanded. So, we will add the space for the children
* if it's expanded and not the one clicked, or if it's not already expanded
* and is the one clicked.
*
*/
if(
((listView.isGroupExpanded(i)) && (i!= groupid)) ||
((!listView.isGroupExpanded(i)) && (i== groupid))
){
for(int j = 0; j < listAdapter.getChildrenCount(i); j++){
View listItem = listAdapter.getChildView(i, j, false, null, null);
listItem.measure(desiredWidth, MeasureSpec.UNSPECIFIED);
totalHeight += listItem.getMeasuredHeight();
}
}
}
ViewGroup.LayoutParams params = listView.getLayoutParams();
int height = totalHeight + (listView.getDividerHeight() * (listAdapter.getGroupCount() - 1));
if (height < 10)
height = 200;
params.height = height;
listView.setLayoutParams(params);
listView.requestLayout();
}
}
I didn't change the original function name since I didn't use normal listviews, so change it if you are using both.
I use the above when one of the group's headers has been clicked. Pass in the id of the group header that has been clicked. i.e.:
ReplyDeletelistView.setOnGroupClickListener(new OnGroupClickListener()
{
@Override
public boolean onGroupClick(ExpandableListView arg0, View arg1, int arg2, long arg3)
{ Utility.setListViewHeightBasedOnChildren(arg0,arg2);
});
It's working just the way i wanted. Great job!
ReplyDeleteTOTALLY GREAT, THANK YOU SO MUCH FROM SPAIN =)
ReplyDeleteIt's working, thank you very much, both of you guys :D
ReplyDeleteHello,
ReplyDeleteI have a problem to implement this. As when I call adapter's notifyDataSetChanged then the listview doesn't scroll to the end but when I set the EndlessAdapter to the listview, it is working fine. Please suggest me why it doesn't scroll when we call notifyDataSetChanged.
Nice job Dude!!!
ReplyDeletehey i got this error
ReplyDelete09-07 11:56:30.100: ERROR/AndroidRuntime(657): FATAL EXCEPTION: main
09-07 11:56:30.100: ERROR/AndroidRuntime(657): java.lang.NullPointerException
09-07 11:56:30.100: ERROR/AndroidRuntime(657): at android.widget.RelativeLayout.onMeasure(RelativeLayout.java:428)
09-07 11:56:30.100: ERROR/AndroidRuntime(657): at android.view.View.measure(View.java:8171)
09-07 11:56:30.100: ERROR/AndroidRuntime(657): at com.amitech.hashchat.utill.TweetView$Utility.setListViewHeightBasedOnChildren(TweetView.java:1072)
09-07 11:56:30.100: ERROR/AndroidRuntime(657): at com.amitech.hashchat.utill.TweetView$getTweet.onPostExecute(TweetView.java:705)
09-07 11:56:30.100: ERROR/AndroidRuntime(657): at com.amitech.hashchat.utill.TweetView$getTweet.onPostExecute(TweetView.java:1)
09-07 11:56:30.100: ERROR/AndroidRuntime(657): at android.os.AsyncTask.finish(AsyncTask.java:417)
09-07 11:56:30.100: ERROR/AndroidRuntime(657): at android.os.AsyncTask.access$300(AsyncTask.java:127)
09-07 11:56:30.100: ERROR/AndroidRuntime(657): at android.os.AsyncTask$InternalHandler.handleMessage(AsyncTask.java:429)
09-07 11:56:30.100: ERROR/AndroidRuntime(657): at android.os.Handler.dispatchMessage(Handler.java:99)
09-07 11:56:30.100: ERROR/AndroidRuntime(657): at android.os.Looper.loop(Looper.java:123)
09-07 11:56:30.100: ERROR/AndroidRuntime(657): at android.app.ActivityThread.main(ActivityThread.java:4627)
09-07 11:56:30.100: ERROR/AndroidRuntime(657): at java.lang.reflect.Method.invokeNative(Native Method)
09-07 11:56:30.100: ERROR/AndroidRuntime(657): at java.lang.reflect.Method.invoke(Method.java:521)
09-07 11:56:30.100: ERROR/AndroidRuntime(657): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:868)
09-07 11:56:30.100: ERROR/AndroidRuntime(657): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:626)
09-07 11:56:30.100: ERROR/AndroidRuntime(657): at dalvik.system.NativeStart.main(Native Method)
any one has solustion
Is it work for Arrayadapter?
ReplyDeleteMust we use CustomAdapater?Plz help me.
any one has solution.
This comment has been removed by the author.
ReplyDeleteGreat it solve my problem.
ReplyDeleteThanks a lot
I use it,but listview onScrollListener donot work.
ReplyDeletewho can tell me why?
thanks
Works like a charm :-) Thanks!
ReplyDeleteAwesome man You saved my time. Thanks a Lot man.
ReplyDeleteThis solution DID NOT work for me when I have a view with children that wrapped across multiple "lines".
ReplyDeleteInstead I found this worked: http://stackoverflow.com/a/5720141/333427
Here's the contents to simplify your life!
1) Create a new class that extends ListView:
package com.example.android.views;
import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.widget.ListView;
public class ExpandedListView extends ListView {
private android.view.ViewGroup.LayoutParams params;
private int old_count = 0;
public ExpandedListView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onDraw(Canvas canvas) {
if (getCount() != old_count) {
old_count = getCount();
params = getLayoutParams();
params.height = getCount() * (old_count > 0 ? getChildAt(0).getHeight() : 0);
setLayoutParams(params);
}
super.onDraw(canvas);
}
}
2)Inside your xml layout file:
<com.example.android.views.ExpandedListView
android:id="@+id/list"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:scrollbars="none"
android:padding="0px"
/>
This comment has been removed by the author.
ReplyDeleteThis is not working for my side can u please tell me why...My layout is like
ReplyDeleteI have the following structure
ReplyDeleteBut leave me much space under the Listview, maybe about 10 cms before displaying the TextView, some solution.
Here the full xml
http://pastebin.com/PAx50nZv
I am having same problem of height...after last item in the list scroll appears to nothing...Is there problem with layout or somthing different went wrong?
ReplyDeleteSolved issue of extra height of listview...just change int desiredWidth = MeasureSpec.makeMeasureSpec(listView.getWidth(),
DeleteMeasureSpec.UNSPECIFIED); by int desiredWidth = MeasureSpec.makeMeasureSpec(listView.getWidth(),
MeasureSpec.EXACTLY);
UNSPECIFIED is replaced by EXACTLY
thats it...
its working fine but i have problem that.. footer menu also changing the position... can you tell where i need to change the code
ReplyDeleteIts Awesome Thanks a lots ....:)
ReplyDeletesuch cooooool idea .....thnx man :)
ReplyDeleteThank you very much for the hack
ReplyDeleteThis comment has been removed by the author.
ReplyDeleteGreat !! Thank you very much. It solve my problem.
ReplyDeleteThank you very much, solve my problem too.
ReplyDeletekudos great answer code working like charm........
ReplyDeletewow wow wow,
ReplyDeleteYou save my whole day by giving this solution.
A huge thanks to you and creator