falc0nit3
falc0nit3

Reputation: 1009

Android Listview Measure Height

I'm trying to find a way to set the height of a listview based on the height of its children items. I followed the solution given here: How can I put a ListView into a ScrollView without it collapsing?

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

        if(listItem != null){
            listItem.setLayoutParams(new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT));                
            listItem.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
            //listItem.measure(0, 0);
            totalHeight += listItem.getMeasuredHeight();
        }
    }

    ViewGroup.LayoutParams params = listView.getLayoutParams();
    params.height = totalHeight + (listView.getDividerHeight() * (listAdapter.getCount() - 1));
    listView.setLayoutParams(params);
    listView.requestLayout();
}

Now I call the above function on the listview like this:

public void displayReviews(ArrayList<Reviews> resultReviews){
    // Hide the loading progress
    hideReviewsLoading();

    if(resultReviews != null && resultReviews.size() > 0){
        mCurrentReviewList.onFetchFinished(resultReviews);
        setListViewHeightBasedOnChildren(mCurrentReviewList.getListView());
    }
    else{
        // Display a generic text to indicate no reviews are in yet
        displayEmptyText();
    }
}

Here above the mCurrentreviewList is a ListFragment which basically has an adapter to set the elements within a layout.

The problem I'm having is that the height of each listitem that it measures is not accurate. And so in the end when all the list items(reviews) are populated the overall listview that contains it, never fully displays all the list items. It cuts off somewhere below - Like only shows 7.5 reviews out of a 10 total.

I'm not sure what I'm doing incorrectly. Any help and direction would be greatly appreciated!

Upvotes: 4

Views: 13490

Answers (4)

X-Mohamed Ahmed
X-Mohamed Ahmed

Reputation: 1

falc0nit3's answer (the accepted one) works for me, however I suggest adding the following code right after the for loop if you use list dividers

totalHeight += listView.getDividerHeight() * i;

So the final code snippet would look like this:

public static void setListViewHeightBasedOnChildren(ListView listView) {
    ListAdapter listAdapter = listView.getAdapter();
    if (listAdapter == null)
        return;

    int desiredWidth = View.MeasureSpec.makeMeasureSpec(listView.getWidth(), View.MeasureSpec.UNSPECIFIED);
    int totalHeight = 0;
    View view = null;
    int i;
    for (i = 0; i < listAdapter.getCount(); i++) {
        view = listAdapter.getView(i, view, listView);
        if (i == 0)
            view.setLayoutParams(new ViewGroup.LayoutParams(desiredWidth, ViewGroup.LayoutParams.WRAP_CONTENT));

        view.measure(desiredWidth, View.MeasureSpec.UNSPECIFIED);
        totalHeight += view.getMeasuredHeight();

    }

    //add divider height to total height as many items as there are in listview
    totalHeight += listView.getDividerHeight()*i;
    ViewGroup.LayoutParams params = listView.getLayoutParams();
    params.height = totalHeight + (listView.getDividerHeight() * (listAdapter.getCount() - 1));
    listView.setLayoutParams(params);
}

Upvotes: 0

d4n13l
d4n13l

Reputation: 126

I don't have enough reputation to comment but regarding falc0nit3's answer, I had this implemented but eventually ran into an out of memory error with large cells that have an image etc.

This is because of the first line View listItem = listAdapter.getView(i, null, listView); of the for loop.

Passing null in as the convertView parameter will lead to the adapter inflating each row in your list. This is not ideal, so instead pass in an already inflated row so that the getView only has to replace the contents inside that row (e.g. inflate the first row of the list yourself and continue to use that). See under 'View Recycling' in the following link http://lucasr.org/2012/04/05/performance-tips-for-androids-listview/

Upvotes: 0

falc0nit3
falc0nit3

Reputation: 1009

Ok looks like I was able to figure out why the measurement was inaccurate. I was trying to get a measurement of the listview's height by passing

listItem.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));

For ListItem's with variable/dynamic height, this will not work out. The measurement has to know at least one variable and in this case it will be the constant width. So, first calculate the desired width of the listview which you know is going to be constant, by using:

int desiredWidth = MeasureSpec.makeMeasureSpec(listView.getWidth(), MeasureSpec.AT_MOST);

and then pass it to the listitem measure. Full code is given below:

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

    int totalHeight = listView.getPaddingTop() + listView.getPaddingBottom();
    int desiredWidth = MeasureSpec.makeMeasureSpec(listView.getWidth(), MeasureSpec.AT_MOST);
    for (int i = 0; i < listAdapter.getCount(); i++) {
        View listItem = listAdapter.getView(i, null, listView);

        if(listItem != null){
            // This next line is needed before you call measure or else you won't get measured height at all. The listitem needs to be drawn first to know the height.
            listItem.setLayoutParams(new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT));
            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();
}

Please Note This is a hackish implementation and isn't the recommended way. Please see How can I put a ListView into a ScrollView without it collapsing? and understand exactly what you are doing before implementing this.

Upvotes: 32

VJ V&#233;lan Solutions
VJ V&#233;lan Solutions

Reputation: 6554

ListView is supposed to scroll - so you can, in theory, have infinite number of items in it. No need to set the length of the ListView based on how many items are inside it.

If you do need to set the length, then that defeats the purpose of having a ListView. Please use some other view like Vertical ScrollView or something for what you require.

Upvotes: 1

Related Questions