falc0nit3 falc0nit3 - 3 months ago 44
Android Question

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!

Answer

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.